1、自動(dòng)裝配
當(dāng)項(xiàng)目啟動(dòng)的時(shí)候,會(huì)去從所有的spring.factories文件中讀取@EnableAutoConfiguration鍵對(duì)應(yīng)的值,拿到配置類(lèi),然后根據(jù)一些條件判斷,決定哪些配置可以使用,哪些不能使用。自動(dòng)裝配是SPI機(jī)制的一種運(yùn)用場(chǎng)景。
在SpringBoot中,@EnableAutoConfiguration是通過(guò)@SpringBootApplication來(lái)使用的。
自動(dòng)裝配以及SPI機(jī)制見(jiàn) Spring Boot自動(dòng)配置
調(diào)用時(shí)機(jī):項(xiàng)目啟動(dòng)時(shí)加載;
-
使用場(chǎng)景:框架整合Springboot的時(shí)候,通過(guò)自動(dòng)裝配來(lái)實(shí)現(xiàn)項(xiàng)目啟動(dòng),框架就自動(dòng)啟動(dòng)的,比如Mybatis整合SpringBoot。image.png
- 自定義實(shí)現(xiàn)
//第一步,寫(xiě)個(gè)配置類(lèi):
@Configuration
public class UserAutoConfiguration {
@Bean
public UserFactoryBean userFactoryBean() {
return new UserFactoryBean();
}
}
//第二步,往spring.factories文件配置一下
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sanyou.spring.extension.springbootextension.UserAutoConfiguration
//到這就已經(jīng)實(shí)現(xiàn)了自動(dòng)裝配的擴(kuò)展。
//接下來(lái)進(jìn)行測(cè)試:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class);
User user = applicationContext.getBean(User.class);
System.out.println("獲取到的Bean為" + user);
}
}
//運(yùn)行結(jié)果:
調(diào)用 UserFactoryBean 的 getObject 方法生成 Bean:com.sanyou.spring.extension.User@3406472c
獲取到的Bean為com.sanyou.spring.extension.User@3406472c
從運(yùn)行結(jié)果可以看出,自動(dòng)裝配起了作用,并且雖然往容器中注入的Bean的class類(lèi)型為UserFactoryBean,但是最終會(huì)調(diào)用UserFactoryBean的getObject的實(shí)現(xiàn)獲取到User對(duì)象。
2、Import注解
@Import注解:導(dǎo)入的配置類(lèi)的分類(lèi),在項(xiàng)目中可能不常見(jiàn),但是下面這兩個(gè)注解肯定常見(jiàn)。
@Import({SchedulingConfiguration.class})
public @interface EnableScheduling {
}
@Import({AsyncConfigurationSelector.class})
public @interface EnableAsync {
//忽略
}
@EnableScheduling:開(kāi)啟定時(shí)任務(wù);
@EnableAsync:開(kāi)啟異步執(zhí)行;
通過(guò)這兩個(gè)注解可以看出,他們都使用了@Import注解,所以真正起作用的是@Import注解。并且在很多情況下,@EnbaleXXX這種格式的注解,都是通過(guò)@Import注解起作用的,代表開(kāi)啟了某個(gè)功能。
@Import注解導(dǎo)入的配置類(lèi)可以分為三種情況:
第一種:配置類(lèi)實(shí)現(xiàn)了 ImportSelector 接口
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
當(dāng)配置類(lèi)實(shí)現(xiàn)了 ImportSelector 接口的時(shí)候,就會(huì)調(diào)用 selectImports 方法的實(shí)現(xiàn),獲取一批類(lèi)的全限定名,并把這些類(lèi)注冊(cè)到Spring容器中。
UserImportSelector實(shí)現(xiàn)了ImportSelector,selectImports方法返回User的全限定名,并把User這個(gè)類(lèi)注冊(cè)容器中
public class UserImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("調(diào)用 UserImportSelector 的 selectImports 方法獲取一批類(lèi)限定名");
return new String[]{"com.sanyou.spring.extension.User"};
}
}
測(cè)試:
// @Import 注解導(dǎo)入 UserImportSelector
@Import(UserImportSelector.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//將 Application 注冊(cè)到容器中
applicationContext.register(Application.class);
applicationContext.refresh();
System.out.println("獲取到的Bean為" + applicationContext.getBean(User.class));
}
}
結(jié)果:
調(diào)用 UserImportSelector 的 selectImports 方法獲取一批類(lèi)限定名
獲取到的Bean為com.sanyou.spring.extension.User@282003e1
所以可以看出,的確成功往容器中注入了User這個(gè)Bean
第二種:配置類(lèi)實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 接口
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
當(dāng)配置類(lèi)實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar 接口,你就可以自定義往容器中注冊(cè)想注入的Bean。這個(gè)接口相比與 ImportSelector 接口的主要區(qū)別就是,ImportSelector接口是返回類(lèi),你不能對(duì)這些類(lèi)進(jìn)行任何操作,但是 ImportBeanDefinitionRegistrar 是可以自己注入 BeanDefinition,可以添加屬性之類(lèi)的。
來(lái)個(gè)demo:
實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口
public class UserImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
//構(gòu)建一個(gè) BeanDefinition , Bean的類(lèi)型為 User
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class)
// 設(shè)置 User 這個(gè)Bean的屬性u(píng)sername的值為 TestName
.addPropertyValue("username", "TestName")
.getBeanDefinition();
System.out.println("往Spring容器中注入U(xiǎn)ser");
//把 User 這個(gè)Bean的定義注冊(cè)到容器中
registry.registerBeanDefinition("user", beanDefinition);
}
}
測(cè)試:
// 導(dǎo)入 UserImportBeanDefinitionRegistrar
@Import(UserImportBeanDefinitionRegistrar.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//將 Application 注冊(cè)到容器中
applicationContext.register(Application.class);
applicationContext.refresh();
User user = applicationContext.getBean(User.class);
System.out.println("獲取到的Bean為" + user + ",屬性u(píng)sername值為:" + user.getUsername());
}
}
結(jié)果:
往Spring容器中注入U(xiǎn)ser
獲取到的Bean為com.sanyou.spring.extension.User@6385cb26,屬性u(píng)sername值為:TestName
第三種:普通配置類(lèi)
比如上面的@EnableScheduling注解的SchedulingConfiguration.class配置類(lèi)

