接上篇【熟練掌握spring框架第二篇】
bean的生命周期
參考:http://javainsimpleway.com/spring-bean-life-cycle/
這是一個(gè)比較基礎(chǔ)但是又比較高頻的面試題歼郭。如果面試官問(wèn)你spring bean的生命周期都有哪些含滴?那應(yīng)該怎樣回答呢虽另?在回答之前可以先分析一下這個(gè)題目。首先想想面試官問(wèn)這個(gè)問(wèn)題的目的是什么谢床?換位思考兄一,如果我是面試官,我希望通過(guò)這個(gè)題目了解求職者對(duì)spring
框架的了解程度识腿,它是如何管理bean
的出革。在整個(gè)bean
對(duì)生命周期中都有哪些是我們可以參與的。常用的場(chǎng)景是什么渡讼?不同類型的bean
的生命周期有什么不同嗎骂束?如果求職者這幾個(gè)問(wèn)題都能清楚的表示出來(lái),那我認(rèn)為這道面試題他pass了成箫。學(xué)習(xí)bean的生命周期目的還是為了在實(shí)際工作中可以進(jìn)行自由擴(kuò)展展箱。以滿足業(yè)務(wù)需要。那下面就從這幾個(gè)方面分析下bean
的生命周期蹬昌。
先看下下面這張圖混驰,來(lái)源:http://javainsimpleway.com/spring-bean-life-cycle/
- 首先實(shí)例化
bean
populateBean
- 調(diào)用初始化方法之前首先調(diào)用所有
bean
的有感知
的方法,包括BeanNameAware
皂贩,BeanClassLoaderAware
栖榨,BeanFactoryAware
。 - 然后執(zhí)行
BeanPostProcessor
的postProcessBeforeInitialization
- 執(zhí)行初始化方法明刷,如果bean實(shí)現(xiàn)了
InitializingBean
會(huì)調(diào)用他的afterPropertiesSet
方法婴栽。比如之前提到的RepositoryFactoryBeanSupport
就通過(guò)afterPropertiesSet
進(jìn)行repository
的創(chuàng)建。 - 反射調(diào)用自定義
init-method
方法辈末。 - 然后執(zhí)行
BeanPostProcessor
的postProcessAfterInitialization
其中當(dāng)執(zhí)行到ApplicationContextAwareProcessor
的postProcessBeforeInitialization
時(shí)愚争,調(diào)用bean
的應(yīng)用級(jí)的有感知
的方法。比如ApplicationContextAware
挤聘,EnvironmentAware
這些轰枝。
我們熟悉的BeanPostProcessor
還有AutowiredAnnotationBeanPostProcessor
,用來(lái)進(jìn)行屬性自動(dòng)裝配组去。
RequiredAnnotationBeanPostProcessor
狸膏,它可以確保聲明"必需"屬性的bean實(shí)際上已配置了值,否則就會(huì)爆出類似下面這樣的錯(cuò)誤
image-20210506213820813
CommonAnnotationBeanPostProcessor
處理@PostConstruct
和@PreDestroy
添怔,執(zhí)行@PostConstruct
的邏輯是在它的父類InitDestroyAnnotationBeanPostProcessor
的postProcessBeforeInitialization
里進(jìn)行的湾戳。執(zhí)行@PreDestroy
的邏輯是在InitDestroyAnnotationBeanPostProcessor
的postProcessBeforeDestruction
里進(jìn)行的。
所以@PostConstruct
執(zhí)行的時(shí)候广料,bean
的屬性已經(jīng)裝填完成了砾脑。并且只會(huì)被執(zhí)行一次,可以執(zhí)行一些需要依賴項(xiàng)的初始化工作艾杏。
@PreDestroy
的原理是利用了jdk的shutdown hook
韧衣,可以實(shí)現(xiàn)應(yīng)用程序的優(yōu)雅關(guān)閉。注意shutdown hook
不應(yīng)該執(zhí)行耗時(shí)的操作购桑,這樣會(huì)導(dǎo)致程序不能正常退出畅铭。一般運(yùn)維寫腳本的時(shí)候都會(huì)設(shè)置一個(gè)超時(shí)時(shí)間,一旦超過(guò)勃蜘,就使用kill -9
強(qiáng)制退出硕噩。
Spring管理的Bean默認(rèn)是單例的。bean的所有scope有如下這些
image-20210507120640098
來(lái)源:spring官方文檔
request
session
application
只存在于web應(yīng)用上下文中缭贡。websocket
存在websocket環(huán)境中炉擅。這些本文不做詳細(xì)描述,singleton
詳細(xì)讀者已經(jīng)很熟悉了阳惹,那么我們著重關(guān)注下prototype
這個(gè)類型谍失。
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class A {
}
定義一個(gè)簡(jiǎn)單的類,聲明為scope為prototype
莹汤。spring啟動(dòng)后調(diào)用applicationContext.getBean("a")
快鱼,代碼流程大致如下。
- 調(diào)用
AbstractBeanFactory
的doGetBean
方法 - 判斷如果原型bean正在創(chuàng)建則直接拋出異常纲岭。
- 拿到相應(yīng)的
BeanDefinition
抹竹,判斷如果是Prototype
類型 - 調(diào)用
beforePrototypeCreation
標(biāo)記正在創(chuàng)建 -
createBean
創(chuàng)建bean
,和創(chuàng)建單例bean
是同一個(gè)方法荒勇。 - 調(diào)用
afterPrototypeCreation
清除標(biāo)記
所以prototype
類型的bean是不支持循環(huán)依賴的柒莉。另外由于和創(chuàng)建singleton
的bean
是同一個(gè)方法,所以bean
的所有有感知的方法
也都是差不多的沽翔。一個(gè)很重要的不同就是原型bean
的@PreDestroy
是不會(huì)執(zhí)行的兢孝。原因很簡(jiǎn)單destroy方法是通過(guò)shutdownhook調(diào)用beanFactory
的destroySingletons
方法實(shí)現(xiàn)的。spring沒有定義prototype
bean的銷毀動(dòng)作仅偎。
更多詳細(xì)的解釋可以參考:https://bluebreeze0812.github.io/learn/2019/10/17/Spring-Destroy-Prototype-Beans/
spring 動(dòng)態(tài)代理與AOP
代理模式
代理模式是GoF
23種Java常用設(shè)計(jì)模式之一跨蟹,隸屬于結(jié)構(gòu)型模式。一個(gè)隨處可見的應(yīng)用場(chǎng)景就是rpc框架
比如dubbo里面的service調(diào)用橘沥。本地調(diào)用的service實(shí)際上是遠(yuǎn)程對(duì)象的代理對(duì)象窗轩。調(diào)用代理對(duì)象的方法實(shí)際是調(diào)用了遠(yuǎn)程對(duì)象的方法。又比如 JAVA RMI
座咆,當(dāng)然了對(duì)遠(yuǎn)程代理這里不做過(guò)多描述痢艺。今天我們要講的是spring
的動(dòng)態(tài)代理仓洼。眾所周知,Spring代理實(shí)際上是對(duì)JDK代理
和CGLIB
代理做了一層封裝堤舒。那么我們先來(lái)看下jdk和cglib代理色建。這也是烹飪spring aop
這道大菜比不可少的佐料。
JDK動(dòng)態(tài)代理
public class JdkProxyDemo {
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
public static class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
}
public static class ProxyFactory implements InvocationHandler {
private final Calculator real;
public ProxyFactory(Calculator real) {
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object result = method.invoke(real, args);
System.out.println("after");
return result;
}
}
public static void main(String[] args) {
Calculator real = new CalculatorImpl();
ProxyFactory proxyFactory = new ProxyFactory(real);
Calculator proxy = (Calculator) Proxy.newProxyInstance(real.getClass().getClassLoader(), new Class[]{Calculator.class}, proxyFactory);
System.out.println(proxy.add(1, 2));
System.out.println(proxy.subtract(2, 1));
}
}
由上面這個(gè)簡(jiǎn)單的例子可以總結(jié)出jdk動(dòng)態(tài)代理
有如下特點(diǎn)舌缤。
- 創(chuàng)建代理對(duì)象需要三要素:類加載器箕戳,代理對(duì)象需要實(shí)現(xiàn)的接口列表。
InvocationHandler
實(shí)例国撵。
- 代理對(duì)象的class是
com.sun.proxy.$Proxy0
實(shí)現(xiàn)了Calculator
接口 - 代理對(duì)象持有
InvocationHandler
實(shí)例的引用陵吸,而InvocationHandler
持有被代理對(duì)象的引用。 -
InvocationHandler
的invoke方法代理了接口的所有方法介牙。你可以在被代理對(duì)象執(zhí)行前后添加邏輯壮虫,你甚至不調(diào)用代理對(duì)象的方法都可以。 - 代理對(duì)象需要實(shí)現(xiàn)的接口列表是必須的耻瑟。這也是jdk動(dòng)態(tài)代理最大的特點(diǎn)旨指。代理對(duì)象和被代理對(duì)象都實(shí)現(xiàn)了共同的接口。否則是無(wú)法代理的喳整。
cglib動(dòng)態(tài)代理
字節(jié)碼生成類庫(kù)谆构,它封裝了ASM
,它是一個(gè)字節(jié)碼操作框架框都,類似的框架還有javaassit
搬素,大概原理就是解析.class
文件然后動(dòng)態(tài)修改它。
public class CglibProxyDemo {
public static class Calculator {
public int add(int a, int b) {
return a + b;
}
}
public static class CalculatorInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("before add");
Object o1 = methodProxy.invokeSuper(o, objects);
System.out.println("after add");
return o1;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Calculator.class);
enhancer.setCallback(new CalculatorInterceptor());
Calculator calculator = (Calculator) enhancer.create();
System.out.println(calculator.add(1, 2));
}
}
由上面這個(gè)簡(jiǎn)單的例子我們可以總結(jié)出cglib動(dòng)態(tài)代理有如下特點(diǎn):
- 生成的代理對(duì)象的class是
com.family.spring.core.CglibProxyDemo$Calculator$$EnhancerByCGLIB$$b4da3734
- 它是
Calculator
類的子類魏保。遵循繼承規(guī)則熬尺,子類不能覆蓋父類的私有方法。也就是說(shuō)私有方法是不能被代理的谓罗。 -
MethodInterceptor
定義了一個(gè)方法攔截器粱哼。這個(gè)攔截器會(huì)攔截代理類的所有可以代理的方法。你也可以決定是否調(diào)用父類真實(shí)的方法檩咱。 - cglib代理和jdk代理有兩個(gè)很重要的區(qū)別揭措,第一就是不需要共同的接口,第二不需要準(zhǔn)備一個(gè)被代理的對(duì)象刻蚯。
如果讀者對(duì)于代理的class結(jié)構(gòu)到底是什么樣感興趣的話绊含。也可以使用java代理技術(shù)讀取jvm里面相應(yīng)的class文件,進(jìn)行分析炊汹。
spring動(dòng)態(tài)代理
為什么需要AOP
軟件開發(fā)是一個(gè)演變的過(guò)程躬充,從最初的POP(面向過(guò)程程序設(shè)計(jì))到OOP(面向?qū)ο蟪绦蛟O(shè)計(jì))再到AOP(面向切面編程),未來(lái)可能還有一堆的OP,每種編程思想都是軟件開發(fā)進(jìn)化的產(chǎn)物充甚。都是為了解決特定的問(wèn)題應(yīng)運(yùn)而生的以政。那么AOP產(chǎn)生的背景是什么呢。我認(rèn)為隨著軟件系統(tǒng)的復(fù)雜化津坑,一些與核心業(yè)務(wù)邏輯無(wú)關(guān)的內(nèi)容越來(lái)越多妙蔗。比如:記錄日志,權(quán)限驗(yàn)證疆瑰,事務(wù)控制,錯(cuò)誤信息檢測(cè)昙啄。而這些邏輯又散落在程序的每一個(gè)地方穆役。這樣不僅會(huì)增加寫代碼的復(fù)雜性和工作量,還會(huì)大大增加代碼的維護(hù)成本梳凛。比如權(quán)限驗(yàn)證耿币,如果每個(gè)接口都手寫代碼去判斷當(dāng)前用戶是否有該接口的訪問(wèn)權(quán)限的話,那真的很蛋疼韧拒。所以聰明的程序員們就想把這些代碼放到同一個(gè)地方淹接,然后采取動(dòng)態(tài)植入的方式添加到業(yè)務(wù)代碼執(zhí)行前后,這樣代碼統(tǒng)一起來(lái)了叛溢,而且業(yè)務(wù)邏輯里面幾乎看不到添加的代碼塑悼,程序員就可以專心致志的進(jìn)行CRUD
了,這種設(shè)計(jì)思想有個(gè)高大上的名字就是AOP
楷掉,英文全稱是Aspect Oriented Programming
厢蒜,維基百科管這個(gè)叫編程范式。為了讓這個(gè)設(shè)計(jì)理念更加專業(yè)化烹植,還特地引入一堆的專業(yè)術(shù)語(yǔ)斑鸦。下面就簡(jiǎn)單闡述下每個(gè)術(shù)語(yǔ)的含義。
術(shù)語(yǔ) | 含義 |
---|---|
通知 Advice | 類似于前面說(shuō)的權(quán)限驗(yàn)證草雕,springaop 支持的通知有:前置通知巷屿,后置通知,異常通知墩虹,最終通知嘱巾,環(huán)繞通知五種 |
連接點(diǎn) JoinPoint | 就是允許使用通知的地方,比如說(shuō)方法連接點(diǎn)(方法執(zhí)行前后)败晴,異常連接點(diǎn)(拋出異常時(shí))等 |
切點(diǎn) Pointcut | 織入通知的連接點(diǎn)就叫做切點(diǎn)浓冒。 |
切面 Aspect | 切面就是通知和切點(diǎn)的結(jié)合,兩者組合一起定義了切面三要素:要做什么 尖坤,何時(shí)做 稳懒,何地做 。 |
織入 weaving | 把切面應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程 |
有了上面的概念理解,我們對(duì)spring aop
仍然是理論層面的场梆。那么他的實(shí)現(xiàn)是怎樣的呢墅冷。下面就以一個(gè)簡(jiǎn)單的例子一探究竟。
核心代碼:
@Aspect
@Component
public class MonitorAspect {
@Pointcut("execution(* com.family.spring.core..*.*(..)) ")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = pjp.proceed();
stopWatch.stop();
System.out.println("執(zhí)行" + pjp.getSignature().getName() + "共花費(fèi)了" + stopWatch.getTotalTimeMillis() + "毫秒");
return result;
}
}
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAopDemoApplication implements ApplicationRunner {
@Autowired
private ApplicationContext applicationContext;
public static void main(String[] args) {
SpringApplication.run(SpringAopDemoApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
UserService userService = (UserService) applicationContext.getBean("userService");
userService.login();
userService.register();
}
}
//userService很簡(jiǎn)單或油,就定義了兩個(gè)方法: login register
程序輸出是這樣的:
執(zhí)行l(wèi)ogin共花費(fèi)了1000毫秒
執(zhí)行register共花費(fèi)了2000毫秒
執(zhí)行run共花費(fèi)了3009毫秒
分析:getBean拿到的userService
肯定是代理之后的對(duì)象寞忿。那它是什么時(shí)候被代理的呢。debug發(fā)現(xiàn)在執(zhí)行bean
的初始化時(shí)顶岸,會(huì)調(diào)用所有的BeanPostProcessor
逐個(gè)處理腔彰。其中有一個(gè)特別的Processor
是:AnnotationAwareAspectJAutoProxyCreator
,而這個(gè)processor
就是@EnableAspectJAutoProxy
引入的辖佣。打開注解 @EnableAspectJAutoProxy
的源碼發(fā)現(xiàn)霹抛,它的核心是導(dǎo)入了一個(gè)AspectJAutoProxyRegistrar
(AspectJ自動(dòng)代理登記員)的類。而這個(gè)類的作用就是往注冊(cè)中心注冊(cè)AnnotationAwareAspectJAutoProxyCreator
這個(gè)BeanPostProcessor
卷谈。是不是和之前說(shuō)的@EnableJpaRepositories
如出一轍杯拐。線索找到了,接下來(lái)就是解刨它的postProcessAfterInitialization
方法了世蔗。
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
//wrapIfNecessary就是用來(lái)生成代理對(duì)象的端逼。
繼續(xù)跟進(jìn),終于找到了進(jìn)行對(duì)象代理的罪魁禍?zhǔn)琢宋哿堋>褪俏覀兊?code>ProxyFactory 了
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
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);
}
return proxyFactory.getProxy(getProxyClassLoader());
這是spring對(duì)jdk和cglib動(dòng)態(tài)代理的一個(gè)封裝類顶滩。它的getProxy
里的createAopProxy
方法是這樣的。
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!IN_NATIVE_IMAGE &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
翻譯成自然語(yǔ)言就是optimize
芙沥,proxyTargetClass
诲祸,被代理的類沒有接口這三個(gè)條件其中任何一個(gè)成立,就有機(jī)會(huì)走cglib動(dòng)態(tài)代理而昨,否則都是走jdk動(dòng)態(tài)代理救氯。另外就算判斷有機(jī)會(huì)走cglib的話,如果目標(biāo)類是接口還是會(huì)走jdk動(dòng)態(tài)代理歌憨。下面看下sping aop中關(guān)于切面的抽象
使用ProxyFactory
代理對(duì)象着憨,是必須要添加通知的。如果沒有通知就好比代理對(duì)象收了錢务嫡,但是啥事也沒干甲抖。一種簡(jiǎn)單的添加方式是,傳入一個(gè)MethodInterceptor
心铃,實(shí)現(xiàn)攔截准谚。
proxyFactory.addAdvice((MethodInterceptor) invocation -> {
System.out.println("before");
Object result = invocation.proceed();
System.out.println("after");
return result;
});
但是更高級(jí)的方式就是添加Advisor
,可以翻譯為顧問(wèn)去扣,讓顧問(wèn)告訴我通知是什么柱衔?spring內(nèi)置了一個(gè)強(qiáng)大的顧問(wèn),名為InstantiationModelAwarePointcutAdvisorImpl
,它的getAdvice
方法唆铐,可以動(dòng)態(tài)的返回不同類型的通知哲戚。詳見:ReflectiveAspectJAdvisorFactory
的getAdvice
方法。前面說(shuō)的那個(gè)BeanPostProcessor
正是添加了這個(gè)顧問(wèn)實(shí)現(xiàn)了環(huán)繞通知艾岂。
未完待續(xù)顺少,更多內(nèi)容請(qǐng)關(guān)注【熟練掌握spring框架】第四篇