Spring AOP动态代理失效的解决方法@Transactional为何会失效

本文基于springboot2.3.10环境,先介绍JDK动态代理,演示在同一类中调用方法时代理失效的原因及解决办法。接着阐述Spring AOP动态代理,包括编程方式和定义切面两种实现方法,还提及Spring中事务失效的情况及解决思路,同时指出AopContext方式的弊端。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

环境:springboot2.3.10


演示类

JavaBean

public class User {
  private Integer id ;
  private String name ;
  public User(Integer id, String name) {
    this.id = id;
    this.name = name;
  }
}

DAO接口

public interface UserDAO {
  User save(User user) ;
	
  User findUser(Integer id) ;
}

DAO实现类

@Component
public class UserDAOImpl implements UserDAO{
	
  @Override
  public User save(User user) {
    this.findUser(user.getId()) ;
    System.out.println("save method : " + user) ;
    return user ; 
  }

  @Override
  public User findUser(Integer id) {
    System.out.println("findUser method invoke...") ;
    return new User(id, "张三" + new Random().nextInt(10000)) ;
  }

}

JDK 动态代理

通过JDK的动态代理来演示在同一个类中调用另一个方法。

生成代理类:

public static void main(String[] args) {
  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true") ;
  UserDAO target = new UserDAOImpl() ;
  UserDAO dao = (UserDAO) Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class<?>[] {UserDAO.class}, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      System.out.println("before") ;
      method.invoke(target, args) ;
      return null ;
    }
  }) ;
  context.set(dao) ;
  dao.save(new User(1, "田七")) ;
}

说明:System.getProperties().put("
sun.misc.ProxyGenerator.saveGeneratedFiles","true") ;用来生成代理类。保存路径为%当前项目根目录%\com\sun\proxy

 

$Proxy0.class就是生成的代理类。通过反编译查看。

这里我稍微修改了下把多余的方法Object中的方法删除了

生成的代理类继承了Proxy并且实现了我们的接口类UserDAO。具体接口中的方法是通过调用InvocationHandler中的invoke方法来执行的。

查看运行结果:

 

save方法被代理了,输出了before,但是findUser方法并没有被代理。

通过代理对象调用save方法,实际调用的是InvocationHandler.invoke中的方法,而真实的方法调用是如下这行代码:

method.invoke(target, args) ;

这里的target就是我们上面new出来的UserDAOImpl对象。那么在真实的save方法执行的时候this是执行的target对象,我们可以测试下:

// save中打印this对象
public User save(User user) {
  System.out.println(this) ;
}
// main中也代码new出来的UserDAOImpl
public static void main(String[] args) {
  System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true") ;
  UserDAO target = new UserDAOImpl() ;
  System.out.println(target) ;
  // 省略
}

输出:

图片

 

输出的是同一个对象,在save方法执行的时候this是指向的创建出来的那个对象。要想 findUser方法也被代理,我们可以通过如下方法来解决。

图片

 

我们把当前的代理类放到当前线程执行的上下文中ThreadLocal。修改save方法如下:

public User save(User user) {
  ProxyDemo.currentProxy().findUser(user.getId()) ;
  System.out.println("save method : " + user) ;
  return user ; 
}

从当前执行的上下文中获取代理对象。执行结果:

 

findUser方法的调用也是通过代理对象调用的。

以上是在使用JDK的动态代理来演示代理失效的原因及解决办法。

Spring AOP动态代理

方法1:

Spring AOP默认使用的是JDK的动态代理来实现的。Spring AOP也可以使用CGLIB实现代理。默认情况下,如果业务对象没有实现接口,则使用CGLIB。

接下来我们通过编程的方式实现代理

在Spring中可以通过ProxyFactory工厂类来实现代理。

public static void main(String[] args) {
  ProxyFactory proxyFactory = new ProxyFactory(new UserDAOImpl()) ;
  proxyFactory.addInterface(UserDAO.class) ;
  proxyFactory.addAdvice(new MethodBeforeAdvice() {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
      logger.info("调用之前") ;
    }
  }) ;
  UserDAO dao = (UserDAO) proxyFactory.getProxy() ;
  User user = new User(1, "李四") ;
  dao.save(user) ;
}

运行结果:

图片

 