其實(shí)不論是什么樣的配置類(lèi),主要的作用就是往Spring容器中注冊(cè)Bean,只不過(guò)注入的方式不同罷了。
這種方式有什么好處呢?
ImportSelector和ImportBeanDefinitionRegistrar的方法是有入?yún)⒌?,也就是注解的一些屬性的封裝,所以就可以根據(jù)注解的屬性的配置,來(lái)決定應(yīng)該往容器中注入什么樣的類(lèi)型的Bean,可以看一下 @EnableAsync 的實(shí)現(xiàn),看看是如何根據(jù)@EnableAsync注解的屬性來(lái)決定往容器中注入什么樣的Bean。
@Import的核心作用就是導(dǎo)入配置類(lèi),并且還可以根據(jù)配合(比如@EnableXXX)使用的注解的屬性來(lái)決定應(yīng)該往Spring中注入什么樣的Bean。
FeignClient接口在把FeignClientFactoryBean 注入到Spring時(shí),就是通過(guò)ImportBeanDefinitionRegistrar 來(lái)注入的。

3、ApplicationListener
準(zhǔn)確的說(shuō),這個(gè)應(yīng)該不算spring&springboot當(dāng)中的一個(gè)擴(kuò)展點(diǎn),ApplicationListener可以監(jiān)聽(tīng)某個(gè)事件的event,觸發(fā)時(shí)機(jī)可以穿插在業(yè)務(wù)方法執(zhí)行過(guò)程中,用戶可以自定義某個(gè)業(yè)務(wù)事件。但是spring內(nèi)部也有一些內(nèi)置事件,這種事件,可以穿插在啟動(dòng)調(diào)用中。我們也可以利用這個(gè)特性,來(lái)自己做一些內(nèi)置事件的監(jiān)聽(tīng)器來(lái)達(dá)到和前面一些觸發(fā)點(diǎn)大致相同的事情。
Spring內(nèi)置的事件:
ContextRefreshedEvent
ApplicationContext 被初始化或刷新時(shí),該事件被發(fā)布。也會(huì)在調(diào)用ConfigurableApplicationContext 接口中的refresh()方法時(shí)發(fā)生。此處的初始化是指:所有的Bean被成功裝載,后處理Bean被檢測(cè)并激活,所有Singleton Bean 被預(yù)實(shí)例化,ApplicationContext容器已就緒可用。ContextStartedEvent
當(dāng)使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法啟動(dòng) ApplicationContext時(shí),該事件被發(fā)布。你可以連接你的數(shù)據(jù)庫(kù),或者你可以在接受到這個(gè)事件后重啟任何停止的應(yīng)用程序。ContextStoppedEvent
當(dāng)使用 ConfigurableApplicationContext接口中的 stop()停止ApplicationContext 時(shí),發(fā)布這個(gè)事件。你可以在接受到這個(gè)事件后做必要的清理的工作ContextClosedEvent
當(dāng)使用 ConfigurableApplicationContext接口中的 close()方法關(guān)閉 ApplicationContext 時(shí),該事件被發(fā)布。一個(gè)已關(guān)閉的上下文到達(dá)生命周期末端;它不能被刷新或重啟RequestHandledEvent
這是一個(gè) web-specific 事件,告訴所有 bean HTTP 請(qǐng)求已經(jīng)被服務(wù)。只能應(yīng)用于使用DispatcherServlet的Web應(yīng)用。在使用Spring作為前端的MVC控制器時(shí),當(dāng)Spring處理用戶請(qǐng)求結(jié)束后,系統(tǒng)會(huì)自動(dòng)觸發(fā)該事件
在Spring容器啟動(dòng)的過(guò)程中,Spring會(huì)發(fā)布這些事件,如果你需要這Spring容器啟動(dòng)的某個(gè)時(shí)刻進(jìn)行什么操作,只需要監(jiān)聽(tīng)對(duì)應(yīng)的事件即可。
Spring Event 補(bǔ)充
Spring Event 可以說(shuō)是一種觀察者模式的實(shí)現(xiàn),主要是用來(lái)解耦合的。當(dāng)發(fā)生了某件事,只要發(fā)布一個(gè)事件,對(duì)這個(gè)事件的監(jiān)聽(tīng)者(觀察者)就可以對(duì)事件進(jìn)行響應(yīng)或者處理。
Spring Event 事件,就是Spring實(shí)現(xiàn)了這種事件模型,你只需要基于Spring提供的API進(jìn)行擴(kuò)展,就可以完成事件的發(fā)布訂閱。
Spring Event api
- ApplicationEvent:事件的父類(lèi),所有具體的事件都得繼承這個(gè)類(lèi),構(gòu)造方法的參數(shù)是這個(gè)事件攜帶的參數(shù),監(jiān)聽(tīng)器就可以通過(guò)這個(gè)參數(shù)來(lái)進(jìn)行一些業(yè)務(wù)操作。
- ApplicationListener:事件監(jiān)聽(tīng)的接口,泛型是子類(lèi)需要監(jiān)聽(tīng)的事件類(lèi)型,子類(lèi)需要實(shí)現(xiàn)onApplicationEvent,參數(shù)就是事件類(lèi)型,onApplicationEvent方法的實(shí)現(xiàn)就代表了對(duì)事件的處理,當(dāng)事件發(fā)生時(shí),Spring會(huì)回調(diào)onApplicationEvent方法的實(shí)現(xiàn),傳入發(fā)布的事件。
- ApplicationEventPublisher:事件發(fā)布器,通過(guò)publishEvent方法就可以發(fā)布一個(gè)事件,然后就可以觸發(fā)監(jiān)聽(tīng)這個(gè)事件的監(jiān)聽(tīng)器的回調(diào)。
ApplicationContext實(shí)現(xiàn)了ApplicationEventPublisher接口,所以通過(guò)ApplicationContext就可以發(fā)布事件
//第一步:創(chuàng)建一個(gè)火災(zāi)事件類(lèi)
//火災(zāi)事件類(lèi)繼承ApplicationEvent
// 火災(zāi)事件
public class FireEvent extends ApplicationEvent {
public FireEvent(String source) {
super(source);
}
}
//第二步:創(chuàng)建火災(zāi)事件的監(jiān)聽(tīng)器
//打119的火災(zāi)事件的監(jiān)聽(tīng)器:
public class Call119FireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) {
System.out.println("打119");
}
}
//救人的火災(zāi)事件的監(jiān)聽(tīng)器:
public class SavePersonFireEventListener implements ApplicationListener<FireEvent> {
@Override
public void onApplicationEvent(FireEvent event) {
System.out.println("救人");
}
}
//事件和對(duì)應(yīng)的監(jiān)聽(tīng)都有了,接下來(lái)進(jìn)行測(cè)試:
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//將 事件監(jiān)聽(tīng)器 注冊(cè)到容器中
applicationContext.register(Call119FireEventListener.class);
applicationContext.register(SavePersonFireEventListener.class);
applicationContext.refresh();
// 發(fā)布著火的事件,觸發(fā)監(jiān)聽(tīng)
applicationContext.publishEvent(new FireEvent("著火了"));
}
}
//運(yùn)行結(jié)果:
打119
救人
3.1 在Mybatis中的使用
Mybatis的SqlSessionFactoryBean監(jiān)聽(tīng)了ApplicationEvent,然后判斷如果是ContextRefreshedEvent就進(jìn)行相應(yīng)的處理,這個(gè)類(lèi)還實(shí)現(xiàn)了FactoryBean接口。
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
}
3.2 在SpringCloud的運(yùn)用
在SpringCloud的中,當(dāng)項(xiàng)目啟動(dòng)的時(shí)候,會(huì)自動(dòng)往注冊(cè)中心進(jìn)行注冊(cè),這個(gè)過(guò)程當(dāng)然也是基于事件來(lái)的。當(dāng)web服務(wù)器啟動(dòng)完成之后,就發(fā)布ServletWebServerInitializedEvent事件。

