spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

前言

上一篇文章《spring中這些能升華代碼的技巧掸刊,可能會(huì)讓你愛不釋手》發(fā)表之后云茸,受到了不少讀者的好評(píng),很多讀者都在期待續(xù)集妹萨。今天非常高興的通知大家年枕,你們要的續(xù)集來(lái)了。本文繼續(xù)總結(jié)我認(rèn)為spring中還不錯(cuò)的知識(shí)點(diǎn)乎完,希望對(duì)您有所幫助熏兄。

一. @Conditional的強(qiáng)大之處

不知道你們有沒有遇到過(guò)這些問(wèn)題:

  1. 某個(gè)功能需要根據(jù)項(xiàng)目中有沒有某個(gè)jar判斷是否開啟該功能。
  2. 某個(gè)bean的實(shí)例化需要先判斷另一個(gè)bean有沒有實(shí)例化树姨,再判斷是否實(shí)例化自己摩桶。
  3. 某個(gè)功能是否開啟,在配置文件中有個(gè)參數(shù)可以對(duì)它進(jìn)行控制帽揪。

如果你有遇到過(guò)上述這些問(wèn)題硝清,那么恭喜你,本節(jié)內(nèi)容非常適合你转晰。

@ConditionalOnClass

問(wèn)題1可以用@ConditionalOnClass注解解決耍缴,代碼如下:

public  class A {
}

public  class B {
}

@ConditionalOnClass(B.class)
@Configuration
public class TestConfiguration {

    @Bean
    public A a() {
      return    new A();
    }
}

如果項(xiàng)目中存在B類,則會(huì)實(shí)例化A類挽霉。如果不存在B類防嗡,則不會(huì)實(shí)例化A類。

有人可能會(huì)問(wèn):不是判斷有沒有某個(gè)jar嗎侠坎?怎么現(xiàn)在判斷某個(gè)類了蚁趁?

?

直接判斷有沒有該jar下的某個(gè)關(guān)鍵類更簡(jiǎn)單。

?

這個(gè)注解有個(gè)升級(jí)版的應(yīng)用場(chǎng)景:比如common工程中寫了一個(gè)發(fā)消息的工具類mqTemplate实胸,業(yè)務(wù)工程引用了common工程他嫡,只需再引入消息中間件番官,比如rocketmq的jar包,就能開啟mqTemplate的功能钢属。而如果有另一個(gè)業(yè)務(wù)工程徘熔,通用引用了common工程,如果不需要發(fā)消息的功能淆党,不引入rocketmq的jar包即可酷师。

這個(gè)注解的功能還是挺實(shí)用的吧?

@ConditionalOnBean

問(wèn)題2可以通過(guò)@ConditionalOnBean注解解決染乌,代碼如下:

@Configuration
public  class TestConfiguration {

    @Bean
    public B b() {
        return  new B();
    }

    @ConditionalOnBean(name="b")
    @Bean
    public A a() {
      return    new A();
    }
}

實(shí)例A只有在實(shí)例B存在時(shí)山孔,才能實(shí)例化。

@ConditionalOnProperty

問(wèn)題3可以通過(guò)@ConditionalOnProperty注解解決荷憋,代碼如下:

@ConditionalOnProperty(prefix = "demo",name="enable", havingValue = "true",matchIfMissing=true )
@Configuration
public  class TestConfiguration {

    @Bean
    public A a() {
      return    new A();
    }
}

在applicationContext.properties文件中配置參數(shù):

demo.enable=false

各參數(shù)含義:

  • prefix 表示參數(shù)名的前綴台颠,這里是demo* name 表示參數(shù)名* havingValue 表示指定的值,參數(shù)中配置的值需要跟指定的值比較是否相等勒庄,相等才滿足條件* matchIfMissing 表示是否允許缺省配置串前。

這個(gè)功能可以作為開關(guān),相比EnableXXX注解的開關(guān)更優(yōu)雅实蔽,因?yàn)樗梢酝ㄟ^(guò)參數(shù)配置是否開啟酪呻,而EnableXXX注解的開關(guān)需要在代碼中硬編碼開啟或關(guān)閉。