类中嵌套方法的调用也不会被代理。在Spring AOP中可以通过AopContext对象来解决

public User save(User user) {
  ((UserDAO)AopContext.currentProxy()).findUser(user.getId()) ;
  System.out.println("save method : " + user) ;
  return user ; 
}

注意:一定要设置
ProxyFactory.setExposeProxy(true) 否则会报如下错误:

图片

 

代理工厂设置:

图片

 

执行结果:

图片

 

执行成功了。

方法2:

接下来我们通过定义一个切面

@Aspect
@Component
public class UserAspect {
	
  private static Logger logger = LoggerFactory.getLogger(UserAspect.class) ;
	
  @Pointcut("execution(* com.pack.dao..*.*(..))")
  private void log() {}
	
  @Before("log()")
  public void beforeLog() {
    logger.info("方法执行之前操作...");
  }
	
}

修改UserDAOImpl将其注册成Bean

@Component
public class UserDAOImpl implements UserDAO{
  @Resource
  private UserDAO userDAO ;
  @Override
  public User save(User user) {
    userDAO.findUser(user.getId()) ;
    System.out.println("save method : " + user) ;
    return user ; 
  }
}

在该类中我们注入UserDAO对象本身它就是代理对象了

测试类:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringBootAopApplication.class})
public class SpringBootAopApplicationTests {	
  private static final Logger logger = LoggerFactory.getLogger(ProxyAopDemo.class) ;  
	
  @Resource
  private UserDAO userDAO ;
	
  @Test
  public void testAop() {
    User user = new User(1, "李四") ;
    userDAO.save(user) ;
  }
}

执行结果:

图片

 

执行成功。

如果我们这里还是通过如下方式调用:

((UserDAO)AopContext.currentProxy()).findUser(user.getId()) ;

那么还需要进行如下的配置:

图片

暴露代理对象,从当前上下文中能获取代理对象

在实际项目中有时候会发现事务失效,这时候我们就该检查代码,可以使用要么用上面的方法(AopContext方式),要么我们把调用的方法放到另外的一个类中。

在Spring中事务失效时可能你的代码就是如下调用方式:

public void createUser(User user) {
  this.save(user)
}
@Transactional
public User save(User user) {
  // todo
}

在一个非事务的方法中调用一个事务方法,事物不会生效的。

 

关于通过AopContext.currentProxy()方式,官方给了如下说明:

The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP

来自百度翻译:下一种方法绝对可怕,我们不愿意指出,正是因为它如此可怕。您可以(尽管对我们来说很痛苦)将类中的逻辑完全绑定到springaop

使用这种方法对我们的业务代码来说是强耦合,还不便于理解。不推荐使用(或禁止使用)。

完毕!!!

给个关注+转发呗谢谢

图片

Spring 中,`@Transactional` 注解是基于 AOP 实现的,它通过代理机制来拦截被注解的方法,从而提供事务支持。但是,如果你通过 `this` 关键字来调用一个方法,那么这个方法就不会被代理拦截,因此 `@Transactional` 注解就会失效。 为了解决这个问题,你可以使用 SpringAOP 代理来保证 `@Transactional` 注解的生效。你可以通过在类级别上声明 `@Transactional` 注解来确保所有的方法都会被代理。例如: ```java @Service @Transactional public class UserServiceImpl implements UserService { @Override public void updateUser(User user) { // ... } } ``` 另外,你也可以将 `@Transactional` 注解放在接口上,然后在实现类中重写接口方法。这样,Spring 会将代理织入到接口方法中,从而保证事务的正确性。例如: ```java @Service @Transactional public class UserServiceImpl implements UserService { @Override public void updateUser(User user) { // ... } } public interface UserService { @Transactional void updateUser(User user); } ``` 最后,你也可以直接使用 Spring 的事务模板(`TransactionTemplate`)来手动管理事务,而不是依赖于 `@Transactional` 注解。这种方式需要你在代码中显式地开启和提交事务。例如: ```java @Service public class UserServiceImpl implements UserService { private final TransactionTemplate transactionTemplate; public UserServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } @Override public void updateUser(User user) { transactionTemplate.execute(status -> { // ... return null; }); } } ``` 以上是几种解决方法,你可以根据自己的需求来选择。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值