栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Aop 的原理解析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Aop 的原理解析

文章目录
  • AOP概念:
  • Spring Aop示例
  • Spring AOP 的源码分析:
    • Pointcut 接口 深入
          • NameMatchMethodPointcut
          • JdkRegexpMethodPointcut
          • AnnotationMatchingPointcut
          • ComposablePointcut
          • ControlFlowPointcut
    • Advice接口 深入
      • Around Advice - MethodInterceptor
    • Aspect(Advisor) 接口 深入
    • 织入
    • ProxyFactory 深入:
        • AdvisedSupport
        • 重回 ProxyFactory
    • ProxyFactoryBean
    • 自动化织入过程
  • 总结:

AOP概念:

连接点(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示例

Spring Aop采用的是 动态织入,使用jdk和CGLIB来做动态代理。
我们先来做一个实例,然后再分析原理:
创建一个 SpringBoot 项目,然后导入依赖:


     org.springframework.boot
     spring-boot-starter-aop


   org.springframework.boot
   spring-boot-starter-web

然后编写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 的源码分析:

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() 方法来确定。

  1. 如果isRuntime() 返回false,表示不会考虑具体的 Joincut的方法参数,这种类型的MethodMatch称之为StaticMethodMatcher,因为不用每次都检查参数,那么对于同样类型的方法匹配结构,就可以在框架内部缓存来提高性能。 所以 isRuntime() 返回false,则 matches(Method method, Class targetClass)这个方法的匹配结果会成为其所属的PointCut接口的主要依据
  2. 如果isRuntime() 返回true,表示每次都对方法调用的参数进行匹配检查,叫做DynamicMethodMatcher ,检查时仍会先调用两个参数的matches() 方法,只有返回true时才会调用三个参数的matches()方法去匹配。性能差,最好少使用。
Pointcut 接口 深入

看一下该接口的实现类:

NameMatchMethodPointcut

这是最简单的Pointcut 实现,见名知义,肯定就是根据指定的匹配字符串 和 连接点(JoinCut) 的方法名进行匹配 。
缺点就是无法对重载的方法进行区分,因为它只检查方法名称

JdkRegexpMethodPointcut

看到该实现类里面的 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.* ,这是因为 ,使用该实现类的匹配模式必须以整个方法签名的形式指定,而不能像上面那个实现类一样仅指定 方法名称

AnnotationMatchingPointcut

见名知义,肯定是根据是否存在某注解来匹配 JoinPoint
示例:

AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(RequestMapping.class, RestController.class);

像这样的定义,就会将 所有 @RestController标注的类的所有@RequestMapping标注的方法作为 切入点(PointCut)

ComposablePointcut

该实现类就是可以实现几个 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);
    }
ControlFlowPointcut

比如一个方法 method1() 被作为切入点(pointcut),其他的实现类都在这个方法被执行的时候就切入,但是这个实现类可以判断是哪一个方法调用的它,如果是被指定的方法调用的话才会切入。

Advice接口 深入

通知 advice 是在某个切入点上需要执行的具体逻辑 ,在Spring AOP中对应的接口是 Advice

根据 Advice 实例能否在所有目标对象类的所有实例中共享这一标准 ,可以划分为 per-class和 per-instance 类型的 Advice,per-class的 Advice只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性,

Spring AOP中 BeforeAdvice和AfterAdvice 都是 per-class,Interceptor 则是 per-instance

  1. BeforeAdvice 所实现的横切逻辑将在对应的 Joincut 之前执行,在 BeforeAdvice 执行完成之后,程序将会从Joincut 处继续执行。我们可以使用 BeforeAdvice 来进行整个系统的某些资源初始化,或者其他的一些准备工作。
  2. ThrowsAdvice这个就是 在抛出异常之后执行的切面逻辑,
    我们可以提供实现该接口来实现对系统中特定的异常情况进行监控。虽然该接口没有定义任何方法,但是我们在实现类中定义方法,仍然需要按照一定的规则: void afterThrowing(Method m ,Object[] args,Object target,Throwable t)(前三个参数是可以省略的)
  3. AfterReturningAdvice这个则是在方法执行成功之后进行的处理逻辑,其中定义了 afterReturning()方法,有个缺点就是,该接口只能访问方法的返回值,但却不能修改返回值
  4. 我们在前面的AOP理论中介绍了 Around Advice,那么在Spring AOP中对应的接口就是MethodInterceptor。
Around Advice - MethodInterceptor

在这个接口中,定义了一个方法:

