Spring Aop底層原理詳解(利用spring後置處理器實現AOP)
寫在前面:對於一個java程式設計師來說,相信絕大多數都有這樣的面試經歷,面試官問:你知道什麼是aop嗎?談談你是怎麼理解aop的?等等諸如此類關於aop的問題。當然對於一些小白可能會一臉懵逼;對於一些工作一兩年的,可能知道,哦!aop就是面向切面變成,列印日誌啊,什麼什麼的,要是有點學習深度的呢可能會說aop底層實現利用了jdk動態代理,cglib啊什麼的。很多時候可能面試就到此打住了,當然,然後也就沒有然後了(客氣點的來句:回頭有訊息我會通知你的!)。
今天,通過這篇文章,我想帶大家先了解下什麼是spring後置處理器,然後利用spring的後置處理器我們自己來手寫一個springAop,來完成和springAop一樣的功能!讓你可以對你的面試官說:你精通AOP!
在開始之前呢,我想先引入一個概念:spring擴充套件點和後置處理器
我們知道springIoc可以對我們應用程式中的java物件做一個集中化的管理,從而使我們從繁瑣的new Object();中解脫出來。
其核心思想呢就是先創造出一個bean工廠,也就是我們的beanFactory,通過beanFactory來生產出我們應用程式中所需要的java物件,也就是我們的java bean。
今天呢我跟大家介紹的後置處理器呢,有三個
BeanFactoryPostProcessor : 可以插手beanFactory的生命週期
BeanPostProcessor :可以插手bean的生命週期
ImportSelector :藉助@Import註解,可以動態實現將一個類是否交由spring管理,常用作開關操作
1,BeanFactoryPostProcessor(BeanDefinitionRegistryPostProcessor 有興趣的可以自行查閱spring原始碼)
可以看到,該介面只定義了一個方法,具體實現的執行時機呢,我們可以通過spring原始碼得知:
在我們AnnotationConfigApplicationContext.refresh();的時候,從下原始碼可知,在我們beanFactory被創建出來後,相關準備工作做完後,會去執行invokeBeanFactoryPostProcessors(beanFactory);也就是去執行我們的BeanFactoryPostProcessor
從上面兩處程式碼可以看出,spring在執行
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
的時候,會傳入一個List beanFactoryPostProcessors;然後迴圈去執行list裡面所有實現了
BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor的物件的相關方法(spring處理邏輯比較嚴謹,我這只是大致描述,如想深入瞭解細節可以參考spring原始碼)。
2,BeanPostProcessor:可以看出,該介面定義了兩個方法,分別在bean例項化之後放到我們的容器之前和之後去執行,方法的返回值為一個object,這個object呢就是我們存在於容器的物件了(所以這個位置我們是不是可以對我們的bean做一個動態的修改,替換等等操作,所以這也是我們spring的擴充套件點之一,後面結合我麼自己手寫aop來詳細講解這個擴充套件點的應用)
3, ImportSelector
在講ImportSelector之前呢,我想先講一下@Import這個註解。在spring處理我們的java類的時候,會分成四種情況去處理
1)普通類:就是我們家裡@Component,@Service,@Repository等等的類
2)處理我們的import進來的類:
這裡呢,又分為三種情況:
a)import一個普通類:@Import(A.class)
b)import一個Registrar:比如我們的aop @Import(AspectJAutoProxyRegistrar.class)
c)import一個ImportSelector:具體妙用見下文
至於spring在什麼時候處理的呢,我大致敘述一下,有興趣的可以自己去研究下spring原始碼:
對於普通類,spring在doScan的時候,就將掃描出來的java類轉換成我們的BeanDefinition,然後放入一個BeanDefinitionMap中去
對於@import的三種情況,處理就在我們的ConfigurationClassPostProcessor(該類是我們BeanDefinitionRegistryPostProcessor後置處理器的一個實現,同時這也是我們spring內部自己維護的唯一實現類(排除內部類)),具體處理import的核心程式碼如下,if-else 很容易可以看出spring對於我們import三種類型的處理。
/**
* 處理我們的@Import註解,注意我們的@Import註解傳入的引數,可能有三種類型
* 1,傳入一個class類,直接解析
* 2,傳入一個registrar,需要解析這個registrar
* 3,傳入一個ImporterSelector,這時候會去解析ImporterSelector的實現方法中返回的陣列的class
*
*/
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
}
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//處理我們的ImportSelector
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
//注意可能我們ImportSelector傳入的類上還有可能會Import,所以這裡,spring採用了
//一個遞迴呼叫,解析所有的import
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
//處理我們的Registrar
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
//新增的一個和Importselector方式不同的map中,sprig對兩種方式傳入的類註冊方式不同
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
//最後如果是普通類,傳入importStack後交由processConfigurationClass進行註冊處理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
4,實現思路:在我們開發過程中,無非就是明確需求,列出需要解決的問題,然後針對問題,找出解決方案就OK!
同樣根據如上原理,下面我們便可以來模擬我們的springAop,如果有點基礎的可能應該會知道,spring是基於我們的動態代理實現的(先不考慮是cglib還是jdk動態代理),結合我們aop使用(沒用過的好去百度了),那麼我們就需要解決如下幾個問題:
a)我們知道開啟和關閉aop需要註解@EnableAspectJAutoProxy,如何實現,結合上文,我們可以使用@import(ImportSelector.class)
b)如何確定代理關係,即哪些是我們需要代理的目標物件和其中的目標方法,以及哪些方法是要增強到目標物件的目標方法上去的?
c)如何實現目標物件的替換,就是我們在getBean的時候,如何根據目標物件來獲取到我們增強後的代理物件?
如上問題都解決了,那麼也就實現了我們的AOP.
5,根據如上思路,構建工程如下:
具體工程說明如下:
annotation:放我們所有的自定義註解
holder:自定義資料結構,具體類後面說
processor:放我們所有的後置處理器及代理相關
selector:放我們的ImportSelector的實現
util:工具類
要模擬aop,那麼我們就要結合我們怎麼去使用aop:
對於AOP,我們知道有一個開關注解類 @EnableAspectJAutoProxy(同樣我們定義個@EnableAop),
註解@Aspect,@Before,@After。。。(注意這些都不是spring的註解,是Aspectj的註解,只是我們spring直接引用了而已,同樣我們也對於新建自定義註解@AopJ,@BeforeBaomw,@AfterBaomw,@AfterBaomw。。。)
針對問題2,由於BeanFactoryPostProcessor的所有實現會在beanFactory完成對由於bean的掃描後,在例項化之前執行,所以我們可以新建一類,實現這個介面,然後實現方法裡面主要完成對有BeanDefinition的掃描,找出我們所有的通知類,然後迴圈裡面的方法,找到所有的通知方法,然後根據註解判斷切入型別(也就是前置,後置還是環繞),最後解析註解的內容,掃描出所有的目標類,放入我們定義好的容器中。
具體實現如下:
(1)定義holder,用於描述通知資訊
/**
* 描述:
* 自定義資料結構
*
* @author baomw
* @create 2018-11-19 下午 4:56
*/
public class ProxyBeanHolder {
//通知類名稱
private volatile String className;
//通知方法名稱
private volatile String methodName;
//註解類名稱
private volatile String annotationName;
...
< get and setter>
}
(2)定義資料工具類,具體作用見註釋
/**
* 描述:
*
* @author baomw
* @create 2018-11-19 下午 1:48
*/
public class ConfigurationUtil {
/**
* aop標識註解類
*/
public static final String AOP_POINTCUT_ANNOTATION
= "com.baomw.annotation.AopJ";
/**
* 前置通知註解類
*/
public static final String BEFORE
= "com.baomw.annotation.BeforeBaomw";
/**
* 後置通知註解類
*/
public static final String AFTER
= "com.baomw.annotation.AfterBaomw";
/**
* 環繞通知註解類
*/
public static final String AROUND
= "com.baomw.annotation.AroundBaomw";
/**
* 存放需代理的全部目標物件類
*/
public static volatile Map<String,List<ProxyBeanHolder>> classzzProxyBeanHolder = new ConcurrentHashMap<>();
}
(3)定義我們的註冊類,用於註冊我們的目標物件和通知物件之間的關係,其核心程式碼如下,首先實現BeanFactoryPostProcessor ,保證其實在對所有的bean完成掃描後,在bean的例項化之前執行,然後再其中按上述思路,scan出所有的目標物件,然後建立起目標物件和通知物件的關聯關係,然後放入我們的Map中
/**
* 描述:
*
* @author baomw
* @create 2018-11-19 下午 1:59
*/
@Component
public class RegisterBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
/**
* 存放需要代理的相關資訊類
*/
public static volatile List<ProxyBeanHolder> roxyBeanHolderList = new Vector<>();
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//獲取所有的bdName
String[] beanDefinitionNames = configurableListableBeanFactory.getBeanDefinitionNames();
for (String beanDefinitionName:beanDefinitionNames){
BeanDefinition beanDefinition
= configurableListableBeanFactory.getBeanDefinition(beanDefinitionName);
//判斷bd是否是一個註解bd
if (beanDefinition instanceof AnnotatedBeanDefinition) {
//取得bd上的所有註解
AnnotationMetadata metadata = ((AnnotatedBeanDefinition) beanDefinition).getMetadata();
Set<String> Annotations = metadata.getAnnotationTypes();
//迴圈所有註解,找到aop切面註解類
for (String annotation:Annotations)
if (annotation.equals(ConfigurationUtil.AOP_POINTCUT_ANNOTATION))
doScan((GenericBeanDefinition)beanDefinition);
}
}
}
如此問題二就得到了完美 的解決
針對問題3,我們可以利用BeanPostProcessor,在bean例項化之後,在放入容器之前,進行一個條件過濾,如果當前物件是我們的目標物件(即在我們定義好的Map中),則對物件進行代理,將目標物件替換成代理物件返回即可
(注:spring實現aop採用cglib和jdk動態代理兩種方式,@EnableAspectJAutoProxy(proxyTargetClass=true)可以加開關控制,如果不加,目標物件如果有實現介面,則使用jdk動態代理,如果沒有就採用cglib(因為我們知道cglib是基於繼承的))
我們這裡實現,都簡單粗暴一點,統一採用cglib代理,這樣就可以完成對任意物件的代理了。
具體實現如下:
/**
* 描述:
* aop實現核心處理類
*
* @author baomw
* @create 2018-11-18 下午 11:24
*/
public class RealizedAopBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
String targetClass = bean.getClass().getName();
Object object = bean;
if (ConfigurationUtil.classzzProxyBeanHolder.containsKey(targetClass)){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(new CustomizedProxyInterceptor(ConfigurationUtil.classzzProxyBeanHolder.get(targetClass)));
object = enhancer.create();
}
return object;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
如此我們第三個問題也就順利解決了最後,還剩下我們的問題1,這時候就可以引出我們的@import(ImportSelector.class)了
ImportSelector 介面有一個實現方法,返回一個字串型別的陣列,裡面可以放類名,在@import(ImportSelector.class)的時候,spring會把我們返回方法裡面的類全部註冊到BeanDefinitionMap中,繼而將物件註冊到Spring容器中
/**
* 描述:
* 自定義aop實現,提交給spring處理的類
*
* @author baomw
* @create 2018-11-18 下午 11:29
*/
public class CustomizedAopImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{RealizedAopBeanPostProcessor.class.getName(),RegisterBeanFactoryPostProcessor.class.getName()};
}
}
/**
* 描述:
* aop開關注解
*
* @author baomw
* @create 2018-11-18 下午 11:21
*/
@Retention(RetentionPolicy.RUNTIME)
@Import(CustomizedAopImportSelector.class)
public @interface EnableAop {
}
很明顯,如果我的Appconfig上加了@EnableAop註解,則會將我們的後置處理器的實現類交給了spring管理,spring才能去掃描得到這個類,才能去執行我們的自定義的後置處理器裡面的方法,才能實現我們的aop的代理,因此,我們的開關也就順利完成了。下面編寫測試方法
定義我們的通知,對我們com.baomw.dao下面的所有類的所有方法進行代理
可以看到,已經對我們dao下面的所有方法完成了代理,由此,我們便已經完成了我們的SpringAOP了,只不過我這aop是一個山寨版的,功能比較簡單,但是具體的實現原理是跟springaop的實現大相庭徑的,spring處理的邏輯更縝密嚴謹(畢竟是大師和小菜雞的區別,你們懂的!)
下面我們不妨來看看spring是怎麼來實現aop的:
(1)這裡和我們一樣,也就是我們的開關介面,不過他@Import進來的是一個registrar,前面我已經提到過,sping是可以對我們import進來的registrar進行掃描註冊的
(2)下面為我們aop實現BeanPostProcesser實現的postProcessBeforeInstantiation方法,可以看到,他也是判斷bean是否在
Set targetSourcedBeans裡面,如果在呢,就取到targetSource,然後createProxy,建立我們的代理物件。最後將我們的代理物件返回出去。
可見,其實現跟我們自己實現aop的思路一模一樣,只是spring處理的更嚴謹而已
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// Create proxy here if we have a custom TargetSource.
// Suppresses unnecessary default instantiation of the target bean:
// The TargetSource will handle target instances in a custom fashion.
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
return null;
}
到這裡,你是不是對springaop有了一個更深層次的瞭解呢!希望對大家有所啟發,謝謝!