概述
Spring的切面(Spring動態代理)在Spring中應用十分廣泛,例如還有事務管理,重試等等。網上介紹SpringAop源碼很多,這里假設你對SpringAop有基本的了解。如果你認為Spring代理類會創建多重代理,那說明你真的沒了解。
需求背景
假設我現在想提供一個jar包,這個jar包會攔截制定注解方法,並做一些記錄。這里要分析一下具體需求
攔截的注解是在方法上
如果注解是放在方法上,那么我們完全可以使用SpringAop的方式,通過自己定義一個注解,並對該注解進行攔截即可。這個不是本篇文章的重點,攔截制定注解的很簡單。
攔截的注解是在類上
如果我們需要攔截的注解是類上,這里首先介紹第一個接口
Advisor
public interface Advisor { Advice getAdvice(); boolean isPerInstance(); }
這個接口簡單來說就是持有一個通知(Advice),但是一般不直接使用這個接口,因為這個對通知沒有一個有效的過濾(當然如果你想對所有方法都進行攔截),所以一般是使用PointcutAdvisor,
public interface PointcutAdvisor extends Advisor { Pointcut getPointcut(); }
這個接口就可以通過Pointcut做一些限制。為什么呢? 直接看AopUtils#findAdvisorsThatCanApply方法,這個方法就是從所有Advisor接口實現Bean選擇對某個Class有效。
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor) { // already processed continue; } //!!!有興趣的可以重點看一下重點看這里實現,就不大段貼代碼了 if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }
好了,我們知道了,要想讓我們的自定義的切面生效,首先,
第一步,創建一個Advisor
public class MyAdvisor implements PointcutAdvisor { //這個是Spring提供的一個簡單的通過注解來獲取切點類 AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(MyAop.class); //這個是我們自己實現的繼承於Advice, MyAdvice myAdvice =new MyAdvice(); @Override public Pointcut getPointcut() { return annotationMatchingPointcut; } @Override public Advice getAdvice() { return myAdvice; } @Override public boolean isPerInstance() { return false; } //簡單起見,就直接使用MethodInterceptor class MyAdvice implements MethodInterceptor{ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("haha"); return invocation.proceed(); } }
第二步,定義一個開關注解,讓我們的自定義切面生效
創建一個開關注解,就是一個套路,先創建一個開關注解,@import我們一個Bean注冊類
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(MyImporter.class) public @interface EnableMyAop { }
在Bean注冊類中把我們的MyAdvisor注冊進去吧
public class MyImporter implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { register(registry,MyAdvisor.class); } private void register(BeanDefinitionRegistry registry, Class<?> aopBeanFactoryPostClass) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(); rootBeanDefinition.setBeanClass(aopBeanFactoryPostClass); registry.registerBeanDefinition(aopBeanFactoryPostClass.getSimpleName(),rootBeanDefinition); } }
至此,只要使用MyAop注解的類,在調用他的任何方法的時候(類內部調用不算),都會進入到我們的切面中。
小結
本篇文章是簡單看了一下Spring Aop源碼后,嘗試自己進行擴展,因此可能不是最優的方法。不過希望能起到拋磚迎玉作用,在其基礎上可以做很多有很意思的事情。如果有興趣可以進一步研究spring自己的Advice,Pointcut的實現類。