AOP像OOP一样,是一种独立于语言的编程范式,实现AOP协议的方式多种多样,包括:运行时、编译器植入、代理等,而SpringAop的采用的是动态代理与Cglib静态植入。
添加依赖
在IOC容器的基础上添加Aop相关依赖。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
编程式Aop演示
ProxyFactory生成代理类是SpringAop底层真正的实现
目标类与接口
interface RunIntf {
void work();
void work1();
}
static class SimpleRun implements RunIntf {
@Override
public void work() {
System.out.println("SimpleRun.work()");
}
@Override
public void work1() {
System.out.println("SimpleRun.work1()");
}
}
通知
static class SimpleAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("SimpleAdvice.before");
return invocation.proceed();
}
}
切面
static class SimplePointCutAdvisor extends AbstractPointcutAdvisor {
@Override
public Advice getAdvice() {
return new SimpleAdvice();
}
@Override
public Pointcut getPointcut() {
JdkRegexpMethodPointcut jdkRegexpMethodPointcut = new JdkRegexpMethodPointcut();
jdkRegexpMethodPointcut.setPattern(".*work1.*");
return jdkRegexpMethodPointcut;
}
}
运行
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisors(new SimplePointCutAdvisor());
proxyFactory.setTarget(new SimpleRun());
proxyFactory.setInterfaces(RunIntf.class);
RunIntf runIntf = (RunIntf) proxyFactory.getProxy();
runIntf.work();
runIntf.work1();
}
结果
SimpleRun.work()
SimpleAdvice.before
SimpleRun.work1()
可以看到,work1方法被植入了切面逻辑。由此可见,我们通过提供目标类、目标类接口、切面逻辑,通过ProxyFactory生成了带有切面功能的代理类。
AOP概念:
- Aspect:切面,包括切入点和切面,SimplePointCutAdvisor就是一个切面。
- Advice:通知,指的是切面提供的处理逻辑,例如上例中的SimpleAdvice。
- Pointcut:切入点,指的是如何定位切面的手段,在Spring中指的是切面表达式。
分析
ProxyFactory是Spring中用来生成代理类的工厂,包装了生成代理类的细节,包括:Cglib方式和Jdk动态代理方式。
接下来我们说一说通过JDK动态代理方式生成代理类的JdkDynamicAopProxy,而这是建立在熟悉Jdk动态代理基础上的:动态代理系列(二)JDK动态代理
调用切面通知链
执行切面逻辑,很简单,如下图:
说白了,依次执行切面逻辑,最后执行目标类方法。
如果每个切面方法都返回布尔类型,我们可以顺序遍历切面逻辑,根据返回值来判断是否继续传递,这是链式处理比较常见的方式,实现也比较简单,比较容易想到。
但Spring并不是使用的这种方式,切面方法也并没有返回布尔类型。
那么Spring是如何执行切面逻辑的呢?
在代理类中,通过把目标类和切面包装为MethodInvocation对象,切面方法的入参正好也是这个对象。
代理类被调用时,会先执行MethodInvocation.proceed(),执行切面逻辑,而切面是否继续传递就看是否继续执行MethodInvocation.proceed()方法,实现如下:
public class SimpleMethodInvocation implements MethodInvocation {
Object target;
List<MethodInterceptor> list;
int i = 0;
@Override
public Object proceed() throws Throwable {
if (i != list.size()) {
list.get(i++).invoke(this); //调用切面逻辑
} else {
return 反射调用(target);
}
return null;
}
}
基于IOC的Aop
先通过例子展示基于IOC的Aop的实现
业务类
package com.esy.stu.aop;
import org.springframework.stereotype.Component;
@Component
public class Car {
public void work() {
System.out.println("Car动了");
}
}
切面逻辑
package com.esy.stu.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class SimpleAspact {
@Before("within(com.esy.stu.aop.Car)")
public void t1() {
System.out.println("before");
}
}
启动类
package com.esy.stu.aop;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = "com.esy.stu.aop")
public class Boot {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Boot.class);
applicationContext.getBean(Car.class).work();
}
}
执行结果:
before
Car动了
分析
从上面可以发现,与一般IOC容器启动相比较,多了一个使用@Aspect标记的切面逻辑的类,并且配置类上面也多了一个注解EnableAspectJAutoProxy,先分析下这个配置注解。
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
EnableAspectJAutoProxy注解其实是引入AspectJAutoProxyRegistrar注册代理类创建器,如下图:
它Aop的关键,这张图也把AnnotationAwareAspectJAutoProxyCreator分为两个部分:
- BeanPostProcessor(AOP就是对业务类进行代理,是由Bean处理器来触发的)
- ProxyCreator(代理类生成工厂)
创建代理
如果了解IOC实例化过程,就会知道,Bean完全初始化之前可以作为半成品被使用,并且能够被其他bean注入。由此可以推测,在引用加入半成品之前,对象就已经被代理对象所替换了,不然其他bean注入就该是目标类了。
下面是核心的源码:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// 反射生成Bean
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
//这里将完成AOP替换,将目标类的引用替换成代理类,并存储到半成品容器中
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
// 装配Bean
populateBean(beanName, mbd, instanceWrapper);
return exposedObject;
}
getEarlyBeanReference方法中调用组件,其中就有AbstractAutoProxyCreator#getEarlyBeanReference
使用的ProxyFactory生成代理类。
关键点
找到切面声明
BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors
遍历所有的bean定义,找到Aspect注解标注的类
获取advice
通过Around、Before、After、AfterReturning、AfterThrowing这几种不同的注解包装方法成为不同的通知对象。
注解 | 通知对象 |
---|---|
Around | AspectJAroundAdvice |
Before | AspectJMethodBeforeAdvice |
After | AspectJAfterAdvice |
AfterReturning | AspectJAfterReturningAdvice |
AfterThrowing | AspectJAfterThrowingAdvice |
如果研究就会发现AspectJMethodBeforeAdvice、AspectJAfterReturningAdvice、AspectJAfterThrowingAdvice并不满足MethodInterceptor接口,需要进一部包装成AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter、ThrowsAdviceAdapter。
构造Advisor
ReflectiveAspectJAdvisorFactory#getAdvisors
包装通知和切面表达式为InstantiationModelAwarePointcutAdvisorImpl。
可以说整个Aop的核心就是ProxyFactory,而IOC容器提供的功能就是构造advice和pointcut。