三天吃透Spring面試八股文
Spring是什么?
Spring是一個輕量級的控制反轉(zhuǎn)(IoC)和面向切面(AOP)的容器框架。
Spring的優(yōu)點
通過控制反轉(zhuǎn)和依賴注入實現(xiàn)松耦合。
支持面向切面的編程,并且把應(yīng)用業(yè)務(wù)邏輯和系統(tǒng)服務(wù)分開。
通過切面和模板減少樣板式代碼。
聲明式事務(wù)的支持。可以從單調(diào)繁冗的事務(wù)管理代碼中解脫出來,通過聲明式方式靈活地進行事務(wù)的管理,提高開發(fā)效率和質(zhì)量。
方便集成各種優(yōu)秀框架。內(nèi)部提供了對各種優(yōu)秀框架的直接支持(如:Hessian、Quartz、MyBatis等)。
方便程序的測試。Spring支持Junit4,添加注解便可以測試Spring程序。
Spring 用到了哪些設(shè)計模式?
1、簡單工廠模式:BeanFactory
就是簡單工廠模式的體現(xiàn),根據(jù)傳入一個唯一標(biāo)識來獲得 Bean 對象。
@Override
public Object getBean(String name) throws BeansException {
? ?assertBeanFactoryActive();
? ?return getBeanFactory().getBean(name);
}
2、工廠方法模式:FactoryBean
就是典型的工廠方法模式。spring在使用getBean()
調(diào)用獲得該bean時,會自動調(diào)用該bean的getObject()
方法。每個 Bean 都會對應(yīng)一個 FactoryBean
,如 SqlSessionFactory
對應(yīng) SqlSessionFactoryBean
。
3、單例模式:一個類僅有一個實例,提供一個訪問它的全局訪問點。Spring 創(chuàng)建 Bean 實例默認是單例的。
4、適配器模式:SpringMVC中的適配器HandlerAdatper
。由于應(yīng)用會有多個Controller實現(xiàn),如果需要直接調(diào)用Controller方法,那么需要先判斷是由哪一個Controller處理請求,然后調(diào)用相應(yīng)的方法。當(dāng)增加新的 Controller,需要修改原來的邏輯,違反了開閉原則(對修改關(guān)閉,對擴展開放)。
為此,Spring提供了一個適配器接口,每一種 Controller 對應(yīng)一種 HandlerAdapter
實現(xiàn)類,當(dāng)請求過來,SpringMVC會調(diào)用getHandler()
獲取相應(yīng)的Controller,然后獲取該Controller對應(yīng)的 HandlerAdapter
,最后調(diào)用HandlerAdapter
的handle()
方法處理請求,實際上調(diào)用的是Controller的handleRequest()
。每次添加新的 Controller 時,只需要增加一個適配器類就可以,無需修改原有的邏輯。
常用的處理器適配器:SimpleControllerHandlerAdapter
,HttpRequestHandlerAdapter
,AnnotationMethodHandlerAdapter
。
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public class HttpRequestHandlerAdapter implements HandlerAdapter {
? ?@Override
? ?public boolean supports(Object handler) {//handler是被適配的對象,這里使用的是對象的適配器模式
? ? ? ?return (handler instanceof HttpRequestHandler);
? ?}
? ?@Override
? ?@Nullable
? ?public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
? ? ? ?throws Exception {
? ? ? ?((HttpRequestHandler) handler).handleRequest(request, response);
? ? ? ?return null;
? ?}
}
5、代理模式:spring 的 aop 使用了動態(tài)代理,有兩種方式JdkDynamicAopProxy
和Cglib2AopProxy
。
6、觀察者模式:spring 中 observer 模式常用的地方是 listener 的實現(xiàn),如ApplicationListener
。
7、模板模式: Spring 中 jdbcTemplate
、hibernateTemplate
等,就使用到了模板模式。
本文已經(jīng)收錄到Github倉庫,該倉庫包含計算機基礎(chǔ)、Java基礎(chǔ)、多線程、JVM、數(shù)據(jù)庫、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服務(wù)、設(shè)計模式、架構(gòu)、校招社招分享等核心知識點,歡迎star~
什么是AOP?
面向切面編程,作為面向?qū)ο蟮囊环N補充,將公共邏輯(事務(wù)管理、日志、緩存等)封裝成切面,跟業(yè)務(wù)代碼進行分離,可以減少系統(tǒng)的重復(fù)代碼和降低模塊之間的耦合度。切面就是那些與業(yè)務(wù)無關(guān),但所有業(yè)務(wù)模塊都會調(diào)用的公共邏輯。
AOP有哪些實現(xiàn)方式?
AOP有兩種實現(xiàn)方式:靜態(tài)代理和動態(tài)代理。
靜態(tài)代理
靜態(tài)代理:代理類在編譯階段生成,在編譯階段將通知織入Java字節(jié)碼中,也稱編譯時增強。AspectJ使用的是靜態(tài)代理。
缺點:代理對象需要與目標(biāo)對象實現(xiàn)一樣的接口,并且實現(xiàn)接口的方法,會有冗余代碼。同時,一旦接口增加方法,目標(biāo)對象與代理對象都要維護。
動態(tài)代理
動態(tài)代理:代理類在程序運行時創(chuàng)建,AOP框架不會去修改字節(jié)碼,而是在內(nèi)存中臨時生成一個代理對象,在運行期間對業(yè)務(wù)方法進行增強,不會生成新類。
Spring AOP的實現(xiàn)原理
Spring
的AOP
實現(xiàn)原理其實很簡單,就是通過動態(tài)代理實現(xiàn)的。如果我們?yōu)?code>Spring的某個bean
配置了切面,那么Spring
在創(chuàng)建這個bean
的時候,實際上創(chuàng)建的是這個bean
的一個代理對象,我們后續(xù)對bean
中方法的調(diào)用,實際上調(diào)用的是代理類重寫的代理方法。而Spring
的AOP
使用了兩種動態(tài)代理,分別是JDK的動態(tài)代理,以及CGLib的動態(tài)代理。
JDK動態(tài)代理和CGLIB動態(tài)代理的區(qū)別?
Spring AOP中的動態(tài)代理主要有兩種方式:JDK動態(tài)代理和CGLIB動態(tài)代理。
最全面的Java面試網(wǎng)站:
JDK動態(tài)代理
如果目標(biāo)類實現(xiàn)了接口,Spring AOP會選擇使用JDK動態(tài)代理目標(biāo)類。代理類根據(jù)目標(biāo)類實現(xiàn)的接口動態(tài)生成,不需要自己編寫,生成的動態(tài)代理類和目標(biāo)類都實現(xiàn)相同的接口。JDK動態(tài)代理的核心是InvocationHandler
接口和Proxy
類。
缺點:目標(biāo)類必須有實現(xiàn)的接口。如果某個類沒有實現(xiàn)接口,那么這個類就不能用JDK動態(tài)代理。
CGLIB動態(tài)代理
通過繼承實現(xiàn)。如果目標(biāo)類沒有實現(xiàn)接口,那么Spring AOP會選擇使用CGLIB來動態(tài)代理目標(biāo)類。CGLIB(Code Generation Library)可以在運行時動態(tài)生成類的字節(jié)碼,動態(tài)創(chuàng)建目標(biāo)類的子類對象,在子類對象中增強目標(biāo)類。
CGLIB是通過繼承的方式做的動態(tài)代理,因此如果某個類被標(biāo)記為final
,那么它是無法使用CGLIB做動態(tài)代理的。
優(yōu)點:目標(biāo)類不需要實現(xiàn)特定的接口,更加靈活。
什么時候采用哪種動態(tài)代理?
如果目標(biāo)對象實現(xiàn)了接口,默認情況下會采用JDK的動態(tài)代理實現(xiàn)AOP
如果目標(biāo)對象實現(xiàn)了接口,可以強制使用CGLIB實現(xiàn)AOP
如果目標(biāo)對象沒有實現(xiàn)了接口,必須采用CGLIB庫
兩者的區(qū)別:
jdk動態(tài)代理使用jdk中的類Proxy來創(chuàng)建代理對象,它使用反射技術(shù)來實現(xiàn),不需要導(dǎo)入其他依賴。cglib需要引入相關(guān)依賴:
asm.jar
,它使用字節(jié)碼增強技術(shù)來實現(xiàn)。當(dāng)目標(biāo)類實現(xiàn)了接口的時候Spring Aop默認使用jdk動態(tài)代理方式來增強方法,沒有實現(xiàn)接口的時候使用cglib動態(tài)代理方式增強方法。
Spring AOP相關(guān)術(shù)語
(1)切面(Aspect):切面是通知和切點的結(jié)合。通知和切點共同定義了切面的全部內(nèi)容。
(2)連接點(Join point):指方法,在Spring AOP中,一個連接點總是代表一個方法的執(zhí)行。連接點是在應(yīng)用執(zhí)行過程中能夠插入切面的一個點。這個點可以是調(diào)用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應(yīng)用的正常流程之中,并添加新的行為。
(3)通知(Advice):在AOP術(shù)語中,切面的工作被稱為通知。
(4)切入點(Pointcut):切點的定義會匹配通知所要織入的一個或多個連接點。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點。
(5)引入(Introduction):引入允許我們向現(xiàn)有類添加新方法或?qū)傩浴?/p>
(6)目標(biāo)對象(Target Object): 被一個或者多個切面(aspect)所通知(advise)的對象。它通常是一個代理對象。
(7)織入(Weaving):織入是把切面應(yīng)用到目標(biāo)對象并創(chuàng)建新的代理對象的過程。在目標(biāo)對象的生命周期里有以下時間點可以進行織入:
編譯期:切面在目標(biāo)類編譯時被織入。AspectJ的織入編譯器是以這種方式織入切面的。
類加載期:切面在目標(biāo)類加載到JVM時被織入。需要特殊的類加載器,它可以在目標(biāo)類被引入應(yīng)用之前增強該目標(biāo)類的字節(jié)碼。AspectJ5的加載時織入就支持以這種方式織入切面。
運行期:切面在應(yīng)用運行的某個時刻被織入。一般情況下,在織入切面時,AOP容器會為目標(biāo)對象動態(tài)地創(chuàng)建一個代理對象。SpringAOP就是以這種方式織入切面。
Spring通知有哪些類型?
在AOP術(shù)語中,切面的工作被稱為通知。通知實際上是程序運行時要通過Spring AOP框架來觸發(fā)的代碼段。
Spring切面可以應(yīng)用5種類型的通知:
前置通知(Before):在目標(biāo)方法被調(diào)用之前調(diào)用通知功能;
后置通知(After):在目標(biāo)方法完成之后調(diào)用通知,此時不會關(guān)心方法的輸出是什么;
返回通知(After-returning ):在目標(biāo)方法成功執(zhí)行之后調(diào)用通知;
異常通知(After-throwing):在目標(biāo)方法拋出異常后調(diào)用通知;
環(huán)繞通知(Around):通知包裹了被通知的方法,在被通知的方法調(diào)用之前和調(diào)用之后執(zhí)行自定義的邏輯。
什么是依賴注入?
在Spring創(chuàng)建對象的過程中,把對象依賴的屬性注入到對象中。依賴注入主要有兩種方式:構(gòu)造器注入和屬性注入。
什么是IOC?
IOC:控制反轉(zhuǎn),由Spring容器管理bean的整個生命周期。通過反射實現(xiàn)對其他對象的控制,包括初始化、創(chuàng)建、銷毀等,解放手動創(chuàng)建對象的過程,同時降低類之間的耦合度。
IOC的好處?
ioc的思想最核心的地方在于,資源不由使用資源者管理,而由不使用資源的第三方管理,這可以帶來很多好處。第一,資源集中管理,實現(xiàn)資源的可配置和易管理。第二,降低了使用資源雙方的依賴程度,也就是我們說的耦合度。
也就是說,甲方要達成某種目的不需要直接依賴乙方,它只需要達到的目的告訴第三方機構(gòu)就可以了,比如甲方需要一雙襪子,而乙方它賣一雙襪子,它要把襪子賣出去,并不需要自己去直接找到一個賣家來完成襪子的賣出。它也只需要找第三方,告訴別人我要賣一雙襪子。這下好了,甲乙雙方進行交易活動,都不需要自己直接去找賣家,相當(dāng)于程序內(nèi)部開放接口,賣家由第三方作為參數(shù)傳入。甲乙互相不依賴,而且只有在進行交易活動的時候,甲才和乙產(chǎn)生聯(lián)系。反之亦然。這樣做什么好處么呢,甲乙可以在對方不真實存在的情況下獨立存在,而且保證不交易時候無聯(lián)系,想交易的時候可以很容易的產(chǎn)生聯(lián)系。甲乙交易活動不需要雙方見面,避免了雙方的互不信任造成交易失敗的問題。因為交易由第三方來負責(zé)聯(lián)系,而且甲乙都認為第三方可靠。那么交易就能很可靠很靈活的產(chǎn)生和進行了。
這就是ioc的核心思想。生活中這種例子比比皆是,支付寶在整個淘寶體系里就是龐大的ioc容器,交易雙方之外的第三方,提供可靠性可依賴可靈活變更交易方的資源管理中心。另外人事代理也是,雇傭機構(gòu)和個人之外的第三方。
參考鏈接:https://www.zhihu.com/question/23277575/answer/24259844
IOC容器初始化過程?
從XML中讀取配置文件。
將bean標(biāo)簽解析成 BeanDefinition,如解析 property 元素, 并注入到 BeanDefinition 實例中。
將 BeanDefinition 注冊到容器 BeanDefinitionMap 中。
BeanFactory 根據(jù) BeanDefinition 的定義信息創(chuàng)建實例化和初始化 bean。
單例bean的初始化以及依賴注入一般都在容器初始化階段進行,只有懶加載(lazy-init為true)的單例bean是在應(yīng)用第一次調(diào)用getBean()時進行初始化和依賴注入。
// AbstractApplicationContext
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
多例bean 在容器啟動時不實例化,即使設(shè)置 lazy-init 為 false 也沒用,只有調(diào)用了getBean()才進行實例化。
loadBeanDefinitions
采用了模板模式,具體加載 BeanDefinition
的邏輯由各個子類完成。
Bean的生命周期