其他的Conditional注解

當(dāng)然盐须,spring用得比較多的Conditional注解還有:ConditionalOnMissingClass、ConditionalOnMissingBean漆腌、ConditionalOnWebApplication等贼邓。

下面用一張圖整體認(rèn)識(shí)一下@Conditional家族。

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

自定義Conditional

說(shuō)實(shí)話闷尿,個(gè)人認(rèn)為springboot自帶的Conditional系列已經(jīng)可以滿足我們絕大多數(shù)的需求了塑径。但如果你有比較特殊的場(chǎng)景,也可以自定義自定義Conditional填具。

第一步统舀,自定義注解:

@Conditional(MyCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public  @interface MyConditionOnProperty {
    String name() default "";

    String havingValue() default "";
}

第二步,實(shí)現(xiàn)Condition接口:

public  class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        System.out.println("實(shí)現(xiàn)自定義邏輯");
        return  false;
    }
}

第三步劳景,使用@MyConditionOnProperty注解誉简。

Conditional的奧秘就藏在ConfigurationClassParser類的processConfigurationClass方法中:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

這個(gè)方法邏輯不復(fù)雜:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)
  1. 先判斷有沒有使用Conditional注解,如果沒有直接返回false
  2. 收集condition到集合中
  3. 按order排序該集合
  4. 遍歷該集合盟广,循環(huán)調(diào)用condition的matchs方法闷串。

二. 如何妙用@Import?

有時(shí)我們需要在某個(gè)配置類中引入另外一些類筋量,被引入的類也加到spring容器中烹吵。這時(shí)可以使用@Import注解完成這個(gè)功能碉熄。

如果你看過(guò)它的源碼會(huì)發(fā)現(xiàn),引入的類支持三種不同類型肋拔。

但是我認(rèn)為最好將普通類和@Configuration注解的配置類分開講解锈津,所以列了四種不同類型:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

普通類

這種引入方式是最簡(jiǎn)單的,被引入的類會(huì)被實(shí)例化bean對(duì)象凉蜂。

public  class A {
}

@Import(A.class)
@Configuration
public class TestConfiguration {
}

通過(guò)@Import注解引入A類琼梆,spring就能自動(dòng)實(shí)例化A對(duì)象,然后在需要使用的地方通過(guò)@Autowired注解注入即可:

   @Autowired
private A a;

是不是挺讓人意外的跃惫?不用加@Bean注解也能實(shí)例化bean叮叹。

@Configuration注解的配置類

這種引入方式是最復(fù)雜的,因?yàn)锧Configuration注解還支持多種組合注解爆存,比如:

  • @Import* @ImportResource* @PropertySource等蛉顽。
public  class A {
}

public  class B {
}

@Import(B.class)
@Configuration
public class AConfiguration {

    @Bean
    public A a() {
        return  new A();
    }
}

@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}

通過(guò)@Import注解引入@Configuration注解的配置類,會(huì)把該配置類相關(guān)@Import先较、@ImportResource携冤、@PropertySource等注解引入的類進(jìn)行遞歸,一次性全部引入闲勺。

由于文章篇幅有限不過(guò)多介紹了曾棕,這里留點(diǎn)懸念,后面會(huì)出一篇文章專門介紹@Configuration注解菜循,因?yàn)樗鼘?shí)在太太太重要了翘地。

實(shí)現(xiàn)ImportSelector接口的類

這種引入方式需要實(shí)現(xiàn)ImportSelector接口:

public  class AImportSelector implements ImportSelector {

private  static  final String CLASS_NAME = "com.sue.cache.service.test13.A";

 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return  new String[]{CLASS_NAME};
    }
}

@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}

這種方式的好處是selectImports方法返回的是數(shù)組,意味著可以同時(shí)引入多個(gè)類癌幕,還是非常方便的衙耕。

實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類

這種引入方式需要實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口:

public  class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
        registry.registerBeanDefinition("a", rootBeanDefinition);
    }
}

@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
}

這種方式是最靈活的,能在registerBeanDefinitions方法中獲取到BeanDefinitionRegistry容器注冊(cè)對(duì)象勺远,可以手動(dòng)控制BeanDefinition的創(chuàng)建和注冊(cè)橙喘。

當(dāng)然@import注解非常人性化,還支持同時(shí)引入多種不同類型的類胶逢。

@Import({B.class,AImportBeanDefinitionRegistrar.class})
@Configuration
public class TestConfiguration {
}

這四種引入類的方式各有千秋厅瞎,總結(jié)如下:

  1. 普通類,用于創(chuàng)建沒有特殊要求的bean實(shí)例初坠。
  2. @Configuration注解的配置類和簸,用于層層嵌套引入的場(chǎng)景。
  3. 實(shí)現(xiàn)ImportSelector接口的類碟刺,用于一次性引入多個(gè)類的場(chǎng)景比搭,或者可以根據(jù)不同的配置決定引入不同類的場(chǎng)景。
  4. 實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類,主要用于可以手動(dòng)控制BeanDefinition的創(chuàng)建和注冊(cè)的場(chǎng)景身诺,它的方法中可以獲取BeanDefinitionRegistry注冊(cè)容器對(duì)象蜜托。

在ConfigurationClassParser類的processImports方法中可以看到這三種方式的處理邏輯:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

最后的else方法其實(shí)包含了:普通類和@Configuration注解的配置類兩種不同的處理邏輯。

三. @ConfigurationProperties賦值

我們?cè)陧?xiàng)目中使用配置參數(shù)是非常常見的場(chǎng)景霉赡,比如橄务,我們?cè)谂渲镁€程池的時(shí)候,需要在applicationContext.propeties文件中定義如下配置:

thread.pool.corePoolSize=5
thread.pool.maxPoolSize=10
thread.pool.queueCapacity=200
thread.pool.keepAliveSeconds=30

方法一:通過(guò)@Value注解讀取這些配置穴亏。

public  class ThreadPoolConfig {

    @Value("${thread.pool.corePoolSize:5}")
    private  int corePoolSize;

    @Value("${thread.pool.maxPoolSize:10}")
    private  int maxPoolSize;

    @Value("${thread.pool.queueCapacity:200}")
    private  int queueCapacity;

    @Value("${thread.pool.keepAliveSeconds:30}")
    private  int keepAliveSeconds;

    @Value("${thread.pool.threadNamePrefix:ASYNC_}")
    private String threadNamePrefix;

    @Bean
    public Executor threadPoolExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

這種方式使用起來(lái)非常簡(jiǎn)單蜂挪,但建議在使用時(shí)都加上:,因?yàn)?后面跟的是默認(rèn)值嗓化,比如:@Value("${thread.pool.corePoolSize:5}")棠涮,定義的默認(rèn)核心線程數(shù)是5。

?

假如有這樣的場(chǎng)景:business工程下定義了這個(gè)ThreadPoolConfig類刺覆,api工程引用了business工程严肪,同時(shí)job工程也引用了business工程,而ThreadPoolConfig類只想在api工程中使用谦屑。這時(shí)驳糯,如果不配置默認(rèn)值,job工程啟動(dòng)的時(shí)候可能會(huì)報(bào)錯(cuò)氢橙。

?

如果參數(shù)少還好酝枢,多的話,需要給每一個(gè)參數(shù)都加上@Value注解悍手,是不是有點(diǎn)麻煩帘睦?

此外,還有一個(gè)問(wèn)題坦康,@Value注解定義的參數(shù)看起來(lái)有點(diǎn)分散竣付,不容易辨別哪些參數(shù)是一組的。

這時(shí)涝焙,@ConfigurationProperties就派上用場(chǎng)了,它是springboot中新加的注解孕暇。

第一步仑撞,先定義ThreadPoolProperties類

@Data
@Component
@ConfigurationProperties("thread.pool")
public  class ThreadPoolProperties {

    private  int corePoolSize;
    private  int maxPoolSize;
    private  int queueCapacity;
    private  int keepAliveSeconds;
    private String threadNamePrefix;
}

