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() {
@Override
public 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");
}
@Transactional
public 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 {
@Override
public 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() {
@Override
public boolean matches(Method method, Class<?> aClass) {
//检查方法上是否加了注解
MergedAnnotations annotations = MergedAnnotations.from(method);
if (annotations.isPresent(Transactional.class)){
return true;
}
//检查类上是否加了注解 如果需要检查父类需要重写规则 MergedAnnotations.SearchStrategy.TYPE_HIERARCHY
annotations = 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 {
@Bean
public Advisor advisor(MethodInterceptor advice){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut,advice);
}
@Bean
public MethodInterceptor advice(){
return new MethodInterceptor() {
@Override
public 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{
@Override
public 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{
@Override
public 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;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return args;
}
/**
* 调用环绕通知以及目标
* @return
* @throws Throwable
*/
@Override
public Object proceed() throws Throwable {
if (count>methodInterceptorList.size()){
//调用目标结束递归
return method.invoke(target, args);
}
//调用次数+1
MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
return methodInterceptor.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}