SpringAOP实现原理

目录

  1. 概述
  2. 代理类型
    2.1 静态代理
    2.2 动态代理
    2.2.1 JDK动态代理
    2.2.2 CGLIB动态代理
    2.3 何时使用JDK动态代理还是CGLIB?
  3. 相关术语
  4. 织入方式
  5. 通知类型
  6. 事务的特性
  7. 事务的隔离级别
  8. 事务的传播行为
  9. 事务的管理方式

1. 概述

SpringAOP(Aspect Orient Programming)是一种设计思想,称为面向切面编程,利用横切技术剖析对象内部,将业务之间共同调用的逻辑提取并封装为一个可复用的模块,这个模块被命名为切面(Aspect),该模块减少系统中的重复代码,降低模块间的耦合度,可用于日志、权限认证、事务管理等。

SpringAOP思想的实现一般都是基于代理模式 ,在Java中采用JDK动态代理模式,但是JDK动态代理模式只能代理接口而不能代理类。因此SpringAOP会在CGLIBJDK动态代理之间进行切换。

2. 代理类型

SpringAOP的实现是基于代理模式 ,代理类型包括:静态代理、动态代理。

2.1 静态代理

AspectJ编译时增强)使用的是静态代理。所谓静态代理指的是,AOP框架会在编译阶段生成AOP代理类,它会在编译阶段将AspectJ植入到Java字节码中,运行的时候就是增强后的AOP对象。

AspectJ实现方式上依赖于特殊的AJC编译器,它并非是SpringAOP框架的一部分,而是SpringAOP使用了AspectJ的Annotation(注解),用来定义切面、切点等功能。

2.2 动态代理

SpringAOP使用的是动态代理。所谓动态代理指的是,AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

2.2.1 JDK动态代理

JDK动态代理要求被代理类必须实现一个接口,核心是InvocationHandler接口和Proxy类。JDK动态代理调用了Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h),通过该方法生成字节码,动态的创建一个代理类。interfaces参数是该动态类所继承的所有接口,而继承InvocationHandler接口的类则是实现在调用代理接口方法前后的具体逻辑。当

我们调用代理类对象的方法时,都会委托到InvocationHandler.invoke(Object proxy, Method method, Object[] args)方法,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。

public interface PersonService {
    void speak(String content);
}

public class UserServiceImpl implements PersonService {
    @Override
    public void speak(String content) {
        System.out.println("speak被调用了===" + content);
    }
}

public class JDKProxy implements InvocationHandler {
    private Object proxy;
    public JDKProxy(Object proxy) {
        this.proxy = proxy;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = method.invoke(this.proxy, args);
        return object;
    }
    public static void main(String[] args) {
        PersonService person = (PersonService) Proxy.newProxyInstance(PersonService.class.getClassLoader(), 
                new Class[]{PersonService.class}, new JDKProxy(new UserServiceImpl()));
        person.speak("我是用户");
    }
}
2.2.2 CGLIB动态代理

CGLIB(Code Generation Library)底层使用了ASM(一个短小精悍的字节码操作框架)来操作字节码生成新的类,除了CGLIB库外,脚本语言(如Groovy何BeanShell)也使用ASM生成字节码。

CGLIB既能代理接口也能代理类,如果某个类被标记为final,是无法使用CGLIB做动态代理。

SpringAOP实现原理_第1张图片

相关核心API:

  • net.sf.cglib.proxy.Enhancer:增强类,用来创建动态代理类。
  • net.sf.cglib.proxy.MethodProxy:可以方便的调用代理对象的方法。
  • net.sf.cglib.proxy.MethodInterceptor:方法拦截类,它是Callback接口的子接口需要用户实现。

2.3 何时使用JDK动态代理还是CGLIB?

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
  • 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

如果目标类没有实现接口,那么SpringAOP会选择使用CGLIB动态代理目标类

3. 相关术语

  • 切面(Aspect):切面是通知和切点的结合,通知和切点共同定义了切面的全部内容,一般使用@Aspect实现切面的定义;
  • 通知(Advice):通知定义了切面是什么以及何时使用,如Before、After;
  • 切点(PonitCut):切点定义了在何处应用连接点(JoinPonit),通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点;
  • 连接点(JoinPonit):连接点是在应用执行过程中能够插入切面的一个点,这个点可以是调用方法时、抛出以异常时、甚至修改一个字段时;
  • 目标对象(Target):需要被代理的类,如UserService;
  • 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。

4. 织入方式

  • 编译时织入:需要特殊的编译器,如AspectJ;
  • 类加载时织入:需要特殊的类加载器,如ClassLoader;
  • 运行时织入:Spring采用动态代理的方式实现运行时织入,如SpringAOP。

5. 通知类型

  1. 前置通知(Before):在目标方法被调用之前调用通知功能;
  2. 后置通知(After):在目标方法执行完成之后调用通知,此时不会关心方法的输出是什么;
  3. 返回通知(After-returning):在目标方法成功执行之后调用通知;
  4. 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  5. 环绕通知(Around):在被通知的方法调用之前和调用之后执行自定义的行为。

6. 事务的特性

  1. 原子性(Atomacity):事务包含的操作要么全部成功,要么全部失败,即使回滚也不会对数据库产生影响。
  2. 一致性(Consistency):事务必须使数据从一个一致性状态变换到另一个一致性状态,也就是说当一个系统在一致状态下更新后,系统中所有数据都保持一致。
  3. 隔离性(Isolation):当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间相互隔离。
  4. 持久性(Durability):事务一旦被提交,对数据库中的数据的改变就是永久性的,即使数据库系统遇到故障也不会丢失提交事务的操作。

7. 事务的隔离级别

  • TransactionDefinition.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认采用的REPEATABLE_READ隔离级别。Oracle默认采用的READ_COMMITTED隔离级别。
  • 读未提交TransactionDefinition.ISOLATION_READ_UNCOMMITTED):在这种隔离级别下可能会出现脏读,事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
  • 读已提交TransactionDefinition.ISOLATION_READ_COMMITTED):在这种隔离级别下可能会出现不可重复读,指事务A多次读取同一条数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取的数据不一致。(可避免脏读的发生,MySQL默认的隔离级别)
  • 可重复读(TransactionDefinition.ISOLATION_REPEATABLE_READ):在这种隔离级别下可能会出现幻读。指在一个事务内,多次读取一个范围内的数据,发现多次读取的结果不一致(记录可能会增多或减少)。例如事务T1在读取一个范围内数据时结果为空,然后事务T2插入了N条数据并提交事务,事务T1再次读取时结果依然为空,然后事务T1插入了M条数据之后再次查询时结果为N+M条数据,这就发生了幻读。(可避免脏读、不可重复读的发生)
  • 可串行化TransactionDefinition.ISOLATION_SERIALIZABLE):就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待。(可避免脏读、不可重复读、幻读的发生)

8. 事务的传播行为

  • PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务。如果当前存在事务,就加入该事务。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

9. 事务的管理方式

  • 编程式事务管理:使用TransactionTemplate
  • 声明式事务管理:建立在AOP之上的,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

你可能感兴趣的:(SpringAOP实现原理)