Spring事务中的“坑”

347 阅读4分钟

Spring事务不生效

在开启事物方法A中通过this指针调用本类中开启事物方法B,方法B中的事务未生效。

常见使用模型

public interface IDemo {
    void a();
    void b();
}
/**
 * @author Qi.qingshan
 * @date 2020/9/30
 */
@Service
public class DemoImpl implements IDemo {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void a() {
        this.b();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void b() {
        //do something
    }
}

使用者期望通过带有事务方法a调用带有事务方法b,b方法自动开启新的事务,但是测试发现b方法并未开启新的事务,为什么?

下面通过原理分析事务为何不生效原因,首先需要了解JDK动态代理和Cglib动态代理底层实现原理才能更好的回答这个问题。

如果涉及到字节码指令,可参考JVM 虚拟机字节码指令表

JDK dynamic Proxy底层实现原理

JDK proxy底层是通过实现接口的方式实现dynamic proxy,与原始类实现同一个接口,保证了代理类与原始类功能相同,代理类中拥有原始类对象,通过原始类对象调用原始类方法,由于JDK proxy是基于接口实现的,所以非接口实现类无法使用JDK代理,JDK proxy利用字节码技术在JVM启动时在内存中生成新的代理类字节码,然后由类加载器进行加载,本地不生成代理类字节码文件.class,Demo

//接口
public interface UserService {
    void login();
    void register();
}
//原始目标类
public class UserServiceImpl implements UserService{
    @Override
    public void login() {
        System.out.println("login()....");
    }

    @Override
    public void register() {
        System.out.println("re");
    }
}

使用JDK proxy为login方法增加额外功能(方法执行前后各输出日志)

public class JdkProxy{
    public static  <T> T newProxyInstance(Object target,InvocationHandler handler) {
        return (T) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), target.getClass().getInterfaces(), handler);
    }
}
//handler日志输出
/**
 * @author Qi.qingshan
 * @date 2020/9/1
 */
public class LogInvocationHandler implements InvocationHandler {
    private Object target;

    public LogInvocationHandler(Object target){
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret;
        doBefore();
        ret = method.invoke(target,args);
        doAfter();
        return ret;
    }

    private void doBefore(){
        System.out.println("before.....");
    }

    private void doAfter(){
        System.out.println("after.....");
    }
}
//测试demo
public class JdkProxyTest {

    public static void main(String[] args) throws IOException {
        UserService userService = new UserServiceImpl();
        UserService proxy = JdkProxy.newProxyInstance(userService, new LogInvocationHandler(userService));
        proxy.login();
    }
}

JDK dynamic proxy底层实现原理demo,代理类实现原始目标类接口,保证代理类与原始类功能相同,将原始类对象注入在代理类中$Proxy0,调用原始类方法逻辑,可在调用逻辑前后对原始方法增强。

/**
 * @author Qi.qingshan
 * @date 2020/9/30
 */
public class $Proxy0 implements UserService {

    private UserService userService;

    //注入原理类具体对象
    public $Proxy0 (UserService userService) {
        this.userService = userService;
    }

    @Override
    public void login() {
        doBefore();
        userService.login();
        doAfter();
    }

    @Override
    public void register() {

    }

    private void doBefore(){
        System.out.println("before.....");
    }

    private void doAfter(){
        System.out.println("after.....");
    }
}
//测试
public class JDKDynamicTest{
    public static void main(String []args){
        UserService service = new UserServiceImpl();
        $UserServiceProxy0 $Proxy0 = new $UserServiceProxy0(service);
        $Proxy0.login();
    }
}

由于dynamic proxy生成的class存在于内存中,本地class文件并未修改,怎么去验证上述推理是否正确?

JDK中ProxyGenerator类提供了获取代理类字节码方法,返回byte[],可以通过文件输出流的方式将字节码保存在本地文件中

需要开启JDK参数 sun.misc.ProxyGenerator.saveGeneratedFiles 为true ,可以通过系统变量或者VM参数 
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
public class JdkProxyTest {

    public static void main(String[] args) throws IOException {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        UserService userService = new UserServiceImpl();
        UserService proxy = JdkProxy.newProxyInstance(userService, new LogInvocationHandler(userService));
        byte[] bytes = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), proxy.getClass().getInterfaces());
        FileOutputStream out = new FileOutputStream(new File("E:\\$Proxy0.class"));
        out.write(bytes);
        out.flush();
        out.close();
    }
}

