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
}
}