Spring AOP動態代理失效的解決方法@Transactional為何會失效


環境: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

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

$Proxy0.class就是生成的代理類。通過反編譯查看。

Spring AOP動態代理失效的解決方法@Transactional為何會失效

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

生成的代理類繼承了Proxy並且實現了我們的接口類UserDAO。具體接口中的方法是通過調用InvocationHandler中的invoke方法來執行的。

查看運行結果:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

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) ;
  // 省略
}

輸出:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

輸出的是同一個對象,在save方法執行的時候this是指向的創建出來的那個對象。要想 findUser方法也被代理,我們可以通過如下方法來解決。

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

我們把當前的代理類放到當前線程執行的上下文中ThreadLocal。修改save方法如下:

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

從當前執行的上下文中獲取代理對象。執行結果:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

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動態代理失效的解決方法@Transactional為何會失效

 

類中嵌套方法的調用也不會被代理。在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) 否則會報如下錯誤:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

代理工廠設置:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

執行結果:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

執行成功了。

方法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) ;
  }
}

執行結果:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

 

執行成功。

如果我們這里還是通過如下方式調用:

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

那么還需要進行如下的配置:

Spring AOP動態代理失效的解決方法@Transactional為何會失效

暴露代理對象,從當前上下文中能獲取代理對象

在實際項目中有時候會發現事務失效,這時候我們就該檢查代碼,可以使用要么用上面的方法(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

使用這種方法對我們的業務代碼來說是強耦合,還不便於理解。不推薦使用(或禁止使用)。

完畢!!!

給個關注+轉發唄謝謝

 
標簽:  springaopspringboot
好文要頂  關注我  收藏該文   
0
0
 
 
 
« 上一篇:  Springboot整合百度開源分布式ID生成器UIDGenerator
posted @  2021-07-10 07:42  FastCoder  閱讀(4)  評論(0)  編輯  收藏  舉報

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM