前言
上一篇文章《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)題:
- 某個(gè)功能需要根據(jù)項(xiàng)目中有沒有某個(gè)jar判斷是否開啟該功能。
- 某個(gè)bean的實(shí)例化需要先判斷另一個(gè)bean有沒有實(shí)例化树姨,再判斷是否實(shí)例化自己摩桶。
- 某個(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家族。
自定義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方法中:
這個(gè)方法邏輯不復(fù)雜:
- 先判斷有沒有使用Conditional注解,如果沒有直接返回false
- 收集condition到集合中
- 按order排序該集合
- 遍歷該集合盟广,循環(huán)調(diào)用condition的matchs方法闷串。
二. 如何妙用@Import?
有時(shí)我們需要在某個(gè)配置類中引入另外一些類筋量,被引入的類也加到spring容器中烹吵。這時(shí)可以使用@Import注解完成這個(gè)功能碉熄。
如果你看過(guò)它的源碼會(huì)發(fā)現(xiàn),引入的類支持三種不同類型肋拔。
但是我認(rèn)為最好將普通類和@Configuration注解的配置類分開講解锈津,所以列了四種不同類型:
普通類
這種引入方式是最簡(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é)如下:
- 普通類,用于創(chuàng)建沒有特殊要求的bean實(shí)例初坠。
- @Configuration注解的配置類和簸,用于層層嵌套引入的場(chǎng)景。
- 實(shí)現(xiàn)ImportSelector接口的類碟刺,用于一次性引入多個(gè)類的場(chǎng)景比搭,或者可以根據(jù)不同的配置決定引入不同類的場(chǎng)景。
- 實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口的類,主要用于可以手動(dòng)控制BeanDefinition的創(chuàng)建和注冊(cè)的場(chǎng)景身诺,它的方法中可以獲取BeanDefinitionRegistry注冊(cè)容器對(duì)象蜜托。
在ConfigurationClassParser類的processImports方法中可以看到這三種方式的處理邏輯:
最后的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ì)失效顷牌,主要原因如下:
除了上述列舉的問(wèn)題之外剪芍,由于@Transactional注解最小粒度是要被定義在方法上,如果有多層的事務(wù)方法調(diào)用窟蓝,可能會(huì)造成大事務(wù)問(wèn)題罪裹。
所以,建議在實(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的解決方案翘瓮,目前有三種:
一.使用@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方法:
最終三種方案殊途同歸帖鸦,都會(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)題:
- 如果依賴包較多院仿,找起來(lái)很麻煩,容易找錯(cuò)掰吕,而且要花很多時(shí)間为肮。
- 各依賴包之間可能會(huì)存在版本兼容性問(wèn)題初烘,項(xiàng)目引入這些jar包后口注,可能沒法正常啟動(dòng)变擒。
- 如果有些參數(shù)沒有配好君珠,啟動(dòng)服務(wù)也會(huì)報(bào)錯(cuò)寝志,沒有默認(rèn)配置。
「為了解決這些問(wèn)題,springboot的starter機(jī)制應(yīng)運(yùn)而生」材部。
starter機(jī)制帶來(lái)這些好處:
- 它能啟動(dòng)相應(yīng)的默認(rèn)配置毫缆。
- 它能夠管理所需依賴,擺脫了需要到處找依賴 和 兼容性問(wèn)題的困擾乐导。
- 自動(dòng)發(fā)現(xiàn)機(jī)制苦丁,將spring.factories文件中配置的類,自動(dòng)注入到spring容器中物臂。
- 遵循“約定大于配置”的理念旺拉。
在業(yè)務(wù)工程中只需引入starter包,就能使用它的功能棵磷,太爽了蛾狗。
下面用一張圖,總結(jié)starter的幾個(gè)要素:
接下來(lái)我們一起實(shí)戰(zhàn)仪媒,定義一個(gè)自己的starter沉桌。
第一步,創(chuàng)建id-generate-starter工程:
其中的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工程:
該項(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í)主要流程是這樣的:
在SpringApplication類的callRunners方法中,我們能看到這兩個(gè)接口的具體調(diào)用:
最后還有一個(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)力碟嘴。