面试常问Spring AOP和事务注解@Transactional原理

内容大纲

  • 动态代理技术
  • ProxyFactory
  • Advice分类
  • Advisor
  • Spring创建代理对象的方式
  • Spring AOP概念
  • ProxyFactory对Cglib或JDK Proxy的选择
  • 代理对象创建过程
  • @EnableTransactionManagement原理
  • Spring事务基本执行原理
  • Spring事务的传播机制
  • TransactionSynchronization工具类

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

1.动态代理技术

1.1.JDK动态代理

基于Proxy类的#newInstance( )方法来获取目标对象的代理对象,JDK的动态代理必须基于接口实现,因为Java语言不支持多继承,但是支持多实现。 实现机制:

     1. 编写生成类实现`InvocationHandler接口`,覆盖invoke方法,实现切面逻辑。
     1. 通过Proxy的#newInstance( )静态方法来获取代理对象。
复制代码

1.2.Cglib动态代理

Cglib是一个第三方代码生成库,是对ASM字节码技术的二次封装,在运行时动态生成代理对象,它的特点是基于子类继承的关系来实现。 特点:基于子类的继承、无法对final修饰的类和方法进行代理、效率高、额外引入Jar包。 实现机制:

     1. 编写生成类实现`MethodInterceptor接口`,覆盖intercept方法,实现切面逻辑。
     1. new一个Enhancer类来实现父子类的委托。
     1. 将实现了MethodInterceptor的类作为入参放入Enhancer的callbacks属性里。
     1. 通过enhancer对象的#create( )方法得到代理对象。
复制代码

1.3.ASM字节码技术

ASM是一个操纵字节码的工具框架,ASM可以直接生成二进制的class文件,可以对class文件做修改。 实现机理:ASM通过访问者模式,将文件的内容从头到尾扫描,每当扫描到特定的内容标识就会回调对应的逻辑处理方法,基于此可以实现对源文件的增强,来实现代理技术。 由于是直接操纵字节码,效率十分高。常见的Groovy、Cglib都运用了ASM技术。

1.4.AspectJ

AspectJ全称Eclipse AspectJ,是一个简单易用高效的AOP框架,也是一种基于Java平台的语言。Java语言的编译器是javac,AspectJ语言的编译器是ajc。 AspectJ属于是静态织入,也就是说在编译期就织入完成,但是他的效率比Spring的AOP更高。

2.ProxyFactory 代理工厂

Spring不可能直接写Cglib或者JDK的动态代理,而是基于这俩做了封装,封装出来的类叫做ProxyFactory代表是一个代理工厂,专门负责创建代理对象。

UserService target = new UserService(); 
ProxyFactory proxyFactory = new ProxyFactory(); 
proxyFactory.setTarget(target); 
proxyFactory.addAdvice(new MethodInterceptor() { 
    @Override 
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before...");                                                                                                                   
        Object result = invocation.proceed(); 
        System.out.println("after..."); 
        return result; } }); 
UserInterface userService = (UserInterface) proxyFactory.getProxy(); 
userService.test();
复制代码

ProxyFactory会自动判断使用Cglib还是JDK动态代理。

3.Advice的分类

  • Before Advice:在方法执行之前触发。
  • After returning Advice:在方法return后触发。
  • After throwing Advice:在方法抛出异常后触发。
  • After finally Advice:在方法执行完finally和return后触发。
  • Around Advice:环绕触发模式,自定义触发时机。

4.Advisor

Advisor = Advice + Pointcut。Pointcut就是指需要代理的地方。

5.创建代理对象的方式

5.1 ProxyFactoryBean

利用FactoryBean的机制,通过@Bean给容器返回一个工厂Bean,最后通过工厂返回一个代理对象给容器。

    @Bean
    public ProxyFactoryBean userServiceProxyBean() {
        UserService userService = new UserService();
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTarget(userService);
        proxyFactoryBean.addAdvice((MethodInterceptor) invocation -> {
            log.info("执行方法之前.......");
            Object result = invocation.proceed();
            log.info("执行方法之后.......");
            return result;
        });
        return proxyFactoryBean;
    }
复制代码

5.2 BeanNameAutoProxyCreator

通过BeanNameAutoProxyCreator可以对目标Bean进行模式匹配进行AOP,但是他的缺点就是只能通过Bean的名字来指定目标对象。

    @Bean
    public BeanNameAutoProxyCreator beanNameAutoProxycreator() {
        BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
        beanNameAutoProxyCreator.setBeanNames("被代理的对象名字");
        beanNameAutoProxyCreator.setInterceptorNames("你自己定义的Advise");
        beanNameAutoProxyCreator.setProxyTargetClass(true);
        return beanNameAutoProxyCreator;
    }
复制代码

5.3 DefaultAdvisorAutoProxyCreator

    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor() {
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.addMethodName("切点方法名");

        DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
        defaultPointcutAdvisor.setPointcut(pointcut);
        defaultPointcutAdvisor.setAdvice(null);
        return defaultPointcutAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        return new DefaultAdvisorAutoProxyCreator();
    }
复制代码

通过DefaultAdvisorAutoProxyCreator会通过BeanPostProcessor机制检查所有的Advisor类型的Bean,根据Advisor中的Pointcut和Advice逻辑来实现代理功能。常见的注解版本@ASspect、@Before、@Around等就是它的简化版本:

    @Before("execution(com.example.redis.demo.UserService.test())") 
    public void before(JoinPoint joinPoint) {
        log.info("方法执行前......");
    }
复制代码

不过这种方式需要开启:@EnableAspectJAutoProxy注解。

6.Spring Aop概念

AOP本身代表面向切面编程,Spring提供了一套解决方案,可以通过简单的配置就可以实现面向切面的效果。Spring的AOP依赖了AspectJ,使用了AspectJ的一些核心注解,但是具体的解析工作和实现逻辑则是Spring自己处理的,例如常见的@Before、@After等等是AspectJ提供的。 Spring会将AspectJ的五个注解解析为Spring对应的Advice类:

@Before:AspectJMethodBeforeAdvice

@AfterReturning:AspectJAfterReturningAdvice

@AfterThrowing:AspectJAfterThrowingAdvice

@After:AspectJAfterAdvice

@Around:AspectJAroundAdvice 本质上来说就是个MethodInterceptor,当外部调用代理对象的方法时候就会触发方法拦截器来做方法执行前后的增强逻辑。

7.ProxyFactory对Cglib和JDK动态代理的选择

image.png

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
	 * to standard Java interface-based proxies. The default is {@code false}.
	 */
   // 是否强制开启使用cglib
	boolean proxyTargetClass() default false;

	/**
	 * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
	 * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
	 * Off by default, i.e. no guarantees that {@code AopContext} access will work.
	 * @since 4.3.1
	 */
   // 是否暴露代理对象,使用AopContext.currentProxy()获取
	boolean exposeProxy() default false;

}
复制代码
  - 如果开启了`@EnableAspectJAutoProxy`注解,并且`proxyTargetClass`属性赋予了true,那么无条件使用Cglib。
  - 如果需要代理的对象基于接口,且参数没有指定强制使用cglib则使用JDK的动态代理。
复制代码

8.AbstractAdvisorAutoProxyCreator(AOP核心)

这个抽象类的实现类之一就是DefaultAdvisorAutoProxyCreator,这个抽象代理构建类从某种意义上来说,只要Spring IOC容器中有这个Bean的实现,就相当于是开启了AOP功能。 AbstractAdvisorAutoProxyCreator实现了BeanPostProcessor,所以它本质上来说也是一个Spring的后置处理器,在Spring Bean实例化后,在初始化前后会经过这个后置处理器的加工,从而来实现AOP的代理生成。 大致过程是:AbstractAdvisorAutoProxyCreator通过BeanPostProcessor机制来加工初始化后的Bean,找到容器中所有的Advisor(切点+切面逻辑),然后判断这个Bean是否存在Advisor所匹配的切点,如果匹配就表示当前这个初始化的Bean需要代理,此时就会通过ProxyFactory选择具体的动态代理手段来构建代理对象。

8.1 @EnableAspectJAutoProxy

这个注解的核心原理就是往IOC容器中Import了一个AnnotationAwareAspectJAutoProxyCreator类的Bean实例。这个类继承自AbstractAdvisorAutoProxyCreator,所以它本质上也是一个BeanPostProcessor后置处理器。 AnnotationAwareAspectJAutoProxyCreator除了可以找到所有的Advisor类型的Bean,还可以找到@Aspect注解的Bean,并将其解析为Advisor对象,然后走AOP的逻辑生成代理对象。

@Transactional注解的实现无非也是基于AOP。

9.@EnableTransactionManagement注解原理

