zoukankan      html  css  js  c++  java
  • 在Spring中,使用ProxyFactory实现对Cglib代理对象的再次代理

    背景

    公司项目有一个在线测试接口的功能,是使用反射机制实现的

    在项目设计架构中,Service层还有一个高层BizService层

    并且@Transcational注解只会加在BizService层,即Service层接口不会存在@Transcational注解

    需求

    要求能够直接通过在线测试接口的功能测试Service层的接口

    问题

    因基于JPA配置的TransactionManager,当测试不存在@Transcational注解的Service层方法时,抛出无事务支持的异常

    Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'flush' call
    

    问题分析

    首先,@Transcational注解 原理是使用Aop基于原对象默认使用CGLIB生成代理对象(如果注解在Impl类上,而不是接口上),并放入Spring容器中

    那么不存在 @Transcational注解 的Service层bean,则是一个不会被 @Transcational注解 aop织入通知并进行代理的bean

    可想而知,调用这些方法时也不会存在对应的Transaction,需要Transaction支持的方法自然会抛异常

    尝试解决

    首先,我尝试了使用JDK动态代理为Service层Bean添加Transcation的处理,代码如下:

    Object bean = SpringContextUtils.getBeanByName(beanName);
    Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(),(proxy, method, args)->{
    	if (Object.class.equals(method.getDeclaringClass())) {
    		logger.debug("skip Proxy process, it is Object class method : {}", method.getName());
    		return method.invoke(bean, arguments);
    	}
    	logger.debug("Open Transaction for the method : {}", method.getName());
    	Object invoke = null;
    	DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
    	//same as the default propagation of spring Annontation @Transcational
    	transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
    	TransactionStatus transactionStatus = TransactionUtils.getTransaction(transDef);
    	try {
    		invoke = method.invoke(bean, arguments);
    		TransactionUtils.commit(transactionStatus);
    		logger.debug("Committed Transaction for the method : {}", method.getName());
    	} catch (Exception e) {
    		TransactionUtils.rollback(transactionStatus);
    		logger.debug("Rolled back Transaction for the method : {}", method.getName());
    		throw e;
    	}
    	return invoke;
    });
    

    一开始的确可以实现添加Transcation的处理的代理功能

    但是这时我发现,如果这个Bean中,只要 任意一个方法 有 @Transcational注解 ,那么这个Bean就会被spring处理,生成代理对象放入Spring容器中

    并且在这个Bean为代理对象情况下,我的JDK动态代理就起不来作用了。无法为这个代理对象再次进行多一个的代理。而后我尝试使用CGLIB动态代理,也存在一样的情况

    排查后,发现由于 Cglib 本身的设计,无法实现在 Proxy 外面再包装一层 Proxy,通常会报如下错误:

    Caused by: java.lang.ClassFormatError: Duplicate method name "newInstance" with signature "..........
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    ... 10 more
    

    错误来源代码:
    net.sf.cglib.proxy.Enhancer#generateClass(ClassVisitor v)

            ......
     
            // 以下部分的字节码,每次生成 Proxy 实例都会插入。JVM 验证字节码时则会报错。
            if (useFactory || currentData != null) {
                int[] keys = getCallbackKeys();
                emitNewInstanceCallbacks(e);
                emitNewInstanceCallback(e);
                emitNewInstanceMultiarg(e, constructorInfo);
                emitGetCallback(e, keys);
                emitSetCallback(e, keys);
                emitGetCallbacks(e);
                emitSetCallbacks(e);
            }
    

    解决方法

    在查阅资料后,发现spring自带一个强大的AOP代理功能,即 ProxyFactory
    它能够在代理对象上再次进行代理,并且之前的切面代码依旧能够使用
    具体代码如下:

    			Object bean = SpringContextUtils.getBeanByName(beanName);
    
                //get proxy Object
                ProxyFactory proxyFactory = new ProxyFactory(bean);
                proxyFactory.addAdvice((MethodInterceptor) (invocation) -> {
                    if (Object.class.equals(invocation.getMethod().getDeclaringClass())) {
                        logger.info("skip Proxy process, it is Object class method : {}", invocation.getMethod().getName());
                        return invocation.proceed();
                    }
                    logger.info("Open Transaction for the method : {}", invocation.getMethod().getName());
                    Object invoke = null;
                    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition();
                    //same as the default propagation of spring Annontation @Transcational
                    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
                    TransactionStatus transactionStatus = TransactionUtils.getTransaction(transDef);
                    try {
                        invoke = invocation.proceed();
                        TransactionUtils.commit(transactionStatus);
                        logger.info("Committed Transaction for the method : {}", invocation.getMethod().getName());
                    } catch (Exception e) {
                        TransactionUtils.rollback(transactionStatus);
                        logger.info("Rolled back Transaction for the method : {}", invocation.getMethod().getName());
                        throw e;
                    }
                    return invoke;
                });
    		//获取完成代理的代理对象
    		Object proxyBean = proxyFactory.getProxy();
    

    测试再次代理之前(即完成添加Transcation的代理之前)的切面代码是否可用,自定义一个AOP类:

    @Aspect
    @Component
    public class MyTestAop {
    
    	@Pointcut(value = "execution(* com.xxx.common.services.core.xxxService.updateXxx(..))")
    	public void stepBasePoint() {
    	}
    
    	@Before("stepBasePoint()")
    	public void before() throws Throwable {
    		System.out.println("My Test Aop");
    	}
    }
    

    重启项目,测试在线测试接口的功能,结果如下,自定义AOP类的切面代码可以正常调用,并且不再抛出无事务支持的异常

  • 相关阅读:
    【Leetcode_easy】961. N-Repeated Element in Size 2N Array
    【Leetcode_easy】953. Verifying an Alien Dictionary
    【Leetcode_easy】949. Largest Time for Given Digits
    【Leetcode_easy】944. Delete Columns to Make Sorted
    【Leetcode_easy】942. DI String Match
    【Leetcode_easy】941. Valid Mountain Array
    【Leetcode_easy】938. Range Sum of BST
    【Leetcode_easy】937. Reorder Log Files
    【Leetcode_easy】933. Number of Recent Calls
    【Leetcode_easy】929. Unique Email Addresses
  • 原文地址:https://www.cnblogs.com/zhuang229/p/13970041.html
Copyright © 2011-2022 走看看