@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;

其中MethodInvocation 接口定义了这样的方法:

@Nonnull
Method getMethod();

这就是获得当前的Joincut的方法,我们可以决定具体何时再调用该方法。
所以我们可以在 Joincut的逻辑执行之前或者之后插入相应的逻辑,甚至捕获 Joincut方法可能抛出的异常

Aspect(Advisor) 接口 深入

前面已经说过了,切面(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();
}

下面来看一下具体的几个实现:

  1. DefaultPointcutAdvisor 这是最通用的PointcutAdvisor实现,任何类型的Pointcut和任何类型的Advice都可以通过DefaultPointcutAdvisor来使用,可以直接通过构造方法或者 setter和 getter来注入Pointcut和Advice
  2. NameMatchMethodPointcutAdvisor是细化后的 DefaultPointcutAdvisor ,它限定自身能使用的 Pointcut类型为NameMatchMethodPointcut,并且外部不可更改。不过 Advice任何类型均可用。
  3. RegexpMethodPointcutAdvisor这个类也是限定了自己能使用的 Pointcut类型,强制为AbstractRegexpMethodPointcut类型,默认使用的是 JdkRegexpMethodPointcut这个实现类
  4. DefaultBeanFactoryPointcutAdvisor这个是较少使用的一个实现类,它的唯一作用是可以通过 BeanName 来在容器里面找到具体的 Bean的 Pointcut 和 Advice ,其他和第一个没有什么区别。
织入

前面的概念已经说了,织入(weaving) 把切面的代码织入到目标函数的过程。
在Spring AOP中,org.springframework.aop.framework.ProxyFactory 这个类是最基本的织入器。

使用ProxyFactory需要两个最基本的东西 。

  1. 第一个是要对其进行织入的目标对象
  2. 第二个是将要应用到目标对象的 Aspect (Advisor),

我们知道Spring的织入过程是 使用JDK动态代理(类实现了一个接口)和 CGLIB代理(类没有实现任何接口) 。

ProxyFactory 深入:

这个类一看就是工厂类,所以只要看它提供产品类的方法即可:

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实例又是什么呢?

AdvisedSupport

来看一下它的类层次

里面的 ProxyConfig仅定义了五个属性,分别控制在生成代理对象的时候,应该采取怎么样的措施:

  1. proxyTargetClass 如果为true,则使用 CGLIB进行代理。默认为false
  2. optimize主要用于告知代理对象是否需要采取进一步的优化措施。 如果为true,则使用CGLIB代理,默认false
  3. opaque该属性用于控制生成的代理对象是否可以强制转换成Advised类
  4. exposeProxy该属性可以让Spring AOP在生成代理对象时,将其绑定到 ThreadLocal
  5. frozen为true,则一旦针对代理对象生成的各项信息配置完成,则不容许更改

要生成代理对象,仅仅靠 ProxyConfig提供的这几个属性完全不够,我们还需要生成代理对象的一些具体信息,如,要针对哪些目标类生成代理对象,要为代理对象加入哪些横切逻辑等这些信息可以通过 Advised接口查询到,简单地说,我们可以使用 Advised接口访问相应代理对象所持有的Advisor,进行添加、移除Advisor的操作

AdvisedSupport继承了 ProxyConfig,我们可以设置代理对象生成的一些控制属性,实现了Advised接口,我们就可以设置生成代理对象相关的目标类、Advice等必要信息。因此,具体的AopProxy实现在生成代理对象的时候,可以从 AdvisedSupport处获得所有必要的信息

重回 ProxyFactory


然后ProxyFactory 类继承了 AdvisedSupport,又能够通过 createAopProxy() 方法来获取AopProxy。所以我们既可以通过 ProxyFactory 设置生成代理对象的所需要的相关信息,也可以取得最终生成的代理对象,前者是 AdvisedSupport的职责,后者是AopProxy的职责

ProxyFactoryBean


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所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。

总结:

在AOP概念中的词基本在Spring AOP都能找到对应的接口,Pointcut --> Pointcut接口 , Advice --> Advice接口,Aspect --> Advisor接口,织入 --> ProxyFactory类
真正实现AOP,还是靠的 ProxyBeanFactory 这个类集成了 切入点和切面等信息,DefaultAdvisorAutoProxyCreator实现了 BeanPostProcessor 这个接口,会去容器内搜寻所有的
ProxyBeanFactory 实例,并为其生成代理对象,而不是原本的对象,这样就实现了代理,也实现了AOP

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/532228.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号