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

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

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

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

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

Spring Event事件在Spring內(nèi)部中運(yùn)用很多,是解耦合的利器。在實(shí)際項(xiàng)目中,你既可以監(jiān)聽SpringBoot內(nèi)置的一些事件,進(jìn)行相應(yīng)的擴(kuò)展,也可以基于這套模型在業(yè)務(wù)中自定義事件和相應(yīng)的監(jiān)聽器,減少業(yè)務(wù)代碼的耦合。
4、PropertySourceLoader
在SpringBoot環(huán)境下,外部化的配置文件支持properties和yaml兩種格式,這兩種配置文件格式是通過PropertySourceLoader來實(shí)現(xiàn)的。
對于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ì)先通過SPI機(jī)制加載所有PropertySourceLoader,然后遍歷每個(gè)PropertySourceLoader,判斷當(dāng)前遍歷的PropertySourceLoader,通過getFileExtensions獲取到當(dāng)前PropertySourceLoader能夠支持哪些配置文件格式的解析,讓后跟當(dāng)前需要解析的文件格式進(jìn)行匹配,如果能匹配上,那么就會(huì)使用當(dāng)前遍歷的PropertySourceLoader來解析配置文件。
PropertySourceLoader其實(shí)就屬于策略接口,配置文件的解析就是策略模式的運(yùn)用。


所以,如果我們要想實(shí)現(xiàn)json格式的支持,只需要自己實(shí)現(xiàn)可以用來解析json格式的配置文件的PropertySourceLoader就可以了。
第一步:自定義一個(gè)PropertySourceLoader
public class JsonPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
//這個(gè)方法表明這個(gè)類支持解析以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);
//將讀出來的字節(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 的鍵值對讀出來,放入到 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 + ",屬性username值為:" + user.getUsername());
}
@Bean
public User user() {
return new User();
}
}
// 運(yùn)行結(jié)果:
獲取到的Bean為com.sanyou.spring.extension.User@481ba2cf,屬性username值為:三友的java日記
成功將json配置文件的屬性注入到User對象中。
Nacos對于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)過程中,也會(huì)調(diào)用,也是通過SPI機(jī)制來加載擴(kuò)展的。

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

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