6 spring
spring
1、spring容器
1、BeanFactoryPostProcessor 和BeanPostProcessor區別
1、factorypostprocessor是針對整個bean工廠的,對所有的beandefine進行修改,當然也可以不修改,其實修改的就是我們在xml中定義的各種標簽值,例如將單例變成多例。
主要實現是通過實現BeanFactoryPostProcessor接口,之后將這個實現類也要加入到我們的xml中,這樣因為它實現了這個接口,所以他會先被實例化(因為要用嘛)。在bean的生命周期中他是最先別調用的;
這個接口的方法參數ConfigurableListableBeanFactory可以獲取的beandefine,從而對所有bean進行修改。
在加載bean定義的時候,spring會自動檢測實現了這個接口的bean,并將其在實例化其他bean之前將他實例化。在執行refresh方法時調用實現,實現增強。
在每個bean被實例化之前被調用
/*允許自定義修改應用程序上下文的 bean 定義,調整上下文底層 bean 工廠的 bean 屬性值。 應用程序上下文可以在其 bean 定義中自動檢測 BeanFactoryPostProcessor bean,并在創建任何其他 bean 之前應用它們。 對于針對覆蓋應用程序上下文中配置的 bean 屬性的系統管理員的自定義配置文件很有用。 有關滿足此類配置需求的開箱即用解決方案,請參閱 PropertyResourceConfigurer 及其具體實現。 BeanFactoryPostProcessor 可以與 bean 定義交互和修改,但不能與 bean 實例交互。這樣做可能會導致過早的 bean 實例化,違反容器并導致意外的副作用。 如果需要 bean 實例交互,請考慮實現 BeanPostProcessor。 */ @FunctionalInterface public interface BeanFactoryPostProcessor { //修改工廠配置。因為它給的參數是工廠嘛void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; } //通過名字來獲取定義,從而修改定義 BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;//具體使用 public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService");beanDefinition.setScope("prototype");} }2、而bean的處理器是應用于隨后創建的任何 bean進行處理的。spring同樣也會在獲取beandefine時識別出他來。
在每個bean實例化之后被調用。
/* 允許自定義修改新 bean 實例的工廠鉤子,例如檢查標記接口或用代理包裝它們。 ApplicationContexts 可以在它們的 bean 定義中自動檢測 BeanPostProcessor bean,并將它們應用于隨后創建的任何 bean。 普通 bean 工廠允許后處理器的編程注冊,適用于通過該工廠創建的所有 bean。 */ public interface BeanPostProcessor {@Nullabledefault Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}@Nullabledefault Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;} }兩者主要的不同在于調用時間不同。
2、bean初始化流程
https://javadoop.com/post/spring-ioc#toc_7
主要類為AbstractAutowireCapableBeanFactory的doCreateBean方法,起點是AbstractApplicationContext的refresh方法中的finishBeanFactoryInitialization(beanFactory);,之后一直往下獲取單例bean就是這個方法。詳情可以看上面的網址。
org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 主要的三個方法調用就在這里
真正做事的都是do什么的方法。抽象類中一般是模板,留給子類實現一些特殊的方法。
執行到initializeBean的方法棧
3、什么是spring?
1、spring是一個開源的輕量級的java開發框架,提高了開發人員的開發效率和系統的可維護性。核心主要是IOC控制反轉和AOP面向切面編程。spring開箱即用,具有很高的拓展性,有活躍的社區。
4、spring的優勢
1、使用spring提供的ioc容器,我們將對象之間的依賴關系交由spring進行管理,不需要在使用new來創建對象,降低了耦合。
2、通過spring提供的aop功能,我們可以將應用業務和系統服務分開,實現解耦。
3、生態很好,能夠輕松整合其他一些優秀的框架。
4、社區活躍,有問題可以及時解決。
5、能夠開箱即用,能夠實現快速開發。
5、spring體系結構
主要分為四部分:
1、web:web模塊
2、Core Container:這是spring的核心模塊,spring的其他模塊基本都依賴于該模塊,提供IOC和依賴注入功能的支持。
3、Data Access/Integration數據訪問/集成:提供了對數據的訪問支持以及對消息隊列的支持。
4、測試以及aop
6、談談你對springIOC的理解
IOC是控制反轉的意思,這是一種設計思想,而不是一種具體實現,他的思想是將原本在程序中手動創建對象的控制權交由spring框架來管理。springioc容器是用來實現ioc的載體。IOC最常見的實現方式是依賴注入DI。
將對象之間的依賴關系交給IOC容器進行管理,由IOC容器來完成對象的注入。我們無需知道對象是如何被創建出來的,只要通過一些簡單的配置,將我們的需求告訴IOC容器即可。
7、IOC配置的三種方式
1、XML配置
主要是聲明命名空間和bean。
優點:可用于任何場景,結構清晰,通俗易懂
缺點:配置繁瑣,不易維護,枯燥乏味,拓展性差
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- services --><bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl"><property name="userDao" ref="userDao"/><!-- additional collaborators and configuration for this bean go here --></bean><!-- more bean definitions for services go here --> </beans>2、java配置類
這個和xml類似,只是將xml的格式改成java的格式了,同樣適用于任何場景,但是如果配置過多則可讀性較差。
使用方法:創建一個配置類,在類上添加@Configration注解,在每個方法上添加@Bean注解。@Bean的屬性可以指定bena的名字。
@Bean的屬性:
- value:bean別名和name是相互依賴關聯的,value,name如果都使用的話值必須要一致;
- name:bean名稱,如果不寫會默認為注解的方法名稱;
- autowire:自定裝配默認是不開啟的,建議盡量不要開啟,因為自動裝配不能裝配基本數據類型、字符串、數組等,這是自動裝配設計的局限性,并且自動裝配不如依賴注入精確;
- initMethod:bean的初始化之前的執行方法,該參數一般不怎么用,因為完全可以在代碼中實現;
- destroyMethod:默認使用javaConfig配置的bean,如果存在close或者shutdown方法,則在bean銷毀時會自動執行該方法,如果你不想執行該方法,則添加@Bean(destroyMethod=“”)來防止出發銷毀方法;
@Profile 注解
@Profile的作用是把一些meta-data進行分類,分成Active和InActive這兩種狀態,然后你可以選擇在active 和在Inactive這兩種狀態下配置bean,在Inactive狀態通常的注解有一個!操作符,通常寫為:@Profile(“!p”),這里的p是Profile的名字。
@Scope 注解
在Spring中對于bean的默認處理都是單例的,我們通過上下文容器.getBean方法拿到bean容器,并對其進行實例化,這個實例化的過程其實只進行一次,即多次getBean 獲取的對象都是同一個對象,也就相當于這個bean的實例在IOC容器中是public的,對于所有的bean請求來講都可以共享此bean。
@Lazy 注解
表明一個bean 是否延遲加載,可以作用在方法上,表示這個方法被延遲加載;可以作用在@Component (或者由@Component 作為原注解) 注釋的類上,表明這個類中所有的bean 都被延遲加載。
@Primary 注解
指示當多個候選者有資格自動裝配依賴項時,應優先考慮bean。此注解在語義上就等同于在Spring XML中定義的bean 元素的primary屬性。
注意:除非使用component-scanning進行組件掃描,否則在類級別上使用@Primary不會有作用。如果@Primary 注解定義在XML中,那么@Primary 的注解元注解就會忽略,相反的話就可以優先使用。
3、注解配置
比較常用的配置方式,開發便捷,通俗易懂,方便維護,但是有些第三方類不支持,只能使用xml或者java來。
@Component,@Controller,@Service,@Repository
4、繼承FactoryBean接口
這個接口會返回我們的代理類對象。但是首先得將實現了這個接口的類定義加入spring容器。之后spring初始化的時候就會自動調用FactoryBean的getObject方法返回代理對象。這個實現了接口的類就是代理工廠。
8、依賴注入的三種方式
1、setter注入
使用bean的set方法進行注入,在注解和java配置類中就是將@AutoWired加在setter方法上
2、構造方法注入
xml就是一個標簽
注解和java配置類就是在bean的構造方法上面添加@AutoWired注解
3、自動注入
主要是引用不用寫了,spring會根據字段的類型、beanname、constructor自動在容器中尋找合適的類注入
https://www.w3cschool.cn/wkspring/kp5i1ico.html
在要裝配的類上添加autowire屬性,值可以為
<!-- Definition for textEditor bean --><bean id="textEditor" class="com.tutorialspoint.TextEditor" autowire="byType"><property name="name" value="Generic Text Editor" /></bean>9、Resource和Autowired的區別
- 兩者都是注入bean的方式,來源不同,Resource來自jdk,而Autowired來自spring
- 默認查找不同,Resource默認查找Byname(就是和屬性名一樣的bean),而Autowired則是默認查找Bytype的(就是在容器中查找屬性對應的類的bean),如果找不到會報錯,如果有兩個相同的實現類會按照byname來找,找不到報錯。而Resource可以指定name和type屬性進行指定,兩者可以同時指定,但是找不回報錯。
- Autowired可搭配Qualifier注解來顯示指定byname的值
10、bean的作用域
有五種
1、singleton:唯一bean,spring中bean默認都是單例的
2、prototype:每次請求都會創建一個新的bean實例。
3、request:每次HTTP請求都會產生一個新的bean,只在當前HTTP Request中有效
4、session:在web應用中,為每個會話創建一個bean
5、globalsession:每個全局的HTTP Session都會創建一個。spring5好像沒有了。
11、spring中bean線程安全嗎?如何解決?
在多線程環境下,只對bean的成員變量進行查詢,不會修改成員變量的值,這樣的bean叫做無狀態bean,所以無狀態bean是線程安全的。而多線程環境下需要對bean的成員變量進行修改bean稱為有狀態bean,有狀態bean是存在線程安全問題的。
那么如何解決呢?
我們可以更改bean的作用域為prototype,這樣每個需要bean的時候都會創建一個新的,這樣就不存在線程安全問題
2、使用ThreadLocal來存儲成員變量,這樣每個成員變量在每個線程中都會保存一個副本。
12、bean的生命周期
首先容器會根據我們指定的xml文件路徑去讀取xml文件,將里面的bean提取出來稱為beandefine。
bean的生命周期主要分為5個階段:準備處理器,創建實例,屬性填充,容器緩存,銷毀。
1、創建準備階段:在這個階段spring會先去實例化beanfactory后處理器(只執行一次,用來修改bean定義,作用于工廠),和beanpost后處理器(每個bean都會執行一次),在創建bean的時候使用
2、執行beanfactory后處理器修改bean定義
3、執行InstantiationAwareBeanPostProcessor的方法
4、執行bean的構造器
5、執行InstantiationAwareBeanPostProcessor的后方法
6、進行屬性填充
7、執行Aware接口的方法
8、執行普通的bean后處理器前置方法
9、查看bean是否實現initBean接口,如果實現了執行其方法后再執行bean定義中的init方法
10、后處理器后置方法
11、高級bean后處理器創建成功方法
12、正常調用
13、銷毀bean
13、springIOC容器初始化refresh
創建容器是線程安全的,因為在創建的時候加了鎖,先創建bean工廠,讀取配置文件加載bean定義,之后手動注冊幾個必要的bean前置處理器,之后實例化BeanFactory后處理器,調用里面的方法。之后實例化beanpostProcessor,這個在bean填充完屬性之后執行,特殊的Instantia那個除外。
之后就是一些附加功能,例如國際化,廣播器,監聽器,最后就是實例化那些單例bean,發送容器初始化完成事件。
obtainFreshBeanFactory();方法會加載beandefine和實例化解析各種xml標簽的Handler,因為你要讀取xml文件就必須要有相應的解析器來解析標簽。
標簽會被解析為一個beandefine
public void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");// 設置spring狀態為“已啟動”,設置容器啟動時間,處理配置文件占位符prepareRefresh();// 獲取ConfigurableListableBeanFactory工廠,這一步同時還讀取完了beandefine,并且注冊到了BeanFactory中ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 設置幾個必要的beanpostprocesser,和手動注冊幾個beanprepareBeanFactory(beanFactory);try {// 這一步給具體context實現,在標準初始化之后修改應用程序上下文的內部 bean 工廠。所有 bean 定義都將被加載,但還沒有 bean 被實例化。這允許在某些 ApplicationContext 實現中注冊特殊的 BeanPostProcessors 等。// 主要是用來添加beanpostprocessor,用來增強bean的postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");// 調用BeanFactoryPostProcessors的方法,spring會自動在加載beandefine時識別他并且在此函數內被實例化invokeBeanFactoryPostProcessors(beanFactory);// 同樣也會去掃描實現了rBeanPostProcessors接口的beandefine,在這一步實例化registerBeanPostProcessors(beanFactory);beanPostProcess.end();// 初始化當前 ApplicationContext 的 MessageSource,國際化這里就不展開說了,不然沒完沒了了initMessageSource();// 初始化當前 ApplicationContext 的事件廣播器,initApplicationEventMulticaster();// 從方法名就可以知道,典型的模板方法(鉤子方法),// 具體的子類可以在這里初始化一些特殊的 Bean(在初始化 singleton beans 之前)onRefresh();// 注冊事件監聽器,監聽器需要實現 ApplicationListener 接口。registerListeners();// 實例化所有未被lazy-init的beanfinishBeanFactoryInitialization(beanFactory);// 發送初始化完成事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();contextRefresh.end();}} }14、循環依賴問題
回答:
先放入三級緩存(存的是a對象和一個lambda表達,鍵值對),之后放入二級緩存(在C取所有緩存中找的時候會去執行三級緩存中A的lambda表達式,返回一個A對象,并將A對象放入二級緩存),最后再放入一級緩存(C完成初始化操作)。
初始化的時候,發現自己依賴對象 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建
出來;
個時候由于 A 已經添加至緩存中(一般都是添加至三級緩存 singletonFactories),通過
ObjectFactory 提前曝光,所以可以通過 ObjectFactory#getObject() 方法來拿到 A 對象,并將A放入二級緩存中。C 拿 到 A 對象后順利完成初始化,然后將自己添加到一級緩存中;
就已經完成了初始化過程了。
如果是構造器注入的話(假如有A、B類,A先B后),A第一次先把自己放入singletonsCurrentlyInCreation中,然后在createBeanInstance時會去調用@AutoWired標注的有參構造器(此時A沒有實例化,連對象都沒創建),然后會去getBean(B),這就回到了上方流程的開頭,B在第一個getSingleton沒有獲取到A,然后就去getBean(A),對于A來說已經是第二次了,于是在向singletonsCurrentlyInCreation添加的時候就會報錯,因為該集合已經有了A,因此異常在此處拋出。
如果是set注入,A在createBeanInstance時則會調用無參構造方法,在populateBean(此時A已經放入三級緩存了)時調用getBean(B),而B再去getBean(A)的時候(無論B是在@AutoWired標注的有參構造器還是無參構造去獲取A),直接就能從三級緩存中得到,解決循環依賴。
構造器循環依賴不一定不能解決
根據上文最后一句話可知,A先B后,A用set注入,B是構造器注入,這樣的循環依賴也是可以被解決的
https://blog.csdn.net/m0_43448868/article/details/113578628
代理模式就是持有一個原先的對象,我們實現同一個方法名,調用時同時執行增強邏輯和原方法,這里原對象是獨立的,代理對象成員變量指向原對象,這樣我們無論先實例化原對象或者不初始化他都是一樣的,他最后都還是會被初始化。
在我們解決循環依賴的時候,先創建A,A依賴B,這時A(未進行依賴注入)先放在三級緩存中,創建B,這時B又依賴于A,B會去查找一級緩存,發現沒有,在查找二級緩存,也沒有,再找三級緩存這時候有了
(這時會調用getEarlyBeanReference方法,該方法會去執行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,而這個接口下的實現類中只有兩個類實現了這個方法,一個是AbstractAutoProxyCreator,一個是InstantiationAwareBeanPostProcessorAdapter,在整個Spring中,默認就只有AbstractAutoProxyCreator真正意義上實現了getEarlyBeanReference方法,而該類就是用來進行AOP的。最終會調用AbstractAutoProxyCreator#wrapIfNecessary方法進行增強)
就將A的增強對象放進B中,此時A增強對象中的A是一個引用地址,還未被初始化。此時B完成初始化,加入一級緩存。
接著A從一級緩存中獲取到B,完成依賴注入。此時的A是完整版,它指向的地址也是完整版,意味著B中的A是完整版。
在docreateBean執行完bean 的構造方法之后會判斷是否需要提前暴露早期bean
// Eagerly cache singletons to be able to resolve circular references 在填充屬性之前// even when triggered by lifecycle interfaces like BeanFactoryAware.判斷是否需要早期暴露bean未填充boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}//將beanname和ObjectFactory加入三級緩存中,將未填充屬性的bean移除二級緩存,getEarlyBeanReference是AOP的關鍵,這個執行這個方法會返回當前對象增強對象,這個代理對象里面的原始對象可能是未依賴注入的。addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}//org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactoryprotected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}}下面的場景是B去中A的依賴。已經走完了A找B依賴,先找是在執行B的屬性填充,因為在上面那里將A的早期對象放入三級緩存中,所以,這里能在三級緩存中找到A,至之后再將A的初級對象放入二級緩存。這時B已經拿到了A的增強后的對象(但是代理對象中A是沒有被依賴注入的),之后對A進行依賴注入即可。
循環為:A早期對象放入三級緩存,A去找B發現沒有則新建,B依賴注入的時候去找A,經過三重查找在三級緩存中找到A的增強對象,并將A的增強對象放入二級緩存。
//這個是springgetbean的底層,沒有就新建,有就直接拿,先找一級緩存,再找二級緩存,最后找三級緩存 @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock//先找一級緩存Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {//找二級緩存singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {//加個鎖找三級緩存。ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//執行三級緩存中的工廠方法,返回一個只執行構造方法的beansingletonObject = singletonFactory.getObject();//存入二級緩存this.earlySingletonObjects.put(beanName, singletonObject);//將三級緩存移除,這一步已經將A增強對象移除三級緩存this.singletonFactories.remove(beanName);}}}}}}return singletonObject; } //DefaultSingletonBeanRegistry,將完整對象放入單例池中。protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}為什么要有第三級緩存?
因為這里存放的AOP邏輯,用來給那些有增強邏輯的Bean。二級緩存用來存放那些不需要增強
15、Aware接口含義
https://blog.csdn.net/yi524529990/article/details/119888367 這個比較好
https://blog.csdn.net/agonie201218/article/details/108489141
其實就是用來獲取spring容器中的一些數據,只有實現這個接口,就能夠獲取spring內部的一些數據,否則是獲取不到的。
比如說,我想要我的bean(就是自定義的類,類似于user之類的)獲取到spring容器的一些底層bean,這些bean不能通過自動注入獲取,之能夠通過實現接口,通過方法上的參數進行獲取。這就是Aware感知,讓我能夠感知到spring底層。
其實這些接口就是spring底層暴露出來的接口,讓我能夠使用一些數據罷了。
/*** 自定義一個實現類,一定要注入到容器中,這樣我們就能拿到application了。*/ @Component public class ApplicationContextAwareImpl implements ApplicationContextAware {/*** 容器啟動的時候會調用這個方法,只需要將applicationContext設置即可* @param applicationContext 容器啟動會自動注入* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {//將其設置到ApplicationContextUtilApplicationContextUtil.setApplicationContext(applicationContext);} }16、注解方式生成beandefine
在我們寫代碼的時候,一般都是直接給要加入容器的bean加上相應的注解,這樣他就能加入spring容器了。這個的實現邏輯是springboot的啟動類上有包掃描注解,能夠將添加了注解的類生成Beandefine。使用一個解析器來解析類,有點像反射一樣,將class的屬性拿到來分析。
這樣我們就能夠將他加入beandefine中了。
2、Spring AOP
1、aop理解
AOP:面向切面編程,將那些與業務無關的,卻為業務模塊所共同調用的邏輯如日志管理,權限管理等封裝起來,減少系統的重復代碼,降低模塊間的耦合度,并有利于未來的拓展和可維護性。
springAOP的實現是基于動態代理的,如果目標對象實現了某個接口,則spring會使用jdk的動態代理來實現增強,否則使用cglib來實現。
2、相關概念
(1)切面(Aspect):被抽取的公共模塊,可能會橫切多個對象。 在Spring AOP中,切面可以使用通用類(基于模式的風格) 或者在普通類中以 @AspectJ 注解來實現。切點和通知的集合
(2)連接點(Join point):指方法,在Spring AOP中,一個連接點 總是 代表一個方法的執行。 在哪個地方執行增強方法,攔截地點
(3)通知(Advice):在切面的某個特定的連接點(Join point)上執行的動作。通知有各種類型,其中包括“around”、“before”和“after”等通知。許多AOP框架,包括Spring,都是以攔截器做通知模型, 并維護一個以連接點為中心的攔截器鏈。增強邏輯
(4)切入點(Pointcut):切入點是指 我們要對哪些Join point進行攔截的定義。通過切入點表達式,指定攔截的方法,比如指定攔截add*、search*。連接點的集合
(5)引入(Introduction):(也被稱為內部類型聲明(inter-type declaration))。聲明額外的方法或者某個類型的字段。Spring允許引入新的接口(以及一個對應的實現)到任何被代理的對象。例如,你可以使用一個引入來使bean實現 IsModified 接口,以便簡化緩存機制。
(6)目標對象(Target Object): 被一個或者多個切面(aspect)所通知(advise)的對象。也有人把它叫做 被通知(adviced) 對象。 既然Spring AOP是通過運行時代理實現的,這個對象永遠是一個 被代理(proxied) 對象。被代理對象
(7)織入(Weaving):指把增強應用到目標對象來創建新的代理對象的過程。Spring是在運行時完成織入。將增強邏輯通知和連接點融合在一起生成代理對象的過程
切入點(pointcut)和連接點(join point)匹配的概念是AOP的關鍵,這使得AOP不同于其它僅僅提供攔截功能的舊技術。 切入點使得定位通知(advice)可獨立于OO層次。 例如,一個提供聲明式事務管理的around通知可以被應用到一組橫跨多個對象中的方法上(例如服務層的所有業務操作)。
3、5種通知類型及其執行順序
1)前置通知(Before advice):在某連接點(join point)之前執行的通知,但這個通知不能阻止連接點前的執行(除非它拋出一個異常)。
(2)返回后通知(After returning advice):在某連接點(join point)正常完成后執行的通知:例如,一個方法沒有拋出任何異常,正常返回。
(3)拋出異常后通知(After throwing advice):在方法拋出異常退出時執行的通知。
(4)后通知(After (finally) advice):當某連接點退出的時候執行的通知(不論是正常返回還是異常退出)。
(5)環繞通知(Around Advice):包圍一個連接點(join point)的通知,如方法調用。這是最強大的一種通知類型。 環繞通知可以在方法調用前后完成自定義的行為。它也會選擇是否繼續執行連接點或直接返回它們自己的返回值或拋出異常來結束執行。 環繞通知是最常用的一種通知類型。大部分基于攔截的AOP框架,例如Nanning和JBoss4,都只提供環繞通知。
同一個aspect,不同advice的執行順序:
①沒有異常情況下的執行順序:
around before advice 1
before advice 2
target method 執行
around after advice 1
after advice 2
afterReturning
②有異常情況下的執行順序:
around before advice 1
before advice 2
target method 執行
around after advice 1
after advice 2
afterThrowing:異常發生
java.lang.RuntimeException: 異常發生
4、實現原理
https://www.cnblogs.com/yuanbeier/archive/2022/04/17/16155353.html 這個寫完了創建流程,每次創建Bean都會在創建Bean的生命周期的 initializeBean 方法中,會執行 AnnotationAwareAspectJAutoProxyCreator的 postProcessAfterInitialization方法。該方法會拿緩存BeanFactoryAspectJAdvisorsBuilder.advisorsCache 中所有advisor的pointCut去匹配正在創建的實例Bean的所有方法。如果 advisor 和 Bean 的某一個方法能匹配上,則把該advisor添加到 advisor的候選集合中。直到找出匹配Bean的所有Adsivors。最后根據Adsivor的候選集合和Bean類型創建動態代理對象ProxyFactory。
回答:AOP的底層是通過AbstractAutoProxyCreator來創建的,AbstractAutoProxyCreator實現了BeanPostProcessor接口,這里他會去調用postProcessAfterInitialization方法,這里面又有wrapIfnecessary方法,這個方法的作用是獲取增強方法和根據獲取的增強器進行代理。
獲取增強器是會去找bean工廠中被AspectJ注解標記的類,根據類里面的注解和切點生成增強器Advisor。之后根據當前創建bean的信息,來獲取適合當前bean的增強器。有了增強器之后,就能生成代理了。
代理類的創建是使用ProxyFactory來創建的。將增強器advisor和目標類加入工廠中,之后根據目標類是否實現了接口來判斷使用jdk的動態代理還是使用cglib的動態代理來創建代理類。
1、AbstractAutoProxyCreater結構圖
2、在容器初始化的時候就會自動創建AbstractAutoProxyCreater,這個頂層的抽象的創建代理的beanPostProcessor,他間接實現了beanPostProcessor,他實現的beanPostProcessor后置方法postProcessAfterInitialization中有創建代理對象的方法wrapIfNecessary,這個方法主要是獲取攔截器(也就是Advice 是通知,Advisor 是增強器。增強器是通知加切點),使用攔截器來創建bean的代理。
當我們出現循環依賴的時候,第二個bean需要第一個時,會去第三級緩存中獲取到objectFactory來執行他的創建代理方法。之后將代理對象放在二級緩存中,等待其完成依賴注入之后放入一級緩存。
新建一個bean的時候,如果為出現循環依賴,會在執行執行完依賴注入后執行這個創建代理方法。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.//這里獲取攔截器Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);//創建代理對象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean; } //獲取攔截器advisorprotected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {//獲取advisor,就是去解析切面類和xml中的切面配置,并將其封裝為advisor,一個advisor包含一個通知和切點。List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();}//創建代理protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (proxyFactory.isProxyTargetClass()) {// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {// No proxyTargetClass flag enforced, let's apply our default checks...if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);}5、advice與MethodInterceptor聯系
先將各種切面類或者xml文件獲取到的通知和切點組合成各種advisor,之后在將這些advisor變成環繞通知,統一進行調用增強,這樣減少了底層調用的判斷邏輯
MethodBeforeAdviceInterceptor是環繞通知
advisor中的advice一般不是環繞通知,我們使用適配器將他轉換為環繞通知后統一進行調用。
底層適配器MethodBeforeAdviceAdapter,將低級切面中的通知轉換為同一的環繞通知,MethodBeforeAdviceAdapter 將 @Before AspectJMethodBeforeAdvice 適配為 MethodBeforeAdviceInterceptor
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {@Overridepublic boolean supportsAdvice(Advice advice) {return (advice instanceof MethodBeforeAdvice);}@Overridepublic MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}}統一轉換為環繞通知, 體現的是設計模式中的適配器模式
- 對外是為了方便使用要區分 before、afterReturning
- 對內統一都是環繞通知, 統一用 MethodInterceptor 表示
代理方法執行時會做如下工作
- MethodBeforeAdviceAdapter 將 @Before AspectJMethodBeforeAdvice 適配為 MethodBeforeAdviceInterceptor
- AfterReturningAdviceAdapter 將 @AfterReturning AspectJAfterReturningAdvice 適配為 AfterReturningAdviceInterceptor
- 這體現的是適配器設計模式
高級切面@Before->Advice->MethodInvocation
6、Spring AOP 和 AspectJ AOP 有什么區別?
springAOP是運行時增強,而AspectJAOP是編譯時增強。在spring中已經集成了AspectJ,springaop和他相輔相成。
springAOP是基于動態代理的,而AspectJAOP是基于字節碼的
7、Advice和Advisor區別
簡單來說:Advice 是通知,Advisor 是增強器。(說了跟沒說一樣…)
使用 spring aop 要定義切面,切面里面有 通知 和 切點。
在項目啟動的過程中,項目中的所有切面會被 AnnotationAwareAspectJAutoProxyCreator 解析,它會找到切面中的每一個通知以及通知對應的切點,拿這二者構建一個新的對象,這個對象就是 Advisor()。最后將所有解析得到的增強器注入到容器中。
8、springaop執行(重要
在bean初始化的時候,會先自動注冊并實例化一個類AnnotationAwareAspectJAutoProxyCreator類,這個類實現了BeanPostProcessor接口,所以會在initialBean方法時,其他bean會去執行這個beanPostProcessor方法對bean進行增強,調用他的postProcessAfterInitialization方法,這個方法中有wrapIfNecessary方法的調用,用來決定是否需要創建代理類。
wrapIfNecessary方法里的findAdvisorsThatCanApply方法會去找所有的適用于當前bean的增強器根據切點和bean的方法信息做匹配。如果返回結果不為空,則會創建代理,否則不創建代理。底層是使用ProxyFactory來創建代理類的。
在真正的動態代理類中,我們存放的是各種低級切面,這時還未轉換為Methodinterceptor(環繞通知),在執行的時候會調用MethodBeforeAdviceAdapter這樣類似的適配器將低級切面(由@Before注解轉換為的AspectJMethodBeforeAdvice)轉換為環繞通知MethodBeforeAdviceInterceptor,這樣在執行的時候就能夠遞歸調用了。
以上是創建動態代理的過程,下面是執行動態代理方法的過程。
使用MethodInvocation來執行增強器方法,也就遞歸執行通知MethodBeforeAdviceInterceptor。看下面第九點。
around before advice
before advice
target method 執行
around after advice
after advice
afterReturning
9、JDK生成的動態代理
有兩種成員變量,
1、通過外界傳入并且使用靜態代碼塊賦值的invocationHandler
2、使用靜態代碼塊反射調獲取到的接口方法對象Method
因為jdk的動態代理需要實現和目標對象一樣的接口,所以代理對象也有對應的實現方法,在這些對應的實現方法中,是使用InvocationHandler來調用目標方法的,增強邏輯在InvocationHandler里面,我們通過傳入目標方法就是上面的成員變量,最終執行的時候會根據InvocationHandler方法中目標方法的占位來進行增強。
所以spring中的增強類里面都是各種通知,相當于invocationHandler的地位,只不過他是使用調用鏈的形式來執行的。真正的invocationHandler是JdkDynamicAopProxy。
JdkDynamicAopProxy這里面有invoke方法,方法里有MethodInvocation,這就是執行增強邏輯的調用鏈。
創建代理對象與執行代理方法
// org.springframework.aop.framework.JdkDynamicAopProxy#invoke // 攔截器鏈就是在創建代理的時候加入進去了,這里直接那就可以了。 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);10、攔截器(interceptor、advice)與增強器advisor
https://blog.csdn.net/weixin_36359107/article/details/113016311
為攔截器定義切入點(一組連接點)之后產生增強器,增強器可以有選擇性地攔截目標對象中的部分方法.
注意: 攔截器默認攔截所有目標對象中的方法
在我們生成代理對象的時候,需要將攔截器轉變為增強器,使用連接點來實現。
因為spring涉及過多攔截器advice、攔截方法MethodInterceptor,所以同一封裝成advisor來進行代理創建。
11、MethodInvocation
這個是真正執行的方法,相當于jdk中的InvokeHandler中實際調用的增強方法與目標方法鏈,在通知中會繼續遞歸調用MethodInvocation實現遞歸調用通知。
3、springMVC
1、流程
2、口述MVC流程
主要方法:org.springframework.web.servlet.DispatcherServlet#doDispatch
當有一個請求過來之后,首先要經過DispatcherServlet,通過dodispatch來處理請求。這個方法會去找在dispatcherServlet在初始化時就加載的各種HandlerMapping,調用他們的getHandler方法根據請求路徑獲取執行鏈。之后調用getHandlerAdapter獲取適配器,適配器會去調用handle方法處理請求。通過處理返回ModelAndView給doDispath方法中的mv。
之后doDispatch會去使用視圖解析器ViewResolver根據視圖名來解析視圖(這里進行視圖的解析,根據視圖邏輯名將ModelAndView解析成真正的視圖(view,View是一個接口, 它的實現類支持不同的視圖類型,如jsp,freemarker,pdf等等),之后渲染就是對View進行填充屬性),返回View對象,之后DispatcherServlet對View進行視圖渲染,將模型數據填充到視圖,返回視圖給用戶。
視圖解析器的作用就是將Handler處理完返回的ModelAndView對象中的邏輯視圖名轉換為真正的物理地址,就比如ModelAndView中的view字段的值為index,在我們自定義的視圖解析器中配置好了前綴(/prefix=/WEB-INF/)和后綴(.jsp),拼接在一起就是/WEB-INF/index.jsp,之后創建view對象,這個view對象就是對應的jsp文件,但是還未進行屬性填充,里面有一個map保存了對應的屬性值,視圖解析器將view交給dispatchservlet后,dispatch會執行view中的render方法來填充屬性,并且返回給客戶端。
3、ControllerAdvice
https://zhuanlan.zhihu.com/p/73087879
https://www.cnblogs.com/hcmwljfh/p/15767215.html
這個主要是用來拓展controller的一些功能,為執行controller提供一些拓展點,比如說,異常控制,參數解析,返回值解析等。
在處理controller異常時,先創建一個被@ControllerAdvice標注的類,里面的方法配合上@ExecptionHandler(異常類)注解,之后出現異常就會調用的這個handler來處理這個異常。
具體調用者是DispatchServlet檢測到異常之后調用ExcptionHandlerExceptionResolver來調用ExceptionHandlerMethodResolver 處理異常。被@ControllerAdvice標注的類在DispatcherServlet初始化時就被加入了initHandlerExceptionResolvers(context);
4、spring事務
1、spring事務的實現原理
spring事務本質還是數據庫對事務的支持,spring提供接口,具體事務還是由數據庫實現。重寫和回滾是靠數據庫的redolog和undolog進行的。
2、spring的事務種類
- 編程式事務,使用Transaction Template進行操作事務的開啟結束回滾。
- 聲明式事務:本質上是通過AOP來操作的,對方法前后進行攔截,將事務處理功能通過AOP的方式加入方法中,在執行前開啟事務,執行完后提交事務。
兩者優缺點:編程式事務粒度較細,而聲明式事務簡單方便,不用將事務操作代碼寫入方法中,減少代碼污染。
3、spring事務傳播機制
https://blog.csdn.net/qq_52252193/article/details/125181180 這個通俗易懂
七個,兩個拋異常,兩個要,一個隨緣,一個強盜。一個小三。
- 需要:如果當前有事務則加入,沒有事務就創建一個事務
- 有錢:無論當前是否存在事務都新建一個事務
- 隨緣:如果當前存在事務就加入該事務,無則以無事務方式執行
- 霸道:以非事務方式執行,如果有事務則將當前事務掛起
- 嵌入:如果當前有事務則嵌套事務內執行,無則創建一個事務
- 冷漠:需要事務,沒有則拋出異常
- 非常冷漠:不需要事務,有則拋出異常
需要 有錢 嵌入的區別
https://blog.csdn.net/yanxin1213/article/details/100582643
總結:需要是一個事務,同生共死。有錢是連個獨立的事務,兩者異常會分別處理,異常會回滾。嵌入則看外面的,外面的回滾了則都要回滾,如果子死了不影響父,而需要則是一起回滾。
REQUIREDNEW是新建一個事務并且新開始的這個事務與原有事務無關,而NESTED則是當前存在事務時會開啟一個嵌套事務,在NESTED**情況下,父事務回滾時,子事務也會回滾,而REQUIREDNEW情況下,原有事務回滾,不會影響新開啟的事務
NESTED和REQUIRED的區別:
REQUIRED情況下,調用方存在事務時,則被調用方和調用方使用同一個事務,那么被調用方出現異常時,由于共用一個事務,所以無論是否catch異常,事務都會回滾,而在NESTED情況下,被調用方發生異常時,調用方可以catch其異常,這樣只有子事務回滾,父事務不會回滾。
4、事務隔離級別
這個和數據庫的一樣
有五個,多的一個是使用數據庫默認的隔離級別
1、讀未提交,有臟讀問題
2、讀已提交,強調插入,解決臟讀問題
什么是臟讀?一個事務在修改了數據,但是未提交,其他事務能夠獲取到修改但未提交的數據,這時這個事務回滾了,則讀到的數據是錯誤的,這就臟讀。
讀已提交就讓事務B只能讀取到已經提交的事務。但是還會出現不可重復讀問題
什么是不可重復讀?
讀已提交不會限制寫事務,也就是事務A在一個事務中多次讀取同一行數據,這時有一個事務B在A事務還未結束的時候修改了該行數據,這時,A多次讀到的數據就會不一致,使用可重復讀隔離級別可解決。
3、可重復讀,強調更新
實現原理:添加讀鎖,在讀事務為提交的時候,寫事務不能修改該行
但是會出現幻讀現象
什么是幻讀?
在可重復讀隔離級別下,他并沒有全表加鎖,而是添加的行鎖,例如一個事務在進行全表更新的時候,還未提交事務,這時有一個線程來了新增一條數據,這時A提交事務后發現漏修改了一條數據,好像出現幻覺一樣
這個問題可以通過序列化解決。
4、序列化
mysql的默認隔離級別是讀已提交,通過MVCC來解決不可重復讀(不能解決幻讀問題)問題。MVCC是多版本并發控制的縮寫,主要是通過添加版本號實現的。每次更新數據都會添加一個版本號,當別的線程來的時候發現版本號和自己的不一致,這時就會重新執行一遍,直到版本號和自己預期的一致才進行修改。
5、spring事務什么時候會失效?
1、bean對象沒有被spring容器管理
2、方法的訪問修飾符不是public
3、自身調用問題
4、數據源沒有配置事務管理器
5、數據庫不支持事務
6、異常被捕獲
7、異常類型錯誤或者配置錯誤
5、spring其他相關問題
1、spring中使用到的設計模式
1、工廠模式,spring中使用BeanFactory來創建對象
2、代理模式:在spring中的AOP中有使用到,spring使用jdk和cglib來創建代理對象
3、適配器模式:在springAOP中使用適配器將各種advisor轉換為MethodInterceptor,以及在MVC中使用適配器去找到執行前面Mapping返回Handle
4、模板方法模式:可以將相同部分的代碼放在父類中,而將不同的代碼放入不同的子類中,用來解決代碼重復的問題。比如RestTemplate, JmsTemplate, JpaTemplate。我知道的是Springboot中的啟動類run方法,還有初始化容器的refresh方法。
5、觀察者模式:這個就是事件發布模式,在springboot中run方法執行期間會發送許多事件,他會根據事件來執行相應的方法。以及在容器初始化的時候也會發送事件
6、責任鏈模式:DispatcherServlet 中的 doDispatch() 方法中獲取與請求匹配的處理器getHandleMapping
HandlerExecutionChain,this.getHandler() 方法的處理使用到了責任鏈模式。
2、ApplicationContext與BeanFactory區別
1、首先呢APplicationContext繼承了BeanFactory,所以ApplicationContext肯定比BeanFactory更加強大
2、ApplicationContext還繼承了MessageSource接口,能夠實現國際化
3、繼承了APplicationEvenPublisher,能夠發送消息。比如在bean容器初始化時就會發送容器初始化成功的消息,實現了ApplicationListener的Bean都會收到這個消息, 能夠根據消息做出回應。
4、還繼承了ResourceLoader能夠加載多個resource
3、Bean和Component注解區別
Bean注解是用在方法上面的,一般用來注冊第三方的Bean,且這個配置類要加上Configuration注解。而Component注解一般是注冊我們自定義的bean,一般通過類路徑來掃描到,或者使用ComponentScan注解來指定。
4、Repository和Mapper區別
@Repository是spring的注解,單獨使用是沒有用的,用于聲明一個bean,但是他是放在接口上,所以這個bean是沒用的。spring中在mapper接口上寫一個@Repository注解,只是為了標識,要想真正是這個接口被掃描,必須使用@MapperScannerConfigurer
而Mapper是mybatis提供的注解,在spring程序中,mybatis需要找到對應的mapper,在編譯時生成動態代理類,與數據庫進行交互,這時需要用到@Mapper注解
但是有時候當我們有很多mapper接口時,就需要寫很多@Mappe注解,這樣很麻煩,有一種簡便的配置化方法便是在啟動類上使
用@MapperScan注解。
5、ImportBeanDefinitionRegistrar
https://blog.csdn.net/baidu_19473529/article/details/90613661
ImportBeanDefinitionRegistrar接口是也是spring的擴展點之一,它可以支持我們自己寫的代碼封裝成BeanDefinition對象;實現此接口的類會回調postProcessBeanDefinitionRegistry方法,注冊到spring容器中。把bean注入到spring容器不止有 @Service @Component等注解方式;還可以實現此接口。
使用方式和選擇器一樣,將這個接口的實現類加入@Import中,這樣在refresh調用處理invokeBeanFactoryPostProcessors時先加載配置類即@Configuration的ConfigurationClassPostProcessor后處理器(這個處理器會去處理@Configuration, @Component, @ComponentScan, @Import, @Bean),再去解析@Import注解中導入的注冊器類,從而將注冊器中定義的beandefine加入bean定義集合中。
6、談談你對spring boot Starter的理解
https://blog.csdn.net/qq_27828675/article/details/115700493
因為spring是一個很火的一個框架,其他第三方框架想要整合進spring中,就可以去創建一個starter,這個starter中有類似于spring自動裝配的文件spring.factories,這個文件中包含了這個框架需要創建的類,這些類就是一些配置類,讓spring能夠使用這些類來進行操作第三方框架。
starter就好像一個容器,事先為我們定義好了一些類,使我能能夠開箱即用。
命名規范:官方命名規范為:spring-boot-starter-xxx
第三方命名為:xxx-boot-starter
為什么添加了一個starter之后,他能夠自動添加bean到spring容器中?
這時因為在添加了starter之后還要添加一個注解在springboot的啟動類中,這個注解就是類似spring中自動配置注解@EnableAutoConfiguration,只不過他換了一個名字叫@EnableFeignClients,這里同樣包含了一個@Import注解來導入選擇器類來進行選擇要自動配置的bean
這段是錯的
真正的應該是:添加一個starter以后,這個starter中有同時導入了spring-boot-autoconfigure這個jar包,在MATA-INF/spring.factories中定義了要加載的自動配置類,在springboot自動配置的時候,會使用springfactoriesloader來掃描所有jar包中MATA-INF/spring.factories中的配置類,因此配置類中的bean就能加載到容器中,對于一些不是starter的jar包,想要實現自動配置,只要使用import注解,將配置類導入也能實現自動配置類的效果,只不過要使用jar包中提供的一個形如@Enablexxx的注解,這個注解中就包含了import注解, 這就相當于多了一步手動指定要要導入的配置類而已。
https://www.bilibili.com/video/BV1tw411f79E?p=86&spm_id_from=pageDriver&vd_source=7943439cc99094bee6c5f0fe67178c3d
這個網址就是說明這個情況的。
那為什么添加mybaits-plus的時候不需要添加這個注解呢?
因為它是starter,不是starter才需要這些注解
其實也可以不加這個注解,只要那個starter中有一個自動配置類即可,就是添加了@Configuration注解的類。springboot在啟動過程中會自動加載一個bean工廠后處理器ConfigurationClassPostProcessor,這個后處理的方法會去查找所有被@Configuration注解標注的類,并將配置類中的@bean注解標注的方法生成bean對象。
因為這個處理器會找到對應configuration類,進行自動裝配。
新疑問:究竟是誰來加載@configuration的,是ConfigurationClassPostProcessor還是spring的那個springfactoriesloader來加載項目下的所有starter的spring.factories來加載xxxAutoConfiguration類的
解答:
這個是ConfigurationClassPostProcessor上面的注釋
p406根據書上的所說的:是使用ConfigurationClassPostProcessor(自動生成的一個后處理器)來調用AntoConfigurationImportSelector中的selectImports方法,進而調用到@Import注解中傳入的參數xxxSelector,就是選擇器類,進而使用springfactoriesloader來加載所有JAR中的spring.factories定義的類。
在啟動類上有ComponentScan注解,遇到這個注解spring就會去自動注冊ConfigurationClassBeanPostProcessor這個bean工廠后處理器,之后就會去調用loader來加載spring.factories中的類,使用反射來創建實例,之后根據@bean注解來創對應的實例加入容器中,實現自動配置。
7、spring-boot-starter-parent
這個是一個版本控制坐標,里面只是定義常用starter的版本,比如說mysql驅動、redis、amqp、web等等,使用這個以后,我們導入maven坐標直接不用寫版本號了,省得版本沖突。
就好像別人給你配好了這一套能夠正常運行,直接用就好了。
但是這里并沒有真正的導入依賴,只是提供版本號而已,我們還需要手動使用標簽來進行引入。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.6</version> </dependency>6、springBoot
1、run方法與構造方法流程
在springboot的啟動類中,首先會去執行springapplication的構造器方法,他的構造器主要做了
org\springframework\boot\spring-boot\2.2.2.RELEASE\spring-boot-2.2.2.RELEASE.jar!\META-INF\spring.factories
1、判斷應用類型
2、加載/META-INF/springfactories中定義的的初始化器,這些初始化器主要用來在初始化容器之前做一些事情。
3、加載/META-INF/springfactories中定義的的監聽器,這些監聽器會監聽在啟動過程中發布的事件。
4、推斷啟動類
執行完構造方法之后,他就會去執行run方法
run方法主要做了
1、加載/META-INF/springfactories中定義的的事件發布器
2、將啟動類中給的arg參數和一些命令行參數等一些配置文件環境封裝起來
3、打印banner
4、加載org\springframework\boot\spring-boot\2.2.2.RELEASE\spring-boot-2.2.2.RELEASE.jar!\META-INF\spring.factories中的Failure Analyzers,用來處理在初始化容器時出現的一些錯誤
5、準備容器,根據前面獲取到的配置信息準備容器,設置環境對象,配置監聽器,加載資源
6、refresh容器,這一步和spring中的基本類似,只不過多了一個自動配置,這是一個bean的后處理器來實現的。
7、執行runner,這一步執行了實現了CommandLineRunner ApplicationRunner接口的方法,方法作用根據業務來決定,可以用來數據的預加載
8、發布容器準備完成事件。
public ConfigurableApplicationContext run(String... args) {//記錄初始化開始時間StopWatch stopWatch = new StopWatch();stopWatch.start();//容器ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//設置一些屬性到系統屬性中configureHeadlessProperty();//1、獲取事件發布器,和構造方法中不同,構造方法中的監聽器,這個類的實現類叫做EventPublishingRunListener,也是通過springfactoriesloader去讀取/META_INF/springfactories設置好的事件發布器SpringApplicationRunListeners listeners = getRunListeners(args);//事件發布器發布application staring事件listeners.starting();try {//2、封裝啟動類中傳遞的參數argApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//準備 Environment 添加命令行參數,就是設置一些參數//3先獲取默認環境變量源,4同一變量名規則,5添加其他變量源(application.yml等),6是將配置綁定到SpringApplication對象中ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);//7、打印banneerBanner printedBanner = printBanner(environment);//8、根據應用類型創建容器context = createApplicationContext();//9、異常處理器,發生異常的時候調用這些處理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//10、準備容器,會發布一個容器初始事件。只是一個空的籃子prepareContext(context, environment, listeners, applicationArguments, printedBanner);//11、refresh方法,這個方法中有一個bean后處理器去實現了bean的自動裝配,看下面refreshContext(context);afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context; }2、自動配置
回答:在springboot的啟動類中,有一個叫做springbootapplication的注解,他是一個復合注解,由@EnableAutoConfiguration、@ComponentScan、@SpringConfiguration注解組成,其中@EnableAutoConfiguration注解是開啟自動配置的注解。而@EnableAutoConfiguration注解中又是一個復合注解,他里面有個@Import注解導入一個實現了ImportSelecter接口的實現類,這個實現類中的方法會去使用springfactoryLoader這個加載器去讀取/META-INF/下的spring-factories文件,根據條件進行過濾,返回需要自動裝配的類的名字。
在springboot的啟動方法run方法中,在后面的refresh方法的invokeBeanFactoryPostProcessors會去執行ConfigurationClassPostProcessor這個bean工廠后置處理的方法,這個方法會解析被@Configuration注解標識的類,而我們的springboot啟動類上的@SpringbootApplication注解中的EnableAutoConfiguration中又包含了@Import注解,這個注解的作用是導入屬性中選擇器返回的類名集合,一般這個選擇器返回的是第三方配置類,所以我們就自動將第三方的beanDefine加入了容器中。之后經歷bean的初始化依賴注入之后我們就能用了。
更正ConfigurationClassPostProcessor并不會去解析被@Configuration注解標識的類,而是通過各種調用最終使用springfactoriesloader來加載spring.factoies中的定義的類。
解析@Bean注解的類是ConfigurationClassParser來進行的。
@Configuration注解作用只是用來存放@bean方法的。
List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));選擇器使用是SpringFactoriesLoader來加載,這個加載器很強大,能夠掃描jar包中的META-INF/spring.factories文件,這個文件定義了很多自動配置類。找到key為getSpringFactoriesLoaderFactoryClass()返回值(EnableAutoConfiguration.class)的value,將他們的名字返回給@Import注解,這樣import注解就知道要導入哪個bean了。
@Import先默認解析的是第三方的bean,也就是選擇器中的第三方的自動配置類,且spring默認允許bean被覆蓋,因此我們本項目中的bean會覆蓋掉第三方的bean
遇到同名的bean,spring會優先選擇我們本項目中bean。因為spring中bean同名會報錯,如果我們更改了BeanFactory不可被覆蓋,則先加載的第三方這樣就會用不到我們自定以的數據源了,這是我們不希望看到的。
我們可以更換選擇器的實現接口,將ImportSelector改成DeferedImportSelector,這樣第三方的自動配置類就會慢于我們自定義的配置類了,前提是不允許bean覆蓋。
3、springApplication的構造方法
真正都構造方法,主要做了四件事情。
第二次查證
lassParser來進行的。
@Configuration注解作用只是用來存放@bean方法的。
[外鏈圖片轉存中…(img-4LsPvUlG-1668352034494)]
List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));選擇器使用是SpringFactoriesLoader來加載,這個加載器很強大,能夠掃描jar包中的META-INF/spring.factories文件,這個文件定義了很多自動配置類。找到key為getSpringFactoriesLoaderFactoryClass()返回值(EnableAutoConfiguration.class)的value,將他們的名字返回給@Import注解,這樣import注解就知道要導入哪個bean了。
@Import先默認解析的是第三方的bean,也就是選擇器中的第三方的自動配置類,且spring默認允許bean被覆蓋,因此我們本項目中的bean會覆蓋掉第三方的bean
遇到同名的bean,spring會優先選擇我們本項目中bean。因為spring中bean同名會報錯,如果我們更改了BeanFactory不可被覆蓋,則先加載的第三方這樣就會用不到我們自定以的數據源了,這是我們不希望看到的。
我們可以更換選擇器的實現接口,將ImportSelector改成DeferedImportSelector,這樣第三方的自動配置類就會慢于我們自定義的配置類了,前提是不允許bean覆蓋。
[外鏈圖片轉存中…(img-akeeXNpS-1668352034495)]
3、springApplication的構造方法
[外鏈圖片轉存中…(img-gMhey0Fh-1668352034496)]
[外鏈圖片轉存中…(img-KwNZXYHA-1668352034496)]
[外鏈圖片轉存中…(img-UkYHinUf-1668352034496)]
[外鏈圖片轉存中…(img-SkyE8eRT-1668352034496)]
真正都構造方法,主要做了四件事情。
[外鏈圖片轉存中…(img-lyupSlbc-1668352034497)]
[外鏈圖片轉存中…(img-4dY7FeeY-1668352034497)]
[外鏈圖片轉存中…(img-95NMChkW-1668352034498)]
第二次查證
在啟動類上有ComponentScan注解,遇到這個注解spring就會去自動注冊ConfigurationClassBeanPostProcessor這個bean工廠后處理器,之后就會去調用loader來加載spring.factories中的類,使用反射來創建實例,之后根據@bean注解來創對應的實例加入容器中,實現自動配置。
總結
- 上一篇: css 滤镜 filter: grays
- 下一篇: 福建最新建筑八大员(机械员)模拟真题集及