工作满三年了,总觉得不看点源码啥的,肚子里没啥东西。前一阵打算实际的看点spring源码啥的看了,看了几天也没记住多少,还是带着问题分析源码记忆比较深
刻,好闲话少说,上问题。
前几天在处理事务的时候遇到了,事务不生效的情况。
public interface VideoTestService {
void test1();
void test2();
void test3();
void test4();
void test5();
}
@Service
public class VideoTestServiceImpl implements VideoTestService{
@Override
public void test1() {
System.out.println("test1()");
}
@Override
public void test2() {
System.out.println("test2()");
test3();
}
@Override
public void test3() {
System.out.println("test3()");
}
@Override
public void test4() {
System.out.println("test4()");
VideoTestService service = (VideoTestService)AopContext.currentProxy();
service.test5();
}
@Override
public void test5() {
System.out.println("test3()");
}
}
@Service
public class VideoTestService2 {
private static final Logger logger = LoggerFactory.getLogger(VideoTestService2.class);
public void test1() {
System.out.println("test1()");
}
public void test2() {
System.out.println("test2()");
test3();
}
public void test3() {
System.out.println("test3()");
}
public void test4() {
System.out.println("test4()");
VideoTestService2 service = (VideoTestService2)AopContext.currentProxy();
service.test5();
}
public void test5() {
System.out.println("test5()");
}
public void add() {
System.out.println("add()");
test1();
}
}
事务切点配置 execution(* com.test.java.service..impl..test*(..))
@Aspect
public class ServiceConfigAspect {
/**
* 定义切入点函数
*/
@Pointcut("execution(* com.test.java.service.impl.*.test*(..))")
void pointcut() {
}
/**
* 使用@Before声明前置通知
* @param thisJoinPoint
*/
@Before("pointcut()")
public void beforeExecute(JoinPoint thisJoinPoint){
System.out.println("before ...");
}
/**
* 使用@After 注解声明后置通知
* @param thisJoinPoint
*/
@After("pointcut()")
public void afterExecute(JoinPoint thisJoinPoint){
System.out.println("end......");
}
}
public class TestServiceAop extends BaseTest {
@Autowired
private VideoTestService videoTestService;
@Autowired
private VideoTestService2 videoTestService2;
@Test
public void test(){
System.out.println(""+ AopUtils.isCglibProxy(videoTestService));
System.out.println(""+AopUtils.isJdkDynamicProxy(videoTestService));
System.out.println(""+ AopUtils.isCglibProxy(videoTestService2));
System.out.println(""+AopUtils.isJdkDynamicProxy(videoTestService2));
System.out.println("\n--------------------------");
videoTestService.test1();
videoTestService.test2();
videoTestService.test4();
System.out.println("\n--------------------------");
videoTestService2.test1();
videoTestService2.test2();
videoTestService2.test4();
videoTestService2.add();
System.out.println("\n--------------------------");
}
}
为了直观的显示过程,用ServiceConfigAspect模拟了 事务处理过程,把开启事务和事务提交用beforeExecute和afterExecute分别代替。
执行结果:
false
true
true
false
before …
test1()
end……
before …
test2()
test3()
end……
before …
test4()
before …
test3()
end……
end……
before …
test1()
end……
before …
test2()
test3()
end……
before …
test4()
before …
test5()
end……
end……
add()
test1()
为什么会出现这样的情况呢?我们自认为的事务videoTestService.test2(); 和videoTestService2.test2(); videoTestService2.add();都没生效,我们考虑下日常的写代码的过程中是不是也写过这样随意加事务(@Transactional)的代码,或是在一个事务里面嵌套this.methodB(事务方法)和一个普通类里面this.methodB (事务方法),其实这样都是不生效的。下面我们来详细的分析下原因。
首先对于VideoTestService和VideoTestService2的区别是一个是接口的实现类bean另一个是普通的bean,这两个在spring中使用的是不同的方式生成。
Aop 技术其实可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;(AspectJ);而动态代理则在运行时借助于 默写类库在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。其中java是使用的动态代理模式 (JDK+CGlib)。
Spring的动态代理包括两个部分:
· JDK动态代理
JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
· CGLib动态代理
CGLib全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。
和JDK动态代理相比较:JDK创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
这是为什么jdk 会有两种方式生成代理对象,而这两者的切换是对开发者透明的,我们去翻看源码;
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
一目了然,可以看到其中的代理对象可以由JDK或者Cglib来生成的,JdkDynamicAopProxy类和Cglib2AopProxy都实现的是AopProxy的接口,上面的这些逻辑就是要判断采用两种动态代理中的那一种。我们又会问了
既然 CGLib 可以代理任何的类了,那为什么还要用 JDK 的动态代理呢?其实聪明的老罗也想到了,参考网上的观点:CGLib 创建代理的速度比较慢,但创建代理后运行的速度却非常快,而 JDK 动态代理正好相反。如果在运行的时候不断地用 CGLib 去创建代理,系统的性能会大打折扣。
目前我们解析了前面的 false、true、true、false。
借用网上的图片,图片展示了过程,我们到达代理对象前test2(),spring为我们开启事务,然后执行模板对象的test2()方法,此时我们内部有个test3()方法都认为是本地test2()的一部分。执行完之后关闭事务。所以总结来说如果持有代理对象已经到达目标对象,然后执行所有的事务方法都是本地方法,不会有事务起作用。
解决之法:
像test4() 这样,使用AopContext.currentProxy();相当于我们又回到了代理对象,使得事务重新起作用。
下面列举JdkDynamicAopProxy jdk方式生成代理对象时的源码:
在执行被调用方法的时候 invoke()
public Object getProxy(ClassLoader classLoader) {
if(logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); //this 就是JdkDynamicAopProxy实现了InvocationHandler,所以invoke 会被调用。
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代码片段
......
// 得到拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// 检查是否定义了拦截器方法,如果没有的话直接调用目标方法
if (chain.isEmpty()) {
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
//我们需要创建一个调用方法
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// proceed内部实现了递归调用遍历拦截器链
retVal = invocation.proceed();
}
......
invocation.proceed(); //源码
@Override
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint(); //执行目标方法
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
//展示了执行完所有拦截器后执行目标方法。因此此时的test2()内部的test()都相当于内部方法了,不会在调用拦截器了
protected Object invokeJoinpoint() throws Throwable {
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
目前我们已经解释了有些情况下事务失效的原因。事情spring AOP 对于我们来说封装的太好了,所以有些东西对我们很透明,但其实只有我们深入内部一定会发现出现问题的原因。