《Spring揭秘》读书笔记三:AOP

一、一张图看懂 OOP和AOP(来自 《Spring揭秘》)

其中,竖着的各种业务流程对应了 OOP, 而 横着的针对各个流程的统一操作对应 AOP。可以将AOP看作是 对OOP的一种补充和增强。

二、AOP的发展历史

a、静态AOP:横切点和横切逻辑以Aspect形式实现之后,会通过特定的编译器将实现好的 Aspect编译并织入到对应的类中。

好处是:直接以字节码的形式编译到Java类中,不会造成任何的性能损失。

缺点在于:静态的形式不够灵活,涉及到修改需要重新编译再织入。

b、动态AOP:利用Java语言提供的各种动态特性来 将 Aspect织入到系统中。与静态织入最大的区别在于,织入过程发生在系统运行时,而不是 运行之前编译。

缺点在于:会造成运行时的性能损失,但随着JVM不断的升级,大多数情况下可以容忍。

三、Java平台的AOP实现机制

a、动态代理

Java的 Proxy机制,可以在运行期间为 接口动态生成对应的代理对象,可以将 对应的横切逻辑封装在 InvocationHandler中。

唯一的问题在于:动态代理机制 只针对接口有效,它会 生成接口的代理对象,对代理对象的请求都会调用传入的 InvocationHandler的方法, 一般 InvocationHandler中会保存 原始对象的实例,这样就可以添加额外的横切逻辑,并且可以用 反射来调用实际对象的对应方法。

b、动态字节码增强

Java虚拟机加载的 class文件具有严格的格式限定,符合Java class规范的class文件都可以被JVM加载并运行。一般,class文件都是从 Java源代码文件编译生成,我们也可以使用 ASM或者CGLIB等Java工具库在程序运行期间动态构建class文件。

基于以上,我们可以借助 动态字节码增强技术 为 需要织入横切逻辑的类,生成其子类,并将 横切逻辑加入子类之中。在使用时,使用其子类,达到横切的目的。

这种方式不要求一定要有接口实现,但是 因为是 生成子类,对于声明为 final的类和方法 无能为力。

Spring默认采用 动态代理,无法采用动态代理机制时,会选择 使用 CGLIB库的动态字节码增强支持 来 构造代理子类。

c、自定义类加载器

类加载器将class文件加载到虚拟机中,Java允许我们提供自定义的 类加载器来干涉class文件的 加载过程。我们可以在 加载时对符合条件的class 文件织入相应的横切逻辑,并将 改动后的class文件交给JVM达到横切的目的。

这中方式的问题在于,横切逻辑的织入依赖自定义的 类加载器,某些应用服务器会 控制整个类加载体系,可能会造成问题。

四、AOP的基本概念

a、Joinpoint:需要织入横切逻辑的系统执行点

常见类型:方法调用、方法执行、构造方法调用,构造方法执行,字段设置、字段获取、异常处理执行、类初始化。

b、Pointcut:Joinpoint的表述方式或者说匹配规则。支持 名称匹配、正则匹配 或者 最为强大的特定Pointcut表述语言。AspectJ使用这种方式来表述 pointcut,而 Spring 借助了 AspectJ力量也支持了类似的方式。

Pointcut支持 &&和||的运算。

c、Advice:横切逻辑的载体。按照在 Joinpoint处执行时机不同分为:BeforeAdvice、AfterAdvice(AfterThrowing、AfterReturning、AfterAdvice)、AroundAdvice(可以对 方法的入参和出参进行相关的修改)、Introduction

d、Aspect:AOP概念实体,包含了一系列的 Pointcut和 Advice

五、SpringAOP的实现机制

1、代理模式:

 

 

 

 

 

 

 

 

在代理模式中,一般有 一个接口和对应的实现类,然后 可以构造另一个 实现了同一个接口的代理类,代理类中会 持有 实际实现类的实例。对外,我们提供的是 代理类实例,代理类的 request()方法 会 去调用实际对象的 request()方法,同时,可以在调用方法的前后,做出额外的处理。

这种模式存在的问题:假设我们要堆系统中 所有的 request()方法都做拦截,那么就需要去 构造所有包含了 request()方法的接口的实例,这显然增加了很多的重复工作和代码。

2、动态代理

JDK1.3之后引入了动态代理的机制,基于此机制,我们可以为特定的接口在运行时动态构造代理对象,从而拜托了静态AOP的窘境。

针对(1、)中提出的问题,我们可以 提供一个 统一处理 request()方法的  InvocationHandler,如下。

public class MyInvocationHandler implements InvocationHandler {
    
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().contains("request")){
            //处理逻辑
        }
        return null;
    }
}

这样针对 不同的接口,传入构造对象时传入具体的实例就可以 生成对应的代理类实例,并会对 request方法做出额外的处理。

存在的问题:可以看到,动态代理 要求必须是 接口 实现类的模式。如果,对应的实现类没有实现接口,那Spring AOP会尝试借助 CGLIB字节码生成工具来 生成代理子类实例。

