龙空技术网

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

Spring全家桶实战案例 1244

前言:

眼前小伙伴们对“spring跨域配置不生效”大致比较重视,姐妹们都想要分析一些“spring跨域配置不生效”的相关知识。那么小编同时在网摘上搜集了一些对于“spring跨域配置不生效””的相关文章,希望咱们能喜欢,姐妹们快快来学习一下吧!

环境: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实现类

@Componentpublic 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出来的UserDAOImplpublic 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@Componentpublic 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

@Componentpublic 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)}@Transactionalpublic 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 Boot Security防重登录及在线总数

使用Spring Boot Admin实时监控你的系统

SpringBoot邮件发送示例

SpringBoot多数据源配置详解

Springboot整合RabbitMQ死信队列详解

Springboot中Redis事务的使用及Lua脚本

SpringBoot整合RocketMQ入门示例

Springboot接口幂等性基于token实现方案

Springboot整合MyBatis参数传值方式

SpringBoot2 整合 OAuth2 资源认证(保护)

SpringBoot开发自己的@Enable功能

SpringBoot接口防范CSRF攻击

SpringBoot整合Quartz实现任务调度

Springboot中接口参数校验N种方法你会几个?

SpringBoot2 整合OAuth2实现统一认证

Springboot项目中几种跨域的解决方法

关注:Springboot实战案例锦集

标签: #spring跨域配置不生效