这个注解的作用不言而喻,就是开启Spring对事务管理的支持。和其他的Enable注解类似,他的底层原理就是通过ImportSelector接口向IOC容器中放入了2个Bean:

  - AutoProxyRegistrar:开启AOP的支持,底层就是一个PostProcessor。
  
  - ProxyTransactionManagementConfiguration:事务切面功能的具体实现。
复制代码

9.1 AutoProxyRegistrar(AOP的支持)

这个Bean的作用就是向IOC容器中注册了一个AbstractAdvisorAutoProxyCreator的实现类,这个抽象类就是AOP的核心实现。所以这个Bean说白了也就是BeanPostProcessor,在Spring Bean初始化后会进行对Advisor的寻找,然后判断当前Bean是否需要进行AOP并且是否有符合的切点,然后进行AOP的操作。

9.2 ProxyTransactionManagementConfiguration(事务支持)

这也是一个配置类,但是它又定义了3个Bean:

     - `BeanFactoryTransactionAttributeSourceAdvisor`:相当于一个Advisor。
     
     - `AnnotationTransactionAttibuteSource`:用来检查某个类或者方法上是否添加了@Transactional注解。
     
     - `TransactionInterceptor`:具体的事务代理逻辑,如果某个类存在@Transactional注解,那么在运行事务切到的方法时,就会先调用这个TransactionInterceptor的invoke中进行事务的开启。
复制代码

10.Spring事务基本执行原理

  1. Bean的生命周期中,初始化后阶段,通过BeanPostProcessor机制,经过InfrastructureAdvisorAutoProxyCreator的后置处理方法检查当前初始化的Bean是否存在@Transactional注解,随之生成一个代理对象。
  2. 在外部调用这个代理对象的事务方法的时候,命中MethodInterceptor拦截,检查当前方法是否有匹配的Advisor,有则执行对应的invoke方法,在invoke方法里定义了事务的实现原理。
  3. 利用配置好的PlatformTransactionManager事务管理器新建一个数据库连接。
  4. 修改这个Connection的提交模式autoCommit为false,否则每一条SQL都要自动提交。
  5. 执行MethodInvocation.proceed( )方法,这里面就会调用我们自己的方法逻辑,例如SQL的执行。
  6. 执行结束,如果没有抛出异常则提交,否则回滚。

11.Spring事务传播机制

如果需要达到不同方法对事务控制手段的变化,则需要引入事务传播机制的特性。例如A方法调用B方法,如果希望A和B的不处于同一个事务那么原理如下:

  1. 代理对象执行MethodInvocation.proceed()之前会利用事务管理器新建一个数据库连接conn1。
  2. 将conn1的提交模式autoCommit改为false。
  3. 将conn1这个连接设置到ThreadLocal中。
  4. 执行具体的业务逻辑SQL。
  5. 如果A方法中调用了B方法(代理对象调用):
    1. 代理对象执行B方法之前,将conn1进行挂起,将conn1从ThreadLocal中移除,封装到一个挂起对象里。
    2. 事务管理器新建另一个数据库连接conn2。
    3. 重复刚才的动作:设置autoCommit = false,将conn2放入ThreadLocal。
    4. 执行B方法的业务逻辑SQL。
    5. B方法执行结束,拿到ThreadLocal中的conn2,进行commit提交动作。
    6. 提交了conn2后,将conn1的挂起对象进行恢复,将conn1再次放入ThreadLocal中。
  6. A方法执行结束,从ThreadLocal中拿到conn1,进行commit提交动作。

事务是建立在数据库连接Connection上的,如果A和B方法都处于同一个连接,如果B方法报错了,即便是A方法捕获,依然会全部回滚。

12.TransactionSynchronization事务同步器

这个事务同步器可以用来监听事务的状态,一旦事务状态发生了改变就会以回调的方式触发对应的逻辑。

    @Transactional(rollbackFor = Exception.class)
    public void transactionSyncDemo() {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void suspend() {
                
            }

            @Override
            public void resume() {

            }

            @Override
            public void flush() {

            }

            @Override
            public void beforeCommit(boolean readOnly) {
                log.info("准备提交事务");
            }

            @Override
            public void beforeCompletion() {
                log.info("准备提交或准备回滚");
            }

            @Override
            public void afterCommit() {
                log.info("提交事务后");
            }

            @Override
            public void afterCompletion(int status) {
                log.info("提交或回滚成功");
            }
        });
    }
复制代码

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

猜你喜欢

转载自juejin.im/post/7120070833996824590