1. 程式人生 > >Spring Aop底層原理詳解(利用spring後置處理器實現AOP)

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有了一個更深層次的瞭解呢!希望對大家有所啟發,謝謝!

【原始碼地址:】https://github.com/baomw/spring-customized.git