第二步,使用ThreadPoolProperties類

@Configuration
public  class ThreadPoolConfig {

    @Autowired
    private ThreadPoolProperties threadPoolProperties;

    @Bean
    public Executor threadPoolExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(threadPoolProperties.getCorePoolSize());
        executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
        executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
        executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
        executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

使用@ConfigurationProperties注解妖滔,可以將thread.pool開頭的參數(shù)直接賦值到ThreadPoolProperties類的同名參數(shù)中隧哮,這樣省去了像@Value注解那樣一個(gè)個(gè)手動(dòng)去對(duì)應(yīng)的過(guò)程。

這種方式顯然要方便很多座舍,我們只需編寫xxxProperties類沮翔,spring會(huì)自動(dòng)裝配參數(shù)。此外曲秉,不同系列的參數(shù)可以定義不同的xxxProperties類采蚀,也便于管理疲牵,推薦優(yōu)先使用這種方式。

它的底層是通過(guò):ConfigurationPropertiesBindingPostProcessor類實(shí)現(xiàn)的榆鼠,該類實(shí)現(xiàn)了BeanPostProcessor接口纲爸,在postProcessBeforeInitialization方法中解析@ConfigurationProperties注解,并且綁定數(shù)據(jù)到相應(yīng)的對(duì)象上妆够。

綁定是通過(guò)Binder類的bindObject方法完成的:

[圖片上傳失敗...(image-62429-1611580371900)]

以上這段代碼會(huì)遞歸綁定數(shù)據(jù)识啦,主要考慮了三種情況:

  • bindAggregate 綁定集合類* bindBean 綁定對(duì)象* bindProperty 綁定參數(shù) 前面兩種情況最終也會(huì)調(diào)用到bindProperty方法。

「此外神妹,友情提醒一下:」

使用@ConfigurationProperties注解有些場(chǎng)景有問(wèn)題颓哮,比如:在apollo中修改了某個(gè)參數(shù),正常情況可以動(dòng)態(tài)更新到@ConfigurationProperties注解定義的xxxProperties類的對(duì)象中鸵荠,但是如果出現(xiàn)比較復(fù)雜的對(duì)象冕茅,比如:

private Map<String, Map<String,String>>  urls;

可能動(dòng)態(tài)更新不了。

這時(shí)候該怎么辦呢腰鬼?

答案是使用ApolloConfigChangeListener監(jiān)聽器自己處理:

@ConditionalOnClass(com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig.class)
public class ApolloConfigurationAutoRefresh implements ApplicationContextAware {
   private ApplicationContext applicationContext;

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
   }

    @ApolloConfigChangeListener
    private void onChange(ConfigChangeEvent changeEvent{
        refreshConfig(changeEvent.changedKeys());
    }
    private void refreshConfig(Set<String> changedKeys){
       System.out.println("將變更的參數(shù)更新到相應(yīng)的對(duì)象中");
    }
}

四. spring事務(wù)要如何避坑嵌赠?

spring中的事務(wù)功能主要分為:聲明式事務(wù)和編程式事務(wù)。

聲明式事務(wù)

大多數(shù)情況下熄赡,我們?cè)陂_發(fā)過(guò)程中使用更多的可能是聲明式事務(wù)姜挺,即使用@Transactional注解定義的事務(wù),因?yàn)樗闷饋?lái)更簡(jiǎn)單彼硫,方便炊豪。

只需在需要執(zhí)行的事務(wù)方法上,加上@Transactional注解就能自動(dòng)開啟事務(wù):

@Service
public  class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

這種聲明式事務(wù)之所以能生效拧篮,是因?yàn)樗牡讓邮褂昧薃OP词渤,創(chuàng)建了代理對(duì)象,調(diào)用TransactionInterceptor攔截器實(shí)現(xiàn)事務(wù)的功能串绩。

?