然后不同的注冊(cè)中心的實(shí)現(xiàn)都只需要監(jiān)聽(tīng)這個(gè)事件,就知道web服務(wù)器已經(jīng)創(chuàng)建好了,那么就可以往注冊(cè)中心注冊(cè)服務(wù)實(shí)例了。如果你的服務(wù)沒(méi)往注冊(cè)中心,看看是不是web環(huán)境,因?yàn)橹挥衱eb環(huán)境才會(huì)發(fā)這個(gè)事件。
SpringCloud提供了一個(gè)抽象類(lèi) AbstractAutoServiceRegistration,實(shí)現(xiàn)了對(duì)WebServerInitializedEvent(ServletWebServerInitializedEvent的父類(lèi))事件的監(jiān)聽(tīng)

一般不同的注冊(cè)中心都會(huì)去繼承這個(gè)類(lèi),監(jiān)聽(tīng)項(xiàng)目啟動(dòng),實(shí)現(xiàn)往注冊(cè)中心服務(wù)端進(jìn)行注冊(cè)。

Spring Event事件在Spring內(nèi)部中運(yùn)用很多,是解耦合的利器。在實(shí)際項(xiàng)目中,你既可以監(jiān)聽(tīng)SpringBoot內(nèi)置的一些事件,進(jìn)行相應(yīng)的擴(kuò)展,也可以基于這套模型在業(yè)務(wù)中自定義事件和相應(yīng)的監(jiān)聽(tīng)器,減少業(yè)務(wù)代碼的耦合。
4、PropertySourceLoader
在SpringBoot環(huán)境下,外部化的配置文件支持properties和yaml兩種格式,這兩種配置文件格式是通過(guò)PropertySourceLoader來(lái)實(shí)現(xiàn)的。
對(duì)于PropertySourceLoader的實(shí)現(xiàn),SpringBoot兩個(gè)實(shí)現(xiàn)
- PropertiesPropertySourceLoader:可以解析properties或者xml結(jié)尾的配置文件;
- YamlPropertySourceLoader:解析以yml或者yaml結(jié)尾的配置文件
public interface PropertySourceLoader {
//可以支持哪種文件格式的解析
String[] getFileExtensions();
// 解析配置文件,讀出內(nèi)容,封裝成一個(gè)PropertySource<?>結(jié)合返回回去
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}