3、动态字节码生成:我们可以构造 子类继承目标对象,然后在 子类中对 父类的方法进行覆盖并加入相应的 横切逻辑。当然,肯定不能像静态代理那样为每个对象都提供子类,需要 借助 CGLIB类似的动态字节码生成库,在运行时动态生成。

CGLIB方式和 Proxy方式有点类似。需要提供一个 Callback 或者 其子接口 MethodInterceptor的实现类。

public class MyInvocationHandler implements  MethodInterceptor {
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        if (method.getName().contains("request")){
            //处理逻辑
        }
        return null;
    }
}

然后,通过 Enhancer,提供 对应的父类和 Callback实例,即可获得 子类对象。

六、第一代 Spring AOP

在 (五、)中技术的支持下,Spring AOP经历了两代的发展。虽然经过了两代,但 底层的实现机制一直没变,知识 表现形式和使用方式 发生了变化。

二八定律:Spring AOP只支持 方法执行类型的 Joinpoint,但这已经涵盖了 80%的需求。如果需要更强大的功能,可以借助 AspectJ的支持。

1、Pointcut:

public interface Pointcut {
    Pointcut TRUE = TruePointcut.INSTANCE;

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();
}

public interface ClassFilter {
    ClassFilter TRUE = TrueClassFilter.INSTANCE;

    boolean matches(Class<?> var1);
}

public interface MethodMatcher {
    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

    boolean matches(Method var1, Class<?> var2);

    boolean isRuntime();

    boolean matches(Method var1, Class<?> var2, Object... var3);
}

在 MethodMatcher的基础上,可以把 Pointcut可以分为两类:Static和 Dynamic。 就是 根据 是否需要对 参数来判断而 划分的。

Static类型不用管参数,因此 可以对已有的结果进行缓存,性能上比 Dynamic强很多。

常见的 Pointcut:

 

 

 

 

 

 

 

 

 

 

 

顾名思义:可以根据 名称、正则表达式、注解进行匹配,还支持做 逻辑与和或的运算。

2、Advice

上图的  Advice都输入 per-class类型的Advice,无状态的。

AfterReturningAdvice可以接触到返回值,但是无法对 返回值做出修改。

MethodInterceptor 可以对 方法的入参和出参做出相关的修改,invoke()方法一定会有一个 MethodInvocation类型的参数,因为 这个Advice参与到了调用链之中,一般会在 方法中 调用 MethodInvocation对象的  proceed()方法来 让程序执行继续沿着调用链传播。

3、Aspect

Spring中对应 Aspect的是 Advisor,但是其实比较局限,一个 Advisor中 通常只有 一个  Pointcut和 一个 Advice,比较特殊。正常的 Aspect会有 多个 相互对应的 Pointcut和 Advice。

4、Ordered接口的作用

通常 同一个JoinPoint处会有多个 Pointcut匹配,那么就会有多个 Advice的执行顺序问题。使用Ordered接口可以在 必要的情况下,指定优先级。 数字越小,优先级越高,对Before类来说,优先级高的 先执行,对 After类来说,优先级高的后执行。

5、SpringAOP的  自动代理机制

a、原理:自动代理机制需要依托于 Spring的IoC容器,需要使用 ApplicationContext类型的容器。在 容器bean实例化的 BeanPostProcessor那一步  判断当前bean是否符合 横切的条件,符合的情况下 创建代理对象并返回。

注意:对应的BeanPostProcessor是IoC章节讲述到的 特殊的 InstantiationAwareBeanPostProcessor,当IoC容器检测到有 这种类型的 BeanPostProcessor时,会 直接通过InstantiationAwareBeanPostProcessor中的逻辑构造对象并返回,并不会走正常的Bean实例化流程。

七、第二代Spring AOP

1、新特性:

a、增加了 注解 @Aspect @Pointcut@Advice 可以使用 POJO的方式 声明 Aspect,Spring AOP会 扫描带有 @Aspect的类。

b、   @Pointcut 可以使用 Aspect的 pointcut表述方式

-----------------------------------------------------------------------------------

基于注解的 AOP的实现原理:

首先是 所有的 Aspect 通过加上 @Component注解来 注册到Bean容器之中,然后 实现了 BeanPostProcessor的  AnnotationAwareAspectJAutoProxyCreator 会 拿到所有的 加了 Aspect注解的对象,通过反射拿到 @Pointcut 中的 AspectJ形式的 表达式,然后委托 AspectJ类库 分析并返回一个  PointcutExpression,具体的 类和方法的匹配工作就委托给这个返回的对象就可以了,然后 匹配的情况下 再  借助于 AopProxyFactory 根据 BeanDefinition生成具体的 AopProxy,然后 得到对应的代理对象返回。

呃,作者抄书抄的没有耐心了,到此为止,看抄书笔记肯定不如看 《Spring揭秘》原书啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值