环境: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的动态代理来演示在同一个类中调用另一个方法。
生成代理类:
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的动态代理来演示代理失效的原因及解决办法。
方法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
使用这种方法对我们的业务代码来说是强耦合,还不便于理解。不推荐使用(或禁止使用)。
完毕!!!
给个关注+转发呗谢谢