1、Spring选择代理
1.1、Aspect和Advisor
在Spring框架中,"Aspect" 和 "Advisor" 是两个关键的概念,它们都与AOP(面向切面编程)密切相关:
如果要在Spring中定义一个Aop类,通常会:
@Component
@Aspect
public class MyAspect {@Before("execution()")public void before(){System.out.println("before");}@After("execution()")public void after(){System.out.println("after");}}
这意味着可以有多个通知和切点。
而Advisor是Advice的底层实现,Spring在执行Aop时,会将被@Aspect 注解修饰的类中的通知和切点拆分成多个Advisor,一个Advisor只能包含一个通知+一个切点。
1.2、模拟Advisor的实现
要模拟Advisor的实现,首先需要准备切点,我们首先看下PointCut接口:
public interface Pointcut {Pointcut TRUE = TruePointcut.INSTANCE;ClassFilter getClassFilter();MethodMatcher getMethodMatcher();
}
- getClassFilter()方法返回一个ClassFilter对象,用于过滤类匹配的规则。ClassFilter用于确定哪些类应该应用横切逻辑。
- getMethodMatcher()方法返回一个MethodMatcher对象,用于确定哪些方法应该应用横切逻辑。MethodMatcher用于匹配类中的方法。
PointCut接口也有很多实现类,典型的有根据注解进行切点匹配,以及根据包名、类名、参数等进行匹配。(个人喜好根据注解进行匹配,更加灵活)
本次使用AspectJExpressionPointcut实现类进行演示:
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
然后准备通知,我们使用org.aopalliance.intercept包下的MethodInterceptor接口(和CGLIB的MethodInterceptor同名),本质上是环绕通知:
MethodInterceptor interceptor = new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("before");Object proceed = methodInvocation.proceed();System.out.println("after");return proceed;}
};
还需要准备切面:
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
最后创建代理,使用ProxyFactory代理工厂类进行创建,它会自动选择代理模式:(此时使用的理论上应该是JDK动态代理,因为目标类Target实现了接口)
ProxyFactory factory = new ProxyFactory();
factory.addAdvisor(advisor);
factory.setTarget(new Target());
Target proxy = (Target) factory.getProxy();
然而结果却是使用了CGLIB进行增强,这是什么原因?
1.3、选择代理的时机
ProxyFactory代理工厂类选择何种代理方式,除了取决于目标类是否实现了接口,还与ProxyConfig的private boolean proxyTargetClass = false; 成员变量有关,一般有以下的情况:
- proxyTargetClass = false 目标实现了接口,使用jdk实现
- proxyTargetClass = false 目标没有实现接口,使用cglib实现
- proxyTargetClass = true 无论目标是否实现接口,都是使用cglib实现
1.4、切点匹配
我现在有三个类,一个接口,分别是:
public class F1 {public void foo() {System.out.println("foo");}@Transactionalpublic void bar() {}
}
@Transactional
public class F2 {public void foo() {System.out.println("foo");}public void bar() {}
}
@Transactional
interface F3 {void foo();
}
public class F4 implements F3 {@Overridepublic void foo() {System.out.println("foo");}
}
此时如果我需要只增强F1类中的foo()方法,不增强bar()方法,则切点表达式需要根据包名、类名、方法等参数配置:
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
底层是通过.matches() 方法进行判定方法/注解是否匹配的:
//底层是通过调用aspectj的.matches判断方法/注解是否匹配
System.out.println(pointcut.matches(F1.class.getMethod("foo"), F1.class));
System.out.println(pointcut.matches(F1.class.getMethod("bar"), F1.class));
如果我需要只增强F1类中的bar()方法,不增强foo()方法,因为bar()方法上加了注解,我们可以在切点表达式配置注解类型:
AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pointcut2.matches(F1.class.getMethod("foo"), F1.class));
System.out.println(pointcut2.matches(F1.class.getMethod("bar"), F1.class));
在上面的案例中 @Transactional 注解,可以加在方法上,表示某个方法被事务控制;也可以加在类上,表示这个类中所有的方法都被事务控制;还可以加在接口上,表示实现了这个接口的子类中的方法会被事务控制。
而AspectJExpressionPointcut切点表达式,只能切方法,无法识别接口和类。所以我们需要换一种实现方式:
StaticMethodMatcherPointcut pointcut3 = new StaticMethodMatcherPointcut() {@Overridepublic boolean matches(Method method, Class<?> aClass) {//检查方法上是否加了注解MergedAnnotations annotations = MergedAnnotations.from(method);if (annotations.isPresent(Transactional.class)){return true;}//检查类上是否加了注解 如果需要检查父类需要重写规则 MergedAnnotations.SearchStrategy.TYPE_HIERARCHYannotations = MergedAnnotations.from(aClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);if (annotations.isPresent(Transactional.class)){return true;}return false;}};
1.5、Advisor的工作原理
AnnotationAwareAspectJAutoProxyCreator是用于自动创建代理以实现切面功能的Spring后处理器,我们可以从容器中获取:
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
在AnnotationAwareAspectJAutoProxyCreator中,有两个重要的方法:
- protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName):用于查找符合条件的切面通知器(Advisors)。
- protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey):内部调用 .findEligibleAdvisors得到存放符合条件切面通知器的集合,如果集合不为空,就创建代理。
下面通过一个案例模拟AnnotationAwareAspectJAutoProxyCreator的实现:
定义一个Aspect切面:
@Aspect
public class MyAspect {@Before("execution(* foo())")public void before(){System.out.println("before");}@After("execution(* foo())")public void after(){System.out.println("after");}
}
定义两个目标类:
public class Target1 {public void foo(){System.out.println("foo");}
}
public class Target2 {public void bar(){System.out.println("bar...");}
}
定义一个Advisor切面:
@Configuration
public class Config {@Beanpublic Advisor advisor(MethodInterceptor advice){AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression("execution(* foo())");return new DefaultPointcutAdvisor(pointcut,advice);}@Beanpublic MethodInterceptor advice(){return new MethodInterceptor() {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("before");Object proceed = methodInvocation.proceed();System.out.println("after");return proceed;}};}}
无论是Aspect切面还是Advisor切面,都要对foo()方法做增强。
定义主类:
public class A17Application {public static void main(String[] args) {GenericApplicationContext context = new GenericApplicationContext();context.registerBean("MyAspect", MyAspect.class);context.registerBean("Config", Config.class);context.registerBean(ConfigurationClassPostProcessor.class);//自动创建代理以实现切面功能的后处理器context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);context.refresh();AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
// //.findEligibleAdvisors找到目标类Target1.class中能匹配的高级/低级切面List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class,"target1");//此时会有四个,第一个是spring自带的,后面三个有两个是MyAspect中高级切面转换成两个低级切面,还有一个是Config中的低级切面for (Advisor advisor : advisors) {System.out.println(advisor);}}
}
后三个切面,其中一个是Advisor中的,两个是Aspect分解出的:
//.wrapIfNecessary 内部调用 .findEligibleAdvisors 得到List<Advisor> 集合不空,就创建代理
Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
System.out.println(o2.getClass());
最终Target1被创建了代理,因为其中有foo()方法,可以被匹配上。
1.6、代理的创建时机
- 如果没有循环依赖,是在初始化之后创建。
- 如果存在循环依赖,是在实例创建后, 依赖注入前创建。
没有循环依赖,Bean2等待Bean1初始化成功后再创建,注入的Bean1应该是增强后的,所以会在Bean1初始化之后创建代理:
Bean1在初始化之前需要等待Bean2初始化完成,Bean2初始化前需要等待Bean1初始化完成,存在循环依赖的问题,代理会在Bean1实例创建后, 依赖注入前创建
补充:循环依赖
Spring 中的循环依赖问题是指两个或多个 Bean 之间相互依赖,形成了一个循环引用的情况。这种情况下,Spring 容器无法在初始化 Bean 的过程中解决依赖关系,从而导致应用程序启动失败或出现其他异常。
循环依赖通常发生在两个或多个 Bean 之间相互引用的情况下。例如,Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean A。
循环依赖通常发生在单例作用域的 Bean 之间,因为单例 Bean 在容器启动时会被实例化并放入容器中,而在实例化的过程中,Spring 无法解决循环引用。
常见的解决方式:
- 构造器注入:使用构造器注入可以在实例化 Bean 时解决循环依赖问题。这是因为 Spring 会优先实例化构造器参数所需的 Bean,然后再注入到对应的构造器中。
- @Lazy 注解:可以使用 @Lazy 注解延迟注入 Bean,从而打破循环依赖。
1.7、高级切面转换低级切面
可以通过解析高级切面类中@Before、@After等注解,生成低级切面,下面以解析@Before 注解为例:
准备一个类,类中有两个标注了@Before 注解的方法:
public class Aspect {@Before("execution(* foo())")public void before1() {System.out.println("before1");}@Before("execution(* foo())")public void before2() {System.out.println("before2");}public void after() {System.out.println("after");}public void afterReturning() {System.out.println("afterReturning");}public void afterThrowing() {System.out.println("afterThrowing");}public Object around(ProceedingJoinPoint pjp) throws Throwable {try {System.out.println("around...before");return pjp.proceed();} finally {System.out.println("around...after");}}
}
在测试类中,首先需要准备一个集合,存放解析到的切面,以及AspectInstanceFactory。AspectInstanceFactory是 Spring AOP 中的一个接口,用于创建切面(Aspect)的实例。我们选择SingletonAspectInstanceFactory实现(用于创建单例模式的切面实例,即每次获取切面实例时都会返回同一个对象。)
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
//创建一个存放所有切面的集合
ArrayList<Advisor> advisors = new ArrayList<>();
然后我们通过反射获取Aspect类中的所有方法,然后判断方法上是否加了 @Before注解,
并且拿到注解的value属性(切点表达式),就可以准备切点,通知然后放入切面中。
//得到高级切面类Aspect的所有方法Method[] methods = Aspect.class.getMethods();for (Method method : methods) {//判断方法上是否有 @Before注解if (method.isAnnotationPresent(Before.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Before.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}}
此时集合中有两个元素。(Aspect的before1()和before2()方法)
for (Advisor advisor : advisors) {System.out.println(advisor);
}
1.8、环绕通知转换
在“1.7、高级切面转换低级切面”中,我们通过解析高级切面上加了@Before、@After等注解的方式,将高级切面中的每个方法拆解成了低级切面(实际是在创建代理时),最终将其放入了一个集合中:
//创建一个存放所有切面的集合
ArrayList<Advisor> advisors = new ArrayList<>();
改造上面的案例,加入对@AfterReturning、@Around 注解的解析:
//得到高级切面类Aspect的所有方法Method[] methods = Aspect.class.getMethods();for (Method method : methods) {//判断方法上是否有 @Before注解if (method.isAnnotationPresent(Before.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Before.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}else if (method.isAnnotationPresent(AfterReturning.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(AfterReturning.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}else if (method.isAnnotationPresent(Around.class)) {//拿到@Before注解的value属性(切点表达式)String expression = method.getAnnotation(Around.class).value();//准备切点AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();pointcut.setExpression(expression);//准备通知AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);//准备切面Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);advisors.add(advisor);}}
遍历存放了低级切面的集合,可以得到:
- AspectJMethodBeforeAdvice:@Before注解转换成的前置通知
- AspectJAfterReturningAdvice:@AfterReturning注解转换成的AfterReturning通知
- AspectJAroundAdvice:@Around注解转换成的环绕通知
然后下一步就应该创建代理并且调用,无论 ProxyFactory 基于哪种方式创建代理, 最后干活(调用 advice)的是一个 MethodInvocation 对象。
MethodInvocation 对象是一个调用链对象(调用链指的是将通知和目标串成链调用),除了需要调用所有的低级切面,还需要调用目标方法。所以环绕通知才适合作为 advice, 其他 before、afterReturning 都会被转换成环绕通知。(实现了MethodInterceptor接口的通知本身就是环绕通知)
统一转换为环绕通知, 体现的是设计模式中的适配器模式,转换时机是在执行方法时。 为了演示,我们使用proxyFactory.getInterceptorsAndDynamicInterceptionAdvice()方法查看转换结果:
//创建代理Target target = new Target();ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.setTarget(target);proxyFactory.addAdvisors(advisors);//获取代理对象方法调用链中需要执行的拦截器和动态拦截通知,查看转换效果List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);for (Object o : methodInterceptorList) {System.out.println(o);}
遍历methodInterceptorList集合,可以得到:
- MethodBeforeAdviceInterceptor:实现了MethodInterceptor接口,是AspectJMethodBeforeAdvice转换成的环绕通知。
- AfterReturningAdviceInterceptor:实现了MethodInterceptor接口,是AspectJAfterReturningAdvice转换成的环绕通知。
- AspectJAroundAdvice:实现了MethodInterceptor接口,因为AspectJAroundAdvice自身就是环绕通知,无需转换。
而统一进行转换的过程涉及到适配器设计模式:
-
MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
-
AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
我们可以看一下MethodBeforeAdviceAdapter :
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {MethodBeforeAdviceAdapter() {}//检查是否支持适配public boolean supportsAdvice(Advice advice) {return advice instanceof MethodBeforeAdvice;}//进行适配public MethodInterceptor getInterceptor(Advisor advisor) {MethodBeforeAdvice advice = (MethodBeforeAdvice)advisor.getAdvice();return new MethodBeforeAdviceInterceptor(advice);}
}
适配完成后,创建并执行调用链:
//创建并执行调用链 环绕通知+目标MethodInvocation methodInvocation = new ReflectiveMethodInvocation(null,target,Target.class.getMethod("foo"),new Object[0],Target.class,methodInterceptorList);methodInvocation.proceed();
直接调用会报错,原因在于,调用链在调用时执行了不同的通知,有些通知的内部需要用到调用链的对象, 所以应该在最外层就将调用链准备完成。
解决方法:在创建代理工厂时,将MethodInvocation放入当前线程:
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);//准备把MethodInvocation放入当前线程
1.9、模拟实现调用链
需要先明确一个概念:
MethodInterceptor接口和MethodInvocation接口的区别:
MethodInterceptor接口是 Spring AOP 中的拦截器接口,用于实现横切逻辑,可以在目标方法执行前后进行一些操作,例如记录日志、进行安全检查、性能监控等。
MethodInvocation接口代表了方法调用的上下文,它提供了对目标方法及其参数、目标对象等信息的访问,用于在代理对象中调用目标方法,并在调用前后执行拦截器的逻辑。
准备一个目标类:
static class Target{public void foo(){System.out.println("foo");}
}
准备环绕通知类:
static class Advice1 implements MethodInterceptor{@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("advice1.before");//调用链中的下一个通知,如果没有通知就会调用目标方法Object proceed = methodInvocation.proceed();System.out.println("advice1.after");return proceed;}}static class Advice2 implements MethodInterceptor{@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("advice2.before");Object proceed = methodInvocation.proceed();System.out.println("advice2.after");return proceed;}}
创建一个类实现调用链接口(组合目标和环绕通知):在proceed() 方法中,牵涉到递归的思想,在执行目标方法前,首先应该调用完所有的通知(Advice1、Advice2)。
static class MyInvocation implements MethodInvocation{/*** 目标类*/private Object target;/*** 目标方法*/private Method method;/*** 目标方法的参数*/private Object[] args;/*** MethodInterceptor(环绕通知)的集合*/List<MethodInterceptor> methodInterceptorList;/*** 调用次数*/private int count = 1;public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {this.target = target;this.method = method;this.args = args;this.methodInterceptorList = methodInterceptorList;}@Overridepublic Method getMethod() {return method;}@Overridepublic Object[] getArguments() {return args;}/*** 调用环绕通知以及目标* @return* @throws Throwable*/@Overridepublic Object proceed() throws Throwable {if (count>methodInterceptorList.size()){//调用目标结束递归return method.invoke(target, args);}//调用次数+1MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);return methodInterceptor.invoke(this);}@Overridepublic Object getThis() {return target;}@Overridepublic AccessibleObject getStaticPart() {return method;}}