开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情
@Aspect和Advisor
AOP中有两种切面方式,一种是通过@Aspect注解的切面,该方式一个切面类中可以包含多个切点。还有一种是Advisor的方式,这种只能一个切面对应一个切点。具体有哪些区别创建一个例子来进一步分析。
public class A11 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("aspect1", Aspect1.class);
context.registerBean("config", Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);
context.refresh();
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
List<Advisor> target1 = creator.findEligibleAdvisors(Target1.class, "target1");
for (Advisor advisor : target1) {
System.out.println(advisor);
}
Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
System.out.println(o1.getClass());
System.out.println(o2.getClass());
((Target1)o1).foo();
}
static class Target1 {
public void foo() {
System.out.println("target1.foo");
}
}
static class Target2 {
public void bar() {
System.out.println("target2.bar");
}
}
@Aspect
static class Aspect1 {
@Before("execution(* foo())")
public void before() {
System.out.println("aspect..before");
}
@After("execution(* foo())")
public void after() {
System.out.println("aspect..after");
}
}
@Configuration
static class Config {
@Bean
public Advisor advisor1(MethodInterceptor advice1) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice1);
}
@Bean
public MethodInterceptor advice1() {
return invocation -> {
System.out.println("advice...before");
Object proceed = invocation.proceed();
System.out.println("advice...after");
return proceed;
};
}
}
}
代码有点长,解释一下,这里定义代码的含义就是为了实现上面所说的两种切面实现方式。第一种定义切面类Aspect1并加上@Aspect注解,这种方式上一节也有提到过,而且这种方式也是平时使用的方式。第二种我们单独定义一个配置类,使用的还是上节提到的切点和通知组成切面的逻辑,这里是一个切点对应一个通知并组成一个切面。为什么要用这种方式呢,这里提前说一下,上面的切面类注解的方式,虽然是一个切面包含了多个切点和通知,但是最终也会被转化成Advisor这种最底层的切面。
在main方法中和之前一样定义一个没有任何后处理器的容器GenericApplicationContext,除去添加了常见的注册和后处理器,还加了一个AnnotationAwareAspectJAutoProxyCreator,直译过来就是通过自动注解的方式自动创建代理。
AnnotationAwareAspectJAutoProxyCreator解析
该类间接的也实现了BeanPostProcessor,那么它也是一个后处理器。查看源码发现间接实现了InstantiationAwareBeanPostProcessor接口,并实现了postProcessBeforeInstantiation和postProcessAfterInitialization方法,通过之前对Bean生命周期章节的描述,可以得知该后处理器是发生在实例化之前和初始化之后的。
postProcessBeforeInstantiation
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
//针对当前bean生成一个cacheKey
Object cacheKey = getCacheKey(beanClass, beanName);
//判断beanName不为空置且没有存在于targetSourcedBeans之中。
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
//这里如果判断当前bean为AOP基础设施类就不会代理。指的是Advice、Pointcut、Advisor等AOP相关定义类。
//shouldSkip应该跳过的类
//当符合条件时候加入一个advisedBeans集合,上面的if便是判断是已经处理过存在的话就不再走该if逻辑
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
//
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;
}
这里先看postProcessBeforeInstantiation,也就是实例化之前执行了哪些逻辑,每个判断以及每个方法的细节这里就不再一一展开,分析一下该方法的整体作用。方法前半段,如代码中的一部分注释内容,这一半代码主要是为了排除掉不需要代理的类并存放于集合中。然后判断是否有自定义TargetSource,如果有的话就提前进行代理创建,否者的话就等到对象初始化完成后才创建代理。
postProcessAfterInitialization解析
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
wrapIfNecessary方法就是真正创建代理的逻辑,在此之前有一个if判断,这里涉及到另一个知识点:spring循环依赖的三级缓存和代理之间的关系,本篇对此先不做讨论,可以先理解为,如果已经提前创建过代理了,那么就不会再创建了。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
如上wrapIfNecessary方法的源码中除去一些必要的判断条件以外,有两个重点方法,分别是getAdvicesAndAdvisorsForBean和createProxy。通过名字可以看出,大概其中一个是获取切面切点的bean的方法,一个是创建代理的方法。
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
这里会看最初的代码,findEligibleAdvisors方法被模拟实现过,返回所有符合的Advisor,然后打印找到的结果。
org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A11$Config$$Lambda$57/757004314@70325e14]
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A11$Aspect1.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A11$Aspect1.after()]; perClauseKind=SINGLETON
可以看到这里找到了四个结果,第一个比较特殊,是Spring为所有代理都要加的切面,第二个就是config类中一对一创建的切面。而后两个,看着像是@Aspect注解切面里面的两个方法,但是我们找的是Advisor,为什么把这两个方法找出来了呢?其实正如上面提前说的一个结论,就是注解所修饰的切面,最后也都会被转化成Advisor这种最底层的切面,这后两个正是转化后的切面。
切面转换
前面提到多次切面会发生类型的转换,平时多采用注解的编写方式最终都会转换为最底层的Advisor这种切面和切点一对一的切面。在最初示例代码中,为了模拟源码工作原理使用了下面这句代码来获取所有的切面,它返回的结果就是一个Advisor集合。
List<Advisor> target1 = creator.findEligibleAdvisors(Target1.class, "target1");
直接来模拟该逻辑的实现。
List<Advisor> list = new ArrayList<>();
SingletonAspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect1());
for (Method method : Aspect1.class.getDeclaredMethods()) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
Before annotation = method.getAnnotation(Before.class);
if(annotation!=null){
pointcut.setExpression(annotation.value());
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
自定义一个集合用来接收Advisor,Aspect1类回看最k上面的示例代码,是一个注解方式标准的切面类。通过之前学过的方式获取该类中的方法,以及筛选出加了@Before注解的参数。后面就类似示例代码中的写法,定义一个切点,传入Expression参数。再然后需要创建一个通知类:AspectJMethodBeforeAdvice,该通知类是处理前置通知的,所以我们增加了if判断通知类型避免程序报错,最后就是组装成切面,源码的实现比这复杂的多,但是整体的逻辑便是如此,这里不再一一跟踪查看源码细节。