1.調(diào)用bean的構(gòu)造方法創(chuàng)建Bean
2.通過反射調(diào)用setter方法進行屬性的依賴注入
3.如果Bean實現(xiàn)了BeanNameAware
接口,Spring將調(diào)用setBeanName
(),設(shè)置 Bean
的name(xml文件中bean標(biāo)簽的id)
4.如果Bean實現(xiàn)了BeanFactoryAware
接口,Spring將調(diào)用setBeanFactory()
把bean factory設(shè)置給Bean
5.如果存在BeanPostProcessor
,Spring將調(diào)用它們的postProcessBeforeInitialization
(預(yù)初始化)方法,在Bean初始化前對其進行處理
6.如果Bean實現(xiàn)了InitializingBean
接口,Spring將調(diào)用它的afterPropertiesSet
方法,然后調(diào)用xml定義的 init-method 方法,兩個方法作用類似,都是在初始化 bean 的時候執(zhí)行
7.如果存在BeanPostProcessor
,Spring將調(diào)用它們的postProcessAfterInitialization
(后初始化)方法,在Bean初始化后對其進行處理
8.Bean初始化完成,供應(yīng)用使用,這里分兩種情況:
8.1 如果Bean為單例的話,那么容器會返回Bean給用戶,并存入緩存池。如果Bean實現(xiàn)了DisposableBean
接口,Spring將調(diào)用它的destory
方法,然后調(diào)用在xml中定義的 destory-method
方法,這兩個方法作用類似,都是在Bean實例銷毀前執(zhí)行。
8.2 如果Bean是多例的話,容器將Bean返回給用戶,剩下的生命周期由用戶控制。
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
?return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
?return bean;
}
}
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
BeanFactory和FactoryBean的區(qū)別?
BeanFactory:管理Bean的容器,Spring中生成的Bean都是由這個接口的實現(xiàn)來管理的。
FactoryBean:通常是用來創(chuàng)建比較復(fù)雜的bean,一般的bean 直接用xml配置即可,但如果一個bean的創(chuàng)建過程中涉及到很多其他的bean 和復(fù)雜的邏輯,直接用xml配置比較麻煩,這時可以考慮用FactoryBean,可以隱藏實例化復(fù)雜Bean的細節(jié)。
當(dāng)配置文件中bean標(biāo)簽的class屬性配置的實現(xiàn)類是FactoryBean時,通過 getBean()方法返回的不是FactoryBean本身,而是調(diào)用FactoryBean#getObject()方法所返回的對象,相當(dāng)于FactoryBean#getObject()代理了getBean()方法。如果想得到FactoryBean必須使用 '&' + beanName 的方式獲取。
Mybatis 提供了 SqlSessionFactoryBean
,可以簡化 SqlSessionFactory
的配置:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
?@Override
?public void afterPropertiesSet() throws Exception {
? ?notNull(dataSource, "Property 'dataSource' is required");
? ?notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
? ?state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
? ? ? ? ? ? ?"Property 'configuration' and 'configLocation' can not specified with together");
? ?this.sqlSessionFactory = buildSqlSessionFactory();
?}
?protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//復(fù)雜邏輯
?}
? ?
?@Override
?public SqlSessionFactory getObject() throws Exception {
? ?if (this.sqlSessionFactory == null) {
? ? ?afterPropertiesSet();
? ?}
? ?return this.sqlSessionFactory;
?}
}
在 xml 配置 SqlSessionFactoryBean:
<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
? ?<property name="dataSource" ref="trade" />
? ?<property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
? ?<property name="configLocation" value="classpath:mybatis-config.xml" />
? ?<property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
</bean>
Spring 將會在應(yīng)用啟動時創(chuàng)建 SqlSessionFactory
,并使用 sqlSessionFactory
這個名字存儲起來。
BeanFactory和ApplicationContext有什么區(qū)別?
BeanFactory和ApplicationContext是Spring的兩大核心接口,都可以當(dāng)做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
兩者區(qū)別如下:
1、功能上的區(qū)別。BeanFactory是Spring里面最底層的接口,包含了各種Bean的定義,讀取bean配置文檔,管理bean的加載、實例化,控制bean的生命周期,維護bean之間的依賴關(guān)系。
ApplicationContext接口作為BeanFactory的派生,除了提供BeanFactory所具有的功能外,還提供了更完整的框架功能,如繼承MessageSource、支持國際化、統(tǒng)一的資源文件訪問方式、同時加載多個配置文件等功能。
2、加載方式的區(qū)別。BeanFactroy采用的是延遲加載形式來注入Bean的,即只有在使用到某個Bean時(調(diào)用getBean()),才對該Bean進行加載實例化。這樣,我們就不能發(fā)現(xiàn)一些存在的Spring的配置問題。如果Bean的某一個屬性沒有注入,BeanFacotry加載后,直至第一次使用調(diào)用getBean方法才會拋出異常。
而ApplicationContext是在容器啟動時,一次性創(chuàng)建了所有的Bean。這樣,在容器啟動時,我們就可以發(fā)現(xiàn)Spring中存在的配置錯誤,這樣有利于檢查所依賴屬性是否注入。 ApplicationContext啟動后預(yù)載入所有的單例Bean,那么在需要的時候,不需要等待創(chuàng)建bean,因為它們已經(jīng)創(chuàng)建好了。
相對于基本的BeanFactory,ApplicationContext 唯一的不足是占用內(nèi)存空間。當(dāng)應(yīng)用程序配置Bean較多時,程序啟動較慢。
3、創(chuàng)建方式的區(qū)別。BeanFactory通常以編程的方式被創(chuàng)建,ApplicationContext還能以聲明的方式創(chuàng)建,如使用ContextLoader。
4、注冊方式的區(qū)別。BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區(qū)別是:BeanFactory需要手動注冊,而ApplicationContext則是自動注冊。
Bean注入容器有哪些方式?
1、@Configuration + @Bean
@Configuration用來聲明一個配置類,然后使用 @Bean 注解,用于聲明一個bean,將其加入到Spring容器中。
@Configuration
public class MyConfiguration {
? ?@Bean
? ?public Person person() {
? ? ? ?Person person = new Person();
? ? ? ?person.setName("大彬");
? ? ? ?return person;
? ?}
}
2、通過包掃描特定注解的方式
@ComponentScan放置在我們的配置類上,然后可以指定一個路徑,進行掃描帶有特定注解的bean,然后加至容器中。
特定注解包括@Controller、@Service、@Repository、@Component
@Component
public class Person {
? ?//...
}
@ComponentScan(basePackages = "com.dabin.test.*")
public class Demo1 {
? ?public static void main(String[] args) {
? ? ? ?AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
? ? ? ?Person bean = applicationContext.getBean(Person.class);
? ? ? ?System.out.println(bean);
? ?}
}
3、@Import注解導(dǎo)入
@Import注解平時開發(fā)用的不多,但是也是非常重要的,在進行Spring擴展時經(jīng)常會用到,它經(jīng)常搭配自定義注解進行使用,然后往容器中導(dǎo)入一個配置文件。
@ComponentScan /*把用到的資源導(dǎo)入到當(dāng)前容器中*/ @Import({Person.class}) public class App { ? ? public static void main(String[] args) throws Exception { ? ? ? ? ConfigurableApplicationContext context = SpringApplication.run(App.class, args); ? ? ? ? System.out.println(context.getBean(Person.class)); ? ? ? ? context.close(); ? ? } }
4、實現(xiàn)BeanDefinitionRegistryPostProcessor進行后置處理。
在Spring容器啟動的時候會執(zhí)行 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法,就是等beanDefinition加載完畢之后,對beanDefinition進行后置處理,可以在此進行調(diào)整IOC容器中的beanDefinition,從而干擾到后面進行初始化bean。
在下面的代碼中,我們手動向beanDefinitionRegistry中注冊了person的BeanDefinition。最終成功將person加入到applicationContext中。
public class Demo1 { ? ? public static void main(String[] args) { ? ? ? ? AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); ? ? ? ? MyBeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor(); ? ? ? ? applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor); ? ? ? ? applicationContext.refresh(); ? ? ? ? Person bean = applicationContext.getBean(Person.class); ? ? ? ? System.out.println(bean); ? ? } } ? class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { ? ? @Override ? ? public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { ? ? ? ? AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition(); ? ? ? ? registry.registerBeanDefinition("person", beanDefinition); ? ? } ? ? ? ? ?@Override ? ? public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { ? ? } }
5、使用FactoryBean接口
如下圖代碼,使用@Configuration + @Bean的方式將 PersonFactoryBean 加入到容器中,這里沒有向容器中直接注入 Person,而是注入 PersonFactoryBean,然后從容器中拿Person這個類型的bean。
@Configuration public class Demo1 { ? ? @Bean ? ? public PersonFactoryBean personFactoryBean() { ? ? ? ? return new PersonFactoryBean(); ? ? } ? ? ? public static void main(String[] args) { ? ? ? ? AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class); ? ? ? ? Person bean = applicationContext.getBean(Person.class); ? ? ? ? System.out.println(bean); ? ? } } ? class PersonFactoryBean implements FactoryBean<Person> { ? ? @Override ? ? public Person getObject() throws Exception { ? ? ? ? return new Person(); ? ? } ? ? @Override ? ? public Class<?> getObjectType() { ? ? ? ? return Person.class; ? ? } }
Bean的作用域
1、singleton:單例,Spring中的bean默認都是單例的。
2、prototype:每次請求都會創(chuàng)建一個新的bean實例。
3、request:每一次HTTP請求都會產(chǎn)生一個新的bean,該bean僅在當(dāng)前HTTP request內(nèi)有效。
4、session:每一次HTTP請求都會產(chǎn)生一個新的bean,該bean僅在當(dāng)前HTTP session內(nèi)有效。
5、global-session:全局session作用域。
Spring自動裝配的方式有哪些?
Spring的自動裝配有三種模式:byType(根據(jù)類型),byName(根據(jù)名稱)、constructor(根據(jù)構(gòu)造函數(shù))。
byType
找到與依賴類型相同的bean注入到另外的bean中,這個過程需要借助setter注入來完成,因此必須存在set方法,否則注入失敗。
當(dāng)xml文件中存在多個相同類型名稱不同的實例Bean時,Spring容器依賴注入仍然會失敗,因為存在多種適合的選項,Spring容器無法知道該注入那種,此時我們需要為Spring容器提供幫助,指定注入那個Bean實例??梢酝ㄟ^<bean>
標(biāo)簽的autowire-candidate設(shè)置為false來過濾那些不需要注入的實例Bean
<bean id="userDao" ?class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- autowire-candidate="false" 過濾該類型 --> <bean id="userDao2" autowire-candidate="false" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- byType 根據(jù)類型自動裝配userDao--> <bean id="userService" autowire="byType" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
byName
將屬性名與bean名稱進行匹配,如果找到則注入依賴bean。
<bean id="userDao" ?class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <bean id="userDao2" class="com.zejian.spring.springIoc.dao.impl.UserDaoImpl" /> <!-- byName 根據(jù)名稱自動裝配,找到UserServiceImpl名為 userDao屬性并注入--> <bean id="userService" autowire="byName" class="com.zejian.spring.springIoc.service.impl.UserServiceImpl" />
constructor
存在單個實例則優(yōu)先按類型進行參數(shù)匹配(無論名稱是否匹配),當(dāng)存在多個類型相同實例時,按名稱優(yōu)先匹配,如果沒有找到對應(yīng)名稱,則注入失敗。
@Autowired和@Resource的區(qū)別?
Autowire是spring的注解。默認情況下@Autowired是按類型匹配的(byType)。如果需要按名稱(byName)匹配的話,可以使用@Qualifier注解與@Autowired結(jié)合。@Autowired 可以傳遞一個required=false
的屬性,false指明當(dāng)userDao實例存在就注入不存就忽略,如果為true,就必須注入,若userDao實例不存在,就拋出異常。
public class UserServiceImpl implements UserService { ? ? //標(biāo)注成員變量 ? ? @Autowired ? ? @Qualifier("userDao1") ? ? private UserDao userDao; ? ? }
Resource是j2ee的注解,默認按 byName模式自動注入。@Resource有兩個中重要的屬性:name和type。name屬性指定bean的名字,type屬性則指定bean的類型。因此使用name屬性,則按byName模式的自動注入策略,如果使用type屬性,則按 byType模式自動注入策略。倘若既不指定name也不指定type屬性,Spring容器將通過反射技術(shù)默認按byName模式注入。
@Resource(name="userDao")
private UserDao ?userDao;//用于成員變量
//也可以用于set方法標(biāo)注
@Resource(name="userDao")
public void setUserDao(UserDao userDao) {
? this.userDao= userDao;
}
上述兩種自動裝配的依賴注入并不適合簡單值類型,如int、boolean、long、String以及Enum等,對于這些類型,Spring容器也提供了@Value注入的方式。
@Value和@Autowired、@Resource類似,也是用來對屬性進行注入的,只不過@Value是用來從Properties文件中來獲取值的,并且@Value可以解析SpEL(Spring表達式)。
比如,jdbc.properties文件如下:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
利用注解@Value獲取jdbc.url和jdbc.username的值,實現(xiàn)如下:
public class UserServiceImpl implements UserService {
? ?//占位符方式
? ?@Value("${jdbc.url}")
? ?private String url;
? ?//SpEL表達方式,其中代表xml配置文件中的id值configProperties
? ?@Value("#{configProperties['jdbc.username']}")
? ?private String userName;
}
@Qualifier 注解有什么作用
當(dāng)需要創(chuàng)建多個相同類型的 bean 并希望僅使用屬性裝配其中一個 bean 時,可以使用@Qualifier
注解和 @Autowired
通過指定應(yīng)該裝配哪個 bean 來消除歧義。
@Bean和@Component有什么區(qū)別?
都是使用注解定義 Bean。@Bean 是使用 Java 代碼裝配 Bean,@Component 是自動裝配 Bean。
@Component 注解用在類上,表明一個類會作為組件類,并告知Spring要為這個類創(chuàng)建bean,每個類對應(yīng)一個 Bean。
@Bean 注解用在方法上,表示這個方法會返回一個 Bean。@Bean 需要在配置類中使用,即類上需要加上@Configuration注解。
@Component public class Student { ? ? private String name = "lkm"; ? ? ? public String getName() { ? ? ? ? return name; ? ? } } @Configuration public class WebSocketConfig { ? ? @Bean ? ? public Student student(){ ? ? ? ? return new Student(); ? ? } }
@Bean 注解更加靈活。當(dāng)需要將第三方類裝配到 Spring 容器中,因為沒辦法源代碼上添加@Component注解,只能使用@Bean 注解的方式,當(dāng)然也可以使用 xml 的方式。
@Component、@Controller、@Repositor和@Service 的區(qū)別?
@Component:最普通的組件,可以被注入到spring容器進行管理。
@Controller:將類標(biāo)記為 Spring Web MVC 控制器。
@Service:將類標(biāo)記為業(yè)務(wù)層組件。
@Repository:將類標(biāo)記為數(shù)據(jù)訪問組件,即DAO組件。
Spring 事務(wù)實現(xiàn)方式有哪些?
事務(wù)就是一系列的操作原子執(zhí)行。Spring事務(wù)機制主要包括聲明式事務(wù)和編程式事務(wù)。
編程式事務(wù):通過編程的方式管理事務(wù),這種方式帶來了很大的靈活性,但很難維護。
聲明式事務(wù):將事務(wù)管理代碼從業(yè)務(wù)方法中分離出來,通過aop進行封裝。Spring聲明式事務(wù)使得我們無需要去處理獲得連接、關(guān)閉連接、事務(wù)提交和回滾等這些操作。使用
@Transactional
注解開啟聲明式事務(wù)。
@Transactional
相關(guān)屬性如下:
屬性類型描述valueString可選的限定描述符,指定使用的事務(wù)管理器propagationenum: Propagation可選的事務(wù)傳播行為設(shè)置isolationenum: Isolation可選的事務(wù)隔離級別設(shè)置readOnlyboolean讀寫或只讀事務(wù),默認讀寫timeoutint (in seconds granularity)事務(wù)超時時間設(shè)置rollbackForClass對象數(shù)組,必須繼承自Throwable導(dǎo)致事務(wù)回滾的異常類數(shù)組rollbackForClassName類名數(shù)組,必須繼承自Throwable導(dǎo)致事務(wù)回滾的異常類名字數(shù)組noRollbackForClass對象數(shù)組,必須繼承自Throwable不會導(dǎo)致事務(wù)回滾的異常類數(shù)組noRollbackForClassName類名數(shù)組,必須繼承自Throwable不會導(dǎo)致事務(wù)回滾的異常類名字數(shù)組
有哪些事務(wù)傳播行為?
在TransactionDefinition接口中定義了七個事務(wù)傳播行為:
PROPAGATION_REQUIRED
如果存在一個事務(wù),則支持當(dāng)前事務(wù)。如果沒有事務(wù)則開啟一個新的事務(wù)。如果嵌套調(diào)用的兩個方法都加了事務(wù)注解,并且運行在相同線程中,則這兩個方法使用相同的事務(wù)中。如果運行在不同線程中,則會開啟新的事務(wù)。PROPAGATION_SUPPORTS
如果存在一個事務(wù),支持當(dāng)前事務(wù)。如果沒有事務(wù),則非事務(wù)的執(zhí)行。PROPAGATION_MANDATORY
如果已經(jīng)存在一個事務(wù),支持當(dāng)前事務(wù)。如果不存在事務(wù),則拋出異常IllegalTransactionStateException
。PROPAGATION_REQUIRES_NEW
總是開啟一個新的事務(wù)。需要使用JtaTransactionManager作為事務(wù)管理器。PROPAGATION_NOT_SUPPORTED
總是非事務(wù)地執(zhí)行,并掛起任何存在的事務(wù)。需要使用JtaTransactionManager作為事務(wù)管理器。PROPAGATION_NEVER
總是非事務(wù)地執(zhí)行,如果存在一個活動事務(wù),則拋出異常。PROPAGATION_NESTED
如果一個活動的事務(wù)存在,則運行在一個嵌套的事務(wù)中。如果沒有活動事務(wù), 則按PROPAGATION_REQUIRED 屬性執(zhí)行。
PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區(qū)別:
使用PROPAGATION_REQUIRES_NEW
時,內(nèi)層事務(wù)與外層事務(wù)是兩個獨立的事務(wù)。一旦內(nèi)層事務(wù)進行了提交后,外層事務(wù)不能對其進行回滾。兩個事務(wù)互不影響。
使用PROPAGATION_NESTED
時,外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾。而內(nèi)層事務(wù)的異常并不會導(dǎo)致外層事務(wù)的回滾,它是一個真正的嵌套事務(wù)。
Spring事務(wù)在什么情況下會失效?
1.訪問權(quán)限問題
java的訪問權(quán)限主要有四種:private、default、protected、public,它們的權(quán)限從左到右,依次變大。
如果事務(wù)方法的訪問權(quán)限不是定義成public,這樣會導(dǎo)致事務(wù)失效,因為spring要求被代理方法必須是public
的。
翻開源碼,可以看到,在AbstractFallbackTransactionAttributeSource
類的computeTransactionAttribute
方法中有個判斷,如果目標(biāo)方法不是public,則返回null,即不支持事務(wù)。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) { ? ? // Don't allow no-public methods as required. ? ? if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { ? ? ? return null; ? ? } ... }
2. 方法用final修飾
如果事務(wù)方法用final修飾,將會導(dǎo)致事務(wù)失效。因為spring事務(wù)底層使用了aop,也就是通過jdk動態(tài)代理或者cglib,幫我們生成了代理類,在代理類中實現(xiàn)的事務(wù)功能。
但如果某個方法用final修飾了,那么在它的代理類中,就無法重寫該方法,而添加事務(wù)功能。
同理,如果某個方法是static的,同樣無法通過動態(tài)代理,變成事務(wù)方法。
3.對象沒有被spring管理
使用spring事務(wù)的前提是:對象要被spring管理,需要創(chuàng)建bean實例。如果類沒有加@Controller、@Service、@Component、@Repository等注解,即該類沒有交給spring去管理,那么它的方法也不會生成事務(wù)。
4.表不支持事務(wù)
如果MySQL使用的存儲引擎是myisam,這樣的話是不支持事務(wù)的。因為myisam存儲引擎不支持事務(wù)。
5.方法內(nèi)部調(diào)用
如下代碼所示,update方法上面沒有加 @Transactional
注解,調(diào)用有 @Transactional
注解的 updateOrder 方法,updateOrder 方法上的事務(wù)會失效。
因為發(fā)生了自身調(diào)用,調(diào)用該類自己的方法,而沒有經(jīng)過 Spring 的代理類,只有在外部調(diào)用事務(wù)才會生效。
@Service public class OrderServiceImpl implements OrderService { ? ? public void update(Order order) { ? ? ? ? this.updateOrder(order); ? ? } ? ? @Transactional ? ? public void updateOrder(Order order) { ? ? ? ? // update order ? ? } }
解決方法:
1、再聲明一個service,將內(nèi)部調(diào)用改為外部調(diào)用
2、使用編程式事務(wù)
3、使用AopContext.currentProxy()獲取代理對象
@Servcie public class OrderServiceImpl implements OrderService { ? ? ? ? public void update(Order order) { ? ? ? ? ((OrderService)AopContext.currentProxy()).updateOrder(order); ? ?} ? ? @Transactional ? ? public void updateOrder(Order order) { ? ? ? ? // update order ? ? } ?}
6.未開啟事務(wù)
如果是spring項目,則需要在配置文件中手動配置事務(wù)相關(guān)參數(shù)。如果忘了配置,事務(wù)肯定是不會生效的。
如果是springboot項目,那么不需要手動配置。因為springboot已經(jīng)在DataSourceTransactionManagerAutoConfiguration
類中幫我們開啟了事務(wù)。
7.吞了異常
有時候事務(wù)不會回滾,有可能是在代碼中手動catch了異常。因為開發(fā)者自己捕獲了異常,又沒有手動拋出,把異常吞掉了,這種情況下spring事務(wù)不會回滾。
如果想要spring事務(wù)能夠正常回滾,必須拋出它能夠處理的異常。如果沒有拋異常,則spring認為程序是正常的。
Spring怎么解決循環(huán)依賴的問題?
首先,有兩種Bean注入的方式。
構(gòu)造器注入和屬性注入。
對于構(gòu)造器注入的循環(huán)依賴,Spring處理不了,會直接拋出BeanCurrentlylnCreationException
異常。
對于屬性注入的循環(huán)依賴(單例模式下),是通過三級緩存處理來循環(huán)依賴的。
而非單例對象的循環(huán)依賴,則無法處理。
下面分析單例模式下屬性注入的循環(huán)依賴是怎么處理的:
首先,Spring單例對象的初始化大略分為三步:
createBeanInstance
:實例化bean,使用構(gòu)造方法創(chuàng)建對象,為對象分配內(nèi)存。populateBean
:進行依賴注入。initializeBean
:初始化bean。
Spring為了解決單例的循環(huán)依賴問題,使用了三級緩存:
singletonObjects
:完成了初始化的單例對象map,bean name --> bean instance
earlySingletonObjects
:完成實例化未初始化的單例對象map,bean name --> bean instance
singletonFactories
: 單例對象工廠map,bean name --> ObjectFactory,單例對象實例化完成之后會加入singletonFactories。
在調(diào)用createBeanInstance進行實例化之后,會調(diào)用addSingletonFactory,將單例對象放到singletonFactories中。
protected 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);
? ? ? ?}
? ?}
}
假如A依賴了B的實例對象,同時B也依賴A的實例對象。
A首先完成了實例化,并且將自己添加到singletonFactories中
接著進行依賴注入,發(fā)現(xiàn)自己依賴對象B,此時就嘗試去get(B)
發(fā)現(xiàn)B還沒有被實例化,對B進行實例化
然后B在初始化的時候發(fā)現(xiàn)自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects和二級緩存earlySingletonObjects沒找到,嘗試三級緩存singletonFactories,由于A初始化時將自己添加到了singletonFactories,所以B可以拿到A對象,然后將A從三級緩存中移到二級緩存中
B拿到A對象后順利完成了初始化,然后將自己放入到一級緩存singletonObjects中
此時返回A中,A此時能拿到B的對象順利完成自己的初始化
由此看出,屬性注入的循環(huán)依賴主要是通過將實例化完成的bean添加到singletonFactories來實現(xiàn)的。而使用構(gòu)造器依賴注入的bean在實例化的時候會進行依賴注入,不會被添加到singletonFactories中。比如A和B都是通過構(gòu)造器依賴注入,A在調(diào)用構(gòu)造器進行實例化的時候,發(fā)現(xiàn)自己依賴B,B沒有被實例化,就會對B進行實例化,此時A未實例化完成,不會被添加到singtonFactories。而B依賴于A,B會去三級緩存尋找A對象,發(fā)現(xiàn)不存在,于是又會實例化A,A實例化了兩次,從而導(dǎo)致拋異常。
總結(jié):1、利用緩存識別已經(jīng)遍歷過的節(jié)點; 2、利用Java引用,先提前設(shè)置對象地址,后完善對象。
Spring啟動過程
讀取web.xml文件。
創(chuàng)建 ServletContext,為 ioc 容器提供宿主環(huán)境。
觸發(fā)容器初始化事件,調(diào)用 contextLoaderListener.contextInitialized()方法,在這個方法會初始化一個應(yīng)用上下文WebApplicationContext,即 Spring 的 ioc 容器。ioc 容器初始化完成之后,會被存儲到 ServletContext 中。
初始化web.xml中配置的Servlet。如DispatcherServlet,用于匹配、處理每個servlet請求。
Spring 的單例 Bean 是否有并發(fā)安全問題?
當(dāng)多個用戶同時請求一個服務(wù)時,容器會給每一個請求分配一個線程,這時多個線程會并發(fā)執(zhí)行該請求對應(yīng)的業(yè)務(wù)邏輯,如果業(yè)務(wù)邏輯有對單例狀態(tài)的修改(體現(xiàn)為此單例的成員屬性),則必須考慮線程安全問題。
無狀態(tài)bean和有狀態(tài)bean
有實例變量的bean,可以保存數(shù)據(jù),是非線程安全的。
沒有實例變量的bean,不能保存數(shù)據(jù),是線程安全的。
在Spring中無狀態(tài)的Bean適合用單例模式,這樣可以共享實例提高性能。有狀態(tài)的Bean在多線程環(huán)境下不安全,一般用Prototype
模式或者使用ThreadLocal
解決線程安全問題。
Spring Bean如何保證并發(fā)安全?
Spring的Bean默認都是單例的,某些情況下,單例是并發(fā)不安全的。
以 Controller
舉例,假如我們在 Controller
中定義了成員變量。當(dāng)多個請求來臨,進入的都是同一個單例的 Controller
對象,并對此成員變量的值進行修改操作,因此會互相影響,會有并發(fā)安全的問題。
應(yīng)該怎么解決呢?
為了讓多個HTTP請求之間不互相影響,可以采取以下措施:
1、單例變原型
對 web 項目,可以 Controller
類上加注解 @Scope("prototype")
或 @Scope("request")
,對非 web 項目,在 Component
類上添加注解 @Scope("prototype")
。
這種方式實現(xiàn)起來非常簡單,但是很大程度上增大了 Bean 創(chuàng)建實例化銷毀的服務(wù)器資源開銷。
2、盡量避免使用成員變量
在業(yè)務(wù)允許的條件下,可以將成員變量替換為方法中的局部變量。這種方式個人認為是最恰當(dāng)?shù)摹?/p>
3、使用并發(fā)安全的類
如果非要在單例Bean中使用成員變量,可以考慮使用并發(fā)安全的容器,如 ConcurrentHashMap
、ConcurrentHashSet
等等,將我們的成員變量包裝到這些并發(fā)安全的容器中進行管理即可。
4、分布式或微服務(wù)的并發(fā)安全
如果還要進一步考慮到微服務(wù)或分布式服務(wù)的影響,方式3便不合適了。這種情況下可以借助于可以共享某些信息的分布式緩存中間件,如Redis等。這樣即可保證同一種服務(wù)的不同服務(wù)實例都擁有同一份共享信息了。
@Async注解的原理
當(dāng)我們調(diào)用第三方接口或者方法的時候,我們不需要等待方法返回才去執(zhí)行其它邏輯,這時如果響應(yīng)時間過長,就會極大的影響程序的執(zhí)行效率。所以這時就需要使用異步方法來并行執(zhí)行我們的邏輯。在springboot中可以使用@Async注解實現(xiàn)異步操作。
使用@Async注解實現(xiàn)異步操作的步驟:
1.首先在啟動類上添加 @EnableAsync 注解。
@Configuration @EnableAsync public class App { ? ? public static void main(String[] args) { ? ? ? ? ?ApplicationContext ctx = new ? ? ? ? ? ? ? ?AnnotationConfigApplicationContext(App.class); ? ? ? ? MyAsync service = ctx.getBean(MyAsync.class); ? ? ? ? System.out.println(service.getClass()); ? ? ? ? service.async1(); ? ? ? ? System.out.println("main thread finish..."); ? ? } }
2.在對應(yīng)的方法上添加@Async注解。
@Component public class MyAsync { ? ? @Async ? ? public void asyncTest() { ? ? ? ? try { ? ? ? ? ? ? TimeUnit.SECONDS.sleep(20); ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? ? ? System.out.println("asyncTest..."); ? ? } }
運行代碼,控制臺輸出:
main thread finish... asyncTest...
證明asyncTest方法異步執(zhí)行了。
原理:
我們在主啟動類上貼了一個@EnableAsync注解,才能使用@Async生效。@EnableAsync的作用是通過@import導(dǎo)入了AsyncConfigurationSelector。在AsyncConfigurationSelector的selectImports方法將ProxyAsyncConfiguration定義為Bean注入容器。在ProxyAsyncConfiguration中通過@Bean的方式注入AsyncAnnotationBeanPostProcessor類。

代碼如下:
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
}
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
public String[] selectImports(AdviceMode adviceMode) {
?switch (adviceMode) {
? case PROXY:
? ?return new String[] { ProxyAsyncConfiguration.class.getName() };
? //...
?}
}
}
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
? ?@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
? ?public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
? ? ? ?//創(chuàng)建postProcessor
? ? ? ?AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
? ? ? ?//...
? ?}
}
AsyncAnnotationBeanPostProcessor往往期創(chuàng)建了一個增強器AsyncAnnotationAdvisor。在AsyncAnnotationAdvisor的buildAdvice方法中,創(chuàng)建了AnnotationAsyncExecutionInterceptor。
public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor {
? ?@Override
? ?public void setBeanFactory(BeanFactory beanFactory) {
? ? ? ?super.setBeanFactory(beanFactory);
? ? ? ?//創(chuàng)建一個增強器
? ? ? ?AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
? ? ? ?//...
? ? ? ?advisor.setBeanFactory(beanFactory);
? ? ? ?this.advisor = advisor;
? ?}
}
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
? ?public AsyncAnnotationAdvisor(
? ? ? ? ? ?@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
? ? ? ?//增強方法
? ? ? ?this.advice = buildAdvice(executor, exceptionHandler);
? ? ? ?this.pointcut = buildPointcut(asyncAnnotationTypes);
? ?}
? ?// 委托給AnnotationAsyncExecutionInterceptor攔截器
? ?protected Advice buildAdvice(
? ? ? ? ? ?@Nullable Supplier<Executor> executor, @Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
? ? ? ?//攔截器
? ? ? ?AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
? ? ? ?interceptor.configure(executor, exceptionHandler);
? ? ? ?return interceptor;
? ?}
}
AnnotationAsyncExecutionInterceptor繼承自AsyncExecutionInterceptor,間接實現(xiàn)了MethodInterceptor。該攔截器的實現(xiàn)的invoke方法把原來方法的調(diào)用提交到新的線程池執(zhí)行,從而實現(xiàn)了方法的異步。
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor, Ordered {
? ?public Object invoke(final MethodInvocation invocation) throws Throwable {
? ? ? ?//...
? ? ? ?//構(gòu)建放到AsyncTaskExecutor執(zhí)行Callable Task
? ? ? ?Callable<Object> task = () -> {
? ? ? ? ? ?//...
? ? ? ?};
? ? ? ?//提交到新的線程池執(zhí)行
? ? ? ?return doSubmit(task, executor, invocation.getMethod().getReturnType());
? ?}
}
由上面分析可以看到,@Async注解其實是通過代理的方式來實現(xiàn)異步調(diào)用的。
那使用@Async有什么要注意的呢?
1.使用@Aysnc的時候最好配置一個線程池Executor以讓線程復(fù)用節(jié)省資源,或者為SimpleAsyncTaskExecutor設(shè)置基于線程池實現(xiàn)的ThreadFactory,在否則會默認使用SimpleAsyncTaskExecutor,該executor會在每次調(diào)用時新建一個線程。
2.調(diào)用本類的異步方法是不會起作用的。這種方式繞過了代理而直接調(diào)用了方法,@Async注解會失效。
最后給大家分享一個Github倉庫,上面有大彬整理的300多本經(jīng)典的計算機書籍PDF,包括C語言、C++、Java、Python、前端、數(shù)據(jù)庫、操作系統(tǒng)、計算機網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)和算法、機器學(xué)習(xí)、編程人生等,可以star一下,下次找書直接在上面搜索,倉庫持續(xù)更新中~