spring事務(wù)有個(gè)特別的地方:它獲取的數(shù)據(jù)庫(kù)連接放在ThreadLocal中的缺虐,也就是說(shuō)同一個(gè)線程中從始至終都能獲取同一個(gè)數(shù)據(jù)庫(kù)連接,可以保證同一個(gè)線程中多次數(shù)據(jù)庫(kù)操作在同一個(gè)事務(wù)中執(zhí)行礁凡。

?

正常情況下是沒有問(wèn)題的高氮,但是如果使用不當(dāng),事務(wù)會(huì)失效顷牌,主要原因如下:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

除了上述列舉的問(wèn)題之外剪芍,由于@Transactional注解最小粒度是要被定義在方法上,如果有多層的事務(wù)方法調(diào)用窟蓝,可能會(huì)造成大事務(wù)問(wèn)題罪裹。

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

所以,建議在實(shí)際工作中少用@Transactional注解開啟事務(wù)。

編程式事務(wù)

一般情況下編程式事務(wù)我們可以通過(guò)TransactionTemplate類開啟事務(wù)功能状共。有個(gè)好消息套耕,就是springboot已經(jīng)默認(rèn)實(shí)例化好這個(gè)對(duì)象了,我們能直接在項(xiàng)目中使用口芍。

@Service
public  class UserService {
   @Autowired
   private TransactionTemplate transactionTemplate;

   ...

   public void save(final User user) {
         transactionTemplate.execute((status) => {
            doSameThing...
            return Boolean.TRUE;
         })
   }
}

使用TransactionTemplate的編程式事務(wù)能避免很多事務(wù)失效的問(wèn)題箍铲,但是對(duì)大事務(wù)問(wèn)題,不一定能夠解決鬓椭,只是說(shuō)相對(duì)于使用@Transactional注解要好些颠猴。

五. 跨域問(wèn)題的解決方案

關(guān)于跨域問(wèn)題,前后端的解決方案還是挺多的小染,這里我重點(diǎn)說(shuō)說(shuō)spring的解決方案翘瓮,目前有三種:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

一.使用@CrossOrigin注解

@RequestMapping("/user")
@RestController
public  class UserController {

    @CrossOrigin(origins = "http://localhost:8016")
    @RequestMapping("/getUser")
    public String getUser(@RequestParam("name") String name) {
        System.out.println("name:" + name);
        return  "success";
    }
}

該方案需要在跨域訪問(wèn)的接口上加@CrossOrigin注解,訪問(wèn)規(guī)則可以通過(guò)注解中的參數(shù)控制裤翩,控制粒度更細(xì)资盅。如果需要跨域訪問(wèn)的接口數(shù)量較少,可以使用該方案踊赠。

二.增加全局配置

@Configuration
public  class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");

    }
}

該方案需要實(shí)現(xiàn)WebMvcConfigurer接口呵扛,重寫addCorsMappings方法,在該方法中定義跨域訪問(wèn)的規(guī)則筐带。這是一個(gè)全局的配置今穿,可以應(yīng)用于所有接口。

三.自定義過(guò)濾器

@WebFilter("corsFilter")
@Configuration
public  class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET");
        httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

該方案通過(guò)在請(qǐng)求的header中增加Access-Control-Allow-Origin等參數(shù)解決跨域問(wèn)題伦籍。

順便說(shuō)一下蓝晒,使用@CrossOrigin注解 和 實(shí)現(xiàn)WebMvcConfigurer接口的方案,spring在底層最終都會(huì)調(diào)用到DefaultCorsProcessor類的handleInternal方法:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

最終三種方案殊途同歸帖鸦,都會(huì)往header中添加跨域需要參數(shù)芝薇,只是實(shí)現(xiàn)形式不一樣而已田度。

六. 如何自定義starter

以前在沒有使用starter時(shí)床嫌,我們?cè)陧?xiàng)目中需要引入新功能歹鱼,步驟一般是這樣的:

  • 在maven倉(cāng)庫(kù)找該功能所需jar包* 在maven倉(cāng)庫(kù)找該jar所依賴的其他jar包* 配置新功能所需參數(shù)