通过反编译工具查看生成的字节码

//字节码
import com.example.springboot.mybatisplus.proxy.UserService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void login() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void register() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.example.springboot.mybatisplus.proxy.UserService").getMethod("login");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.example.springboot.mybatisplus.proxy.UserService").getMethod("register");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

JDK生成的代理类$Proxy0 实现了原始类接口UserService,代理类​Proxy中拥有login方法供外部调用,login方法如下

    public final void login() throws  {
        try {
            //调用InvocationHandler中方法实现功能增强
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

CgLib dnyamic proxy底层实现原理

CgLib底层是通过继承实现,继承需要增加额外功能的原始类,保证CgLib proxy与原始类功能相同,因为是继承实现所以CgLib代理不必要求必须有接口,需要实现MethodInterceptor接口增加外功能

//原始类
public class OrderService {
    public void show() {
        System.out.println("orderService...");
    }
}
//MethodInterceptor
/**
 * @author Qi.qingshan
 * @date 2020/9/30
 */
public class MyMethodInterceptor implements MethodInterceptor {

    private Object target;

    public MyMethodInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //before
        doBefore();
        //do
        Object ret = method.invoke(target, args);
        //after
        doAfter();
        return ret;
    }

    private void doBefore(){
        System.out.println("before...");
    }

    private void doAfter(){
        System.out.println("after...");
    }
}
//Cglib proxy
public class UserServiceCglibProxy {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        Enhancer enhancer = new Enhancer();
        //设置类加载器
        enhancer.setClassLoader(UserServiceCglibProxy.class.getClassLoader());
        //设置目标类
        enhancer.setSuperclass(orderService.getClass());
        //设置额外功能
        enhancer.setCallback(new MyMethodInterceptor(orderService));
        //创建代理对象
        OrderService orderProxy = (OrderService) enhancer.create();
        orderProxy.show();
    }
}

输出CgLib proxy字节码,验证实现原理

设置CgLib字节码输出位置    System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\class");
public class OrderServiceCglibProxy {
    public static void main(String[] args) throws IOException {

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\class");
        OrderService orderService = new OrderService();
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(OrderServiceCglibProxy.class.getClassLoader());
        enhancer.setSuperclass(orderService.getClass());
        enhancer.setCallback(new MyMethodInterceptor(orderService));
        OrderService orderProxy = (OrderService) enhancer.create();
        orderProxy.show();
    }
}

反编译工具查看字节码文件

//通过继承原始类OrderService方式实现
public class de193060 extends OrderService
  implements Factory
{
  .....

  final void CGLIB$show$0()
  {
    super.show();
  }

  public final void show()
  {
    MethodInterceptor tmp4 = this.CGLIB$CALLBACK_0;
    if (tmp4 == null)
    {
      tmp4;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null)
      return;
    super.show();
  }

  static
  {
    CGLIB$STATICHOOK1();
  }
}

为什么事务不生效

Spring事务是通过Spring AOP实现,Spring AOP底层是通过动态代理实现,支持两种代理实现方式即JDK dynamic proxy与Cglib dynamic proxy,也可通过spring配置选择某种proxy,为什么通过a方法调用b方法,b方法事务不生效?原因是外部调用a方法是通过生成的代理对象调用的,而不是通过原始类对象DemoImpl调用,this代表当前对象DemoImpl,当前对象是没有被代理的,所有b方法上的事务也就不生效。


@Service
public class DemoImpl implements IDemo {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void a() {
        //调用当前对象DemoImpl的方法,当前对象 != 代理对象
        this.b();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void b() {
        //do something
    }
}

如何解决

知道dynamic proxy底层原理,修改也就比较简单,主要有下面几种解决方案

  • 将b方法移到新类中
@Service
public class ServiceB {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void b(){
        //do something
    }
}
  • 从spring工厂中获取被代理过的对象,实现ApplicationAware接口获取spring上下文
@Service
public class DemoImpl implements IDemo ,ApplicationContextAware{
    
    private ApplicationContext contetx;
    
    public void setApplicationContext(ApplicationContext contetx) throws BeansException{
        this.context = contetx;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void a() {
        //容器中获取被代理过的对象
        IDemo idemoProxy = context.getBean(DemoImpl.class);
        idemoProxy.b();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void b() {
        //do something
    }
}