在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类的切面代码可以正常调用,并且不再抛出无事务支持的异常

posted @ 2020-11-13 16:28  229  阅读(801)  评论(0编辑  收藏  举报