以上這種方式會(huì)帶來(lái)三個(gè)問(wèn)題:

  1. 如果依賴包較多院仿,找起來(lái)很麻煩,容易找錯(cuò)掰吕,而且要花很多時(shí)間为肮。
  2. 各依賴包之間可能會(huì)存在版本兼容性問(wèn)題初烘,項(xiàng)目引入這些jar包后口注,可能沒法正常啟動(dòng)变擒。
  3. 如果有些參數(shù)沒有配好君珠,啟動(dòng)服務(wù)也會(huì)報(bào)錯(cuò)寝志,沒有默認(rèn)配置。

「為了解決這些問(wèn)題,springboot的starter機(jī)制應(yīng)運(yùn)而生」材部。

starter機(jī)制帶來(lái)這些好處:

  1. 它能啟動(dòng)相應(yīng)的默認(rèn)配置毫缆。
  2. 它能夠管理所需依賴,擺脫了需要到處找依賴 和 兼容性問(wèn)題的困擾乐导。
  3. 自動(dòng)發(fā)現(xiàn)機(jī)制苦丁,將spring.factories文件中配置的類,自動(dòng)注入到spring容器中物臂。
  4. 遵循“約定大于配置”的理念旺拉。

在業(yè)務(wù)工程中只需引入starter包,就能使用它的功能棵磷,太爽了蛾狗。

下面用一張圖,總結(jié)starter的幾個(gè)要素:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

接下來(lái)我們一起實(shí)戰(zhàn)仪媒,定義一個(gè)自己的starter沉桌。

第一步,創(chuàng)建id-generate-starter工程:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

其中的pom.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <version>1.3.1</version>
    <groupId>com.sue</groupId>
    <artifactId>id-generate-spring-boot-starter</artifactId>
    <name>id-generate-spring-boot-starter</name>
    <dependencies>
        <dependency>
            <groupId>com.sue</groupId>
            <artifactId>id-generate-spring-boot-autoconfigure</artifactId>
            <version>1.3.1</version>
        </dependency>
    </dependencies>
</project>

第二步算吩,創(chuàng)建id-generate-spring-boot-autoconfigure工程:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

該項(xiàng)目當(dāng)中包含:

  • pom.xml* spring.factories* IdGenerateAutoConfiguration* IdGenerateService* IdProperties pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <version>1.3.1</version>
    <groupId>com.sue</groupId>
    <artifactId>id-generate-spring-boot-autoconfigure</artifactId>
    <name>id-generate-spring-boot-autoconfigure</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

spring.factories配置如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.sue.IdGenerateAutoConfiguration

IdGenerateAutoConfiguration類:

@ConditionalOnClass(IdProperties.class)
@EnableConfigurationProperties(IdProperties.class)
@Configuration
public class IdGenerateAutoConfiguration {

    @Autowired
    private IdProperties properties;

    @Bean
    public IdGenerateService idGenerateService() {
        return  new IdGenerateService(properties.getWorkId());
    }
}

IdGenerateService類:

public  class IdGenerateService {

    private Long workId;

    public IdGenerateService(Long workId) {
        this.workId = workId;
    }

    public Long generate() {
        return  new Random().nextInt(100) + this.workId;
    }
}

IdProperties類:

@ConfigurationProperties(prefix = IdProperties.PREFIX)
public  class IdProperties {

    public  static  final String PREFIX = "sue";

    private Long workId;

    public Long getWorkId() {
        return workId;
    }

    public void setWorkId(Long workId) {
        this.workId = workId;
    }
}

這樣在業(yè)務(wù)項(xiàng)目中引入相關(guān)依賴:

<dependency>
      <groupId>com.sue</groupId>
      <artifactId>id-generate-spring-boot-starter</artifactId>
      <version>1.3.1</version>
</dependency>

就能使用注入使用IdGenerateService的功能了

  @Autowired
  private IdGenerateService idGenerateService;

完美留凭。

七.項(xiàng)目啟動(dòng)時(shí)的附加功能

有時(shí)候我們需要在項(xiàng)目啟動(dòng)時(shí)定制化一些附加功能,比如:加載一些系統(tǒng)參數(shù)偎巢、完成初始化蔼夜、預(yù)熱本地緩存等,該怎么辦呢艘狭?