PropertySourceLoader生效:
SpringBoot會(huì)先通過(guò)SPI機(jī)制加載所有PropertySourceLoader,然后遍歷每個(gè)PropertySourceLoader,判斷當(dāng)前遍歷的PropertySourceLoader,通過(guò)getFileExtensions獲取到當(dāng)前PropertySourceLoader能夠支持哪些配置文件格式的解析,讓后跟當(dāng)前需要解析的文件格式進(jìn)行匹配,如果能匹配上,那么就會(huì)使用當(dāng)前遍歷的PropertySourceLoader來(lái)解析配置文件。
PropertySourceLoader其實(shí)就屬于策略接口,配置文件的解析就是策略模式的運(yùn)用。


所以,如果我們要想實(shí)現(xiàn)json格式的支持,只需要自己實(shí)現(xiàn)可以用來(lái)解析json格式的配置文件的PropertySourceLoader就可以了。
第一步:自定義一個(gè)PropertySourceLoader
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
//這個(gè)方法表明這個(gè)類(lèi)支持解析以json結(jié)尾的配置文件
return new String[]{"json"};
}
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
ReadableByteChannel readableByteChannel = resource.readableChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate((int) resource.contentLength());
//將文件內(nèi)容讀到 ByteBuffer 中
readableByteChannel.read(byteBuffer);
//將讀出來(lái)的字節(jié)轉(zhuǎn)換成字符串
String content = new String(byteBuffer.array());
// 將字符串轉(zhuǎn)換成 JSONObject
JSONObject jsonObject = JSON.parseObject(content);
Map<String, Object> map = new HashMap<>(jsonObject.size());
//將 json 的鍵值對(duì)讀出來(lái),放入到 map 中
for (String key : jsonObject.keySet()) {
map.put(key, jsonObject.getString(key));
}
return Collections.singletonList(new MapPropertySource("jsonPropertySource", map));
}
}
第二步:配置PropertySourceLoader
在spring.factories文件中配置一下就行了。
org.springframework.boot.env.PropertySourceLoader=\
com.xxx.JsonPropertySourceLoader
demo
//先創(chuàng)建一個(gè)application.json的配置文件
{
"sanxay.username":"三友的java日記”
}
//改造User
public class User {
// 注入配置文件的屬性
@Value("${sanyou.username:}")
private String username;
}
//啟動(dòng)項(xiàng)目
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class);
User user = applicationContext.getBean(User.class);
System.out.println("獲取到的Bean為" + user + ",屬性u(píng)sername值為:" + user.getUsername());
}
@Bean
public User user() {
return new User();
}
}
// 運(yùn)行結(jié)果:
獲取到的Bean為com.sanyou.spring.extension.User@481ba2cf,屬性u(píng)sername值為:三友的java日記
成功將json配置文件的屬性注入到User對(duì)象中。
Nacos對(duì)于PropertySourceLoader的實(shí)現(xiàn)
Nacos作為配置中心,不僅支持properties和yaml格式的文件,還支持json格式的配置文件,由于 SpringBoot已經(jīng)支持了properties和yaml格式的文件的解析,那么Nacos只需要實(shí)現(xiàn)SpringBoot不支持的json就可以了。

5、EnvironmentPostProcessor
EnvironmentPostProcessor在SpringBoot啟動(dòng)過(guò)程中,也會(huì)調(diào)用,也是通過(guò)SPI機(jī)制來(lái)加載擴(kuò)展的。

EnvironmentPostProcessor是用來(lái)處理ConfigurableEnvironment的,也就是一些配置信息,SpringBoot所有的配置都是存在這個(gè)對(duì)象的。
說(shuō)這個(gè)類(lèi)的主要原因,主要不是說(shuō)擴(kuò)展,而是他的一個(gè)實(shí)現(xiàn)類(lèi)很關(guān)鍵。

這個(gè)類(lèi)的作用就是用來(lái)處理外部化配置文件的,也就是這個(gè)類(lèi)是用來(lái)處理配置文件的,通過(guò)前面提到的PropertySourceLoader解析配置文件,放到ConfigurableEnvironment里面。
可以通過(guò)在EnvironmentPostProcessor的postProcessEnvironment上打斷點(diǎn)來(lái)查看配置文件的加載過(guò)程。尤其是在配置文件修改后,不生效的時(shí)候。
