连接点(joincut) 就是 可以被切入的所有方法
切入点(pointcut)就是你要在哪个方法上切入 的信息,这个信息就是切入点。
通知(advice)就是你要在那个方法前后要执行的具体方法
切面则是定义切入点和通知的组合
把切面应用到目标函数的过程称为织入(weaving)。
像下面这个类:
public class UserService {
public void addUser(){}
public void modifyUser(){}
public void deleteUser(){}
}
连接点(joinpoint) 就是指哪些方法可以被拦截(上面就是addUser()
,modifyUser()
,deleteUser()
)
切入点(pointcut) 就是指定 具体在哪个方法上进行切入 的信息
通知 advice 在某个切入点上需要执行的代码,如日志记录和权限验证
切面(aspect) 由切入点和通知 组合而成
织入(weaving) 把切面的代码织入到目标函数的过程
织入又分静态织入和动态织入,
静态织入: 先将切面(aspect)类编译成class字节码之后,在Java目标类编译时织入,即先编译aspect类再编译目标类
**动态织入:**在运行时动态地将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成。
Spring Aop
采用的是 动态织入,使用jdk和CGLIB来做动态代理。
我们先来做一个实例,然后再分析原理:
创建一个 SpringBoot 项目,然后导入依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
然后编写Controller类:
@RestController
public class MyController {
@RequestMapping("/hello")
public String hello(){
System.out.println("hello");
return "hello";
}
}
编写切面类:
@Component
@Aspect
public class WebAspect {
@Pointcut("execution(public * com.learning.controller..*.*(..)))")
public void controllerAspect(){} //可以理解成 切入点的名称
@Before("controllerAspect()")
public void beforeMethod(){
System.out.println("我在执行请求之前做操作");
}
@After("controllerAspect()")
public void afterMethod(){
System.out.println("我在执行请求之后做操作");
}
}
@Aspect
是告诉Spring容器,这个是一个切面类。
既然这个类是一个 切面(aspect),那么必须包含有 切入点(pointcut)和通知(advice).
其中
@Pointcut
就是说明这是一个切入点,我们这里配置了 表达式,execution是指当执行匹配的那个方法时,
用法是:
execution(方法修饰符(可选) 返回类型 类路径 方法名 参数 异常模式(可选))
还有其他的匹配表达式:
execution: 匹配连接点
within: 某个类里面
this: 指定AOP代理类的类型
target:指定目标对象的类型
args: 指定参数的类型
bean:指定特定的bean名称,可以使用通配符(Spring自带的)
@target: 带有指定注解的类型
@args: 指定运行时传的参数带有指定的注解
@within: 匹配使用指定注解的类
@annotation:指定方法所应用的注解
注意,方法修饰符必须要是 public的,因为动态代理只能拦截那些能访问到的方法,所以尽量不要用其他修饰符
@Pointcut
修饰的方法名就是切入点的名字,用来标识这个切入点。
@Before()
,@After()
, @Around()
@AfterReturning
@AfterThrowing
,就是定义通知(advice)
@Around()
: 它的方法的参数一定要ProceedingJoinPoint
,这个对象是JoinPoint
的子类。我们可以把这个看作是切入点的那个方法的替身,这个proceedingJoinPoint
有个 proceed() 方法,相当于就是那切入点的那个方法执行
@AfterReturning
: 是在目标方法正常完成后把增强处理织入
@AfterThrowing
: 异常抛出后织入的增强
Spring AOP 对我们上面的一些概念都做了抽象接口。
切入点(pointcut)
的接口就是 Pointcut:
我们前面说了 切入点 是指定具体在哪个方法上进行切入,所以接口里面就定义了 ClassFilter
和 MethodMatcher
这两个接口,指定具体在哪个类的哪个方法
分别看一下这两个接口:
ClassFilter
接口 :
很简单,如果class类型匹配的话,matches()
就会返回true,否则返回false,false就不会对该 连接点(Joincut)所在的类进行切入
MethodMatcher
接口:
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, Class<?> targetClass, Object... args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
这里面有两个 matches() 方法,一个带了连接点方法的参数,一个没有,具体执行哪一个则是看 isRuntime() 方法来确定。
MethodMatch
称之为 StaticMethodMatcher
,因为不用每次都检查参数,那么对于同样类型的方法匹配结构,就可以在框架内部缓存来提高性能。 所以 isRuntime() 返回false,则 matches(Method method, Class> targetClass)
这个方法的匹配结果会成为其所属的 PointCut
接口的主要依据DynamicMethodMatcher
,检查时仍会先调用两个参数的matches()
方法,只有返回true时才会调用三个参数的matches()
方法去匹配。性能差,最好少使用。这是最简单的Pointcut 实现,见名知义,肯定就是根据指定的匹配字符串 和 连接点(JoinCut) 的方法名进行匹配 。
缺点就是无法对重载的方法进行区分,因为它只检查方法名称
看到该实现类里面的 Regex ,我们就知道该类肯定是基于正则表达式来实现匹配的。
使用参考下面:
public class test1 {
public static void main(String[] args) {
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
pointcut.setPattern("*.dosth.*");
}
public void dosth(){
System.out.println("do sth");
}
}
我们注意到,匹配的字符串是 *.dosth.*
,这是因为 ,使用该实现类的匹配模式必须以整个方法签名的形式指定,而不能像上面那个实现类一样仅指定 方法名称
见名知义,肯定是根据是否存在某注解来匹配 JoinPoint
示例:
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(RequestMapping.class, RestController.class);
像这样的定义,就会将 所有 @RestController
标注的类的所有@RequestMapping
标注的方法作为 切入点(PointCut)
该实现类就是可以实现几个 Pointcut 之间的交集或者并集
示例:
public static void main(String[] args) {
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(RequestMapping.class, RestController.class);
NameMatchMethodPointcut pointcut1 = new NameMatchMethodPointcut();
pointcut1.addMethodName("dosth");
ComposablePointcut composablePointcut = new ComposablePointcut();
composablePointcut = composablePointcut.union(pointcut);
}
比如一个方法 method1() 被作为切入点(pointcut),其他的实现类都在这个方法被执行的时候就切入,但是这个实现类可以判断是哪一个方法调用的它,如果是被指定的方法调用的话才会切入。
通知 advice 是在某个切入点上需要执行的具体逻辑 ,在Spring AOP中对应的接口是 Advice
根据 Advice 实例能否在所有目标对象类的所有实例中共享这一标准 ,可以划分为 per-class
和 per-instance
类型的 Advice,per-class
的 Advice只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性,
Spring AOP中 BeforeAdvice和AfterAdvice 都是 per-class
,Interceptor 则是 per-instance
BeforeAdvice
所实现的横切逻辑将在对应的 Joincut 之前执行,在 BeforeAdvice
执行完成之后,程序将会从Joincut 处继续执行。我们可以使用 BeforeAdvice
来进行整个系统的某些资源初始化,或者其他的一些准备工作。ThrowsAdvice
这个就是 在抛出异常之后执行的切面逻辑,void afterThrowing(Method m ,Object[] args,Object target,Throwable t)
(前三个参数是可以省略的)AfterReturningAdvice
这个则是在方法执行成功之后进行的处理逻辑,其中定义了 afterReturning()
方法,有个缺点就是,该接口只能访问方法的返回值,但却不能修改返回值Around Advice
,那么在Spring AOP中对应的接口就是MethodInterceptor
。在这个接口中,定义了一个方法:
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
其中MethodInvocation 接口定义了这样的方法:
@Nonnull
Method getMethod();
这就是获得当前的Joincut的方法,我们可以决定具体何时再调用该方法。
所以我们可以在 Joincut的逻辑执行之前或者之后插入相应的逻辑,甚至捕获 Joincut方法可能抛出的异常
前面已经说过了,切面(aspect) 由切入点和通知 组合而成
当 PointCut 和 Advice 都准备好之后,就需要将它们装入到 切面(Aspect) 中去
Spring中的 Aspect 是 Advisor
。正常来说,一个Aspect可以有多个Pointcut 和 多个 Advice ,但是Spring的 Advisor
只有一个 Pointcut和一个Advice,所以Spring中的 Advisor
是特殊的切面(aspect)
Spring AOP 的 Advisor
接口层次如上。
实际上,看 Advisor
接口:
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
boolean isPerInstance();
}
它本身只有 (通知)Advice
到了 PointcutAdvisor
接口才算一个有 Pointcut 和 Advice 的完整的 Advisor:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
下面来看一下具体的几个实现:
PointcutAdvisor
实现,任何类型的Pointcut
和任何类型的Advice
都可以通过DefaultPointcutAdvisor
来使用,可以直接通过构造方法或者 setter
和 getter
来注入Pointcut和AdvicePointcut
类型为 NameMatchMethodPointcut
,并且外部不可更改。不过 Advice任何类型均可用。Pointcut
类型,强制为AbstractRegexpMethodPointcut
类型,默认使用的是 JdkRegexpMethodPointcut
这个实现类前面的概念已经说了,织入(weaving) 把切面的代码织入到目标函数的过程。
在Spring AOP中,org.springframework.aop.framework.ProxyFactory
这个类是最基本的织入器。
使用ProxyFactory
需要两个最基本的东西 。
我们知道Spring的织入过程是 使用JDK动态代理(类实现了一个接口)和 CGLIB代理(类没有实现任何接口) 。
这个类一看就是工厂类,所以只要看它提供产品类的方法即可:
public Object getProxy() {
return createAopProxy().getProxy();
}
然后再去找 createAopProxy()
方法,
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
activate();
}
return getAopProxyFactory().createAopProxy(this);
}
可以看到,它返回的是一个 AopProxy 接口。
接口层次如上,可知,这个 AopProxy接口应该就是拿来做具体的织入过程的。
AopProxy
是通过getAopProxyFactory().createAopProxy(this);
这个工厂方法来获取的
找到这个方法,发现到子类去实现了,所以我们再到子类去看:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!NativeDetector.inNativeImage() &&
(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
伪代码如下:
if((config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)))){
// 创建CglibAopProxy实例返回
}else{
//创建JdkDynamicAopProxy实例返回
}
也就是说,如果传入的AdvisedSupport
实例config的isOptimize或者 isProxyTargetClass为true或者对象没有实现任何接口,那么就使用 CGLIB去代理,否则 使用 JDK去代理
那么 这个传入的AdvisedSupport
实例又是什么呢?
来看一下它的类层次
里面的 ProxyConfig
仅定义了五个属性,分别控制在生成代理对象的时候,应该采取怎么样的措施:
Advised
类要生成代理对象,仅仅靠 ProxyConfig
提供的这几个属性完全不够,我们还需要生成代理对象的一些具体信息,如,要针对哪些目标类生成代理对象,要为代理对象加入哪些横切逻辑等这些信息可以通过 Advised
接口查询到,简单地说,我们可以使用 Advised
接口访问相应代理对象所持有的Advisor
,进行添加、移除Advisor
的操作
AdvisedSupport
继承了 ProxyConfig,我们可以设置代理对象生成的一些控制属性,实现了Advised接口,我们就可以设置生成代理对象相关的目标类、Advice等必要信息。因此,具体的AopProxy实现在生成代理对象的时候,可以从 AdvisedSupport
处获得所有必要的信息
然后ProxyFactory 类继承了 AdvisedSupport
,又能够通过 createAopProxy()
方法来获取AopProxy。所以我们既可以通过 ProxyFactory 设置生成代理对象的所需要的相关信息,也可以取得最终生成的代理对象,前者是 AdvisedSupport
的职责,后者是 AopProxy
的职责
ProxyFactory
只是最普通的一个 织入器。
ProxyFactoryBean
这个类将Spring AOP和Spring IOC容器支持相结合,使我们可以在 容器中对 切入点(PointCut)和通知(Advice)管理更容易。
ProxyFactoryBean可以这样理解 Proxy+ FactoryBean,在IOC容器中,FactoryBean的作用是存储一个对象,如果容器中的某个对象持有某个FactoryBean的引用,那它取得的不是 FactoryBean实例本身,而是 getObject()
方法返回的对象。因此,如果容器中某个对象依赖了 ProxyFactoryBean
的实例,那它就会使用到通过 getObject()
返回的代理对象。
因为 ProxyFactoryBean
继承了 ProxyCreatorSupport
这个类,而这个类又已经把需要做的事情基本完成了(如设置目标对象,配置各种属性,生成对应的AopProxy对象),所以 ProxyFactoryBean
做的主要事情就是拿出AopProxy ,调用它的 getProxy()
拿到代理对象
//简化代码
public Object getObject() throws BeansException {
if (isSingleton()) {
return getSingletonInstance();
}
}
private synchronized Object getSingletonInstance() {
if (this.singletonInstance == null) {
this.singletonInstance = getProxy(createAopProxy());
}
return this.singletonInstance;
}
protected Object getProxy(AopProxy aopProxy) {
return aopProxy.getProxy(this.proxyClassLoader);
}
可以发现,确实如我们所想
Spring AOP 的自动代理是建立在IOC容器的 BeanPostProcessor概念之上。使用BeanPostProcessor,我们可以在遍历容器中所有bean的基础上,对遍历到的bean进行一些操作。
我们只需要提供一个BeanPostProcesser ,然后在这个BeanPostProcesser 内部实现这样的逻辑: 当对象实例化的时候,为其生成代理对象并返回,而不是原本的对象,从而达到代理对象自动生成的目的。
伪代码如下:
for (bean in Ioc容器){
if(bean符合拦截条件){
Object proxy = createProxyFor(bean);
return proxy; //返回代理的对象
}else{
Object instance = createInstance(bean);
return instance;
}
}
createProxyFor()
方法创建代理对象,就直接通过 ProxyFactoryBean
的getBean()都可以。
对于拦截条件,则可以是标注了某些注解。
DefaultAdvisorAutoProxyCreator这个类就是实现了完全自动的自动注入。
可以发现,它确实实现了 BeanPostProcessor
这个接口。
它会自动搜寻容器内的所有 Advisor,然后根据各个 Advisor所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。
我们这里去分析一下它的源码
可以看到在该类中,postProcessBeforeInitialization()
只是返回了当前对象,没有做任何操作,所以织入的具体过程应该在 postProcessAfterInitialization()
方法里面:
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
第一步,判断bean非空
第二步:执行getCacheKey(),该方法是返回一个键,就是存到一些Map或者Set内的的String类型的键
第三步,判断earlyProxyReferences中是否存在过,存在过说明该对象已经被代理过了。
private final Set<Object> earlyProxyReferences = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
第四步,调用wrapIfNecessary()
方法
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 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;
}
在第一步中调用的 getAdvicesAndAdvisorsForBean()
方法,他返回对应于当前class和beanName的所有切面Advisor,
第二步是将当前键存入缓存,防止多次代理
第三步是进行了代理的过程,看一下该方法的源码:
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
//第一步
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
//第二步
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
//最后一步
return proxyFactory.getProxy(getProxyClassLoader());
}
第一步是创建proxyFactory
类,该类就是创建代理对象Proxy的工厂类,然后第二步为该工厂类配置属性,在最后一步获取最终的代理对象。
最后一步的方法:
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
调用createAopProxy方法去获取一个AopProxy的实现类,一种是JDK的是实现类,一种是CGLIB的实现类,使用getProxy()方法来具体获得代理对象。
在AOP概念中的词基本在Spring AOP都能找到对应的接口,Pointcut --> Pointcut
接口 , Advice --> Advice
接口,Aspect --> Advisor
接口,织入 --> ProxyFactory
类
真正实现AOP,还是靠的 ProxyBeanFactory 这个类集成了 切入点和切面等信息,DefaultAdvisorAutoProxyCreator实现了 BeanPostProcessor
这个接口,在postProcessAfterInitialization()
方法里面实现对象的代理过程,里面有个wrapIfNecessary()
方法,里面获取适用于当前对象的所有Advisor对象,
能获取所有Advisor对象又是因为继承了BeanFactoryAware接口,调用了setBeanFactory()方法,所以在该类中有整个容器对象,从中获取所有对象,然后对其中的Advisor对象进行缓存过
然后使用createProxy()
方法获取代理对象,在该方法内部先使用ProxyFactory
这个工厂类,配置相关的代理信息,然后从该工厂类获取具体的AopProxy的实现类,使用该实现类进行具体的代理,并为其生成代理对象,而不是原本的对象,这样就实现了代理,自动地实现了AOP