好消息是springboot提供了:

  • CommandLineRunner* ApplicationRunner

這兩個(gè)接口幫助我們實(shí)現(xiàn)以上需求挎扰。

它們的用法還是挺簡(jiǎn)單的,以ApplicationRunner接口為例:

@Component
public  class TestRunner implements ApplicationRunner {

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
        loadDataService.load();
    }

}

實(shí)現(xiàn)ApplicationRunner接口巢音,重寫run方法遵倦,在該方法中實(shí)現(xiàn)自己定制化需求。

如果項(xiàng)目中有多個(gè)類實(shí)現(xiàn)了ApplicationRunner接口官撼,他們的執(zhí)行順序要怎么指定呢梧躺?

答案是使用@Order(n)注解,n的值越小越先執(zhí)行傲绣。當(dāng)然也可以通過(guò)@Priority注解指定順序掠哥。

springboot項(xiàng)目啟動(dòng)時(shí)主要流程是這樣的:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

在SpringApplication類的callRunners方法中,我們能看到這兩個(gè)接口的具體調(diào)用:

spring中那些讓你愛不釋手的代碼技巧(續(xù)集)

最后還有一個(gè)問(wèn)題:這兩個(gè)接口有什么區(qū)別秃诵?

  • CommandLineRunner接口中run方法的參數(shù)為String數(shù)組* ApplicationRunner中run方法的參數(shù)為ApplicationArguments续搀,該參數(shù)包含了String數(shù)組參數(shù) 和 一些可選參數(shù)。

嘮嘮家常

寫著寫著又有這么多字了菠净,按照慣例禁舷,為了避免篇幅過(guò)長(zhǎng)彪杉,今天就先寫到這里。預(yù)告一下牵咙,后面會(huì)有AOP派近、BeanPostProcessor、Configuration注解等核心知識(shí)點(diǎn)的專題洁桌,每個(gè)主題的內(nèi)容都挺多的渴丸,可以期待一下喔。

最后說(shuō)一句(求關(guān)注另凌,別白嫖我)

如果這篇文章對(duì)您有所幫助谱轨,或者有所啟發(fā)的話,幫忙轉(zhuǎn)發(fā)+關(guān)注一下吠谢,您的支持是我堅(jiān)持寫作最大的動(dòng)力碟嘴。

原文:https://mp.weixin.qq.com/s/M5qBCdUQEj37PGVceC3g3A

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市囊卜,隨后出現(xiàn)的幾起案子娜扇,更是在濱河造成了極大的恐慌,老刑警劉巖栅组,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雀瓢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡玉掸,警方通過(guò)查閱死者的電腦和手機(jī)刃麸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)司浪,“玉大人泊业,你說(shuō)我怎么就攤上這事“∫祝” “怎么了吁伺?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)租谈。 經(jīng)常有香客問(wèn)我篮奄,道長(zhǎng),這世上最難降的妖魔是什么割去? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任窟却,我火速辦了婚禮,結(jié)果婚禮上呻逆,老公的妹妹穿的比我還像新娘夸赫。我一直安慰自己,他們只是感情好咖城,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布茬腿。 她就那樣靜靜地躺著胁附,像睡著了一般。 火紅的嫁衣襯著肌膚如雪滓彰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天州袒,我揣著相機(jī)與錄音揭绑,去河邊找鬼。 笑死郎哭,一個(gè)胖子當(dāng)著我的面吹牛他匪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夸研,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼邦蜜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了亥至?” 一聲冷哼從身側(cè)響起悼沈,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姐扮,沒想到半個(gè)月后絮供,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡茶敏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年壤靶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惊搏。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贮乳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恬惯,到底是詐尸還是另有隱情向拆,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布酪耳,位于F島的核電站亲铡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏葡兑。R本人自食惡果不足惜奖蔓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望讹堤。 院中可真熱鬧吆鹤,春花似錦、人聲如沸洲守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至知允,卻和暖如春撒蟀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背温鸽。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工保屯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涤垫。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓姑尺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蝠猬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子切蟋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容