首先请思考一下如下代码执行的结果:html
//声明一个AOP拦截service包下的全部方法 @Aspect public class LogAop { @Around("execution(* com.demo.service.*.*(..))") public Object log(ProceedingJoinPoint joinPoint) throws Throwable { try { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); Object ret = joinPoint.proceed(); //执行完目标方法以后打印 System.out.println("after execute method:"+method.getName()); return ret; } catch (Throwable throwable) { throw throwable; } } }
@Service public class UserService{ public User save(User user){ //省略代码 } public void sendEmail(User user){ //省略代码 } //注册 public void register(User user){ //保存用户 this.save(user); //发送邮件给用户 this.sendEmail(user); } }
@SpringBootTest public class UserServiceTest{ @Autowired private UserService userService; @Test public void save(){ userService.save(new User()); } @Test public void sendEmail(){ userService.sendEmail(new User()); } @Test public void register(){ UserService.register(new User()); } }
在执行save
方法后,控制台输出为:java
after execute method:save
在执行sendEmail
方法后,控制台输出为:git
after execute method:sendEmail
请问在执行register()
方法后会打印出什么内容?github
这个时候可能不少人都会和我以前想的同样,在register
方法里调用了save
和sendEmail
,那 AOP 会处理save
和sendEmail
,输出:spring
after execute method:save after execute method:sendEmail after execute method:register
然而事实并非这样的,而是输出:jvm
after execute method:register
在这种认知的状况下,极可能就会写出有bug
的代码,例如:ide
@Service public class UserService{ //用户下单一个商品 public void order(User user,String orderId){ Order order = findOrder(orderId); pay(user,order); } @Transactional public void pay(User user,Order order){ //扣款 user.setMoney(user.getMoney()-order.getPrice()); save(user); //...其它处理 } }
当用户下单时调用的order
方法,在该方法里面调用了@Transactional
注解修饰的pay
方法,这个时候pay
方法的事务管理已经不生效了,在发生异常时就会出现问题。this
咱们知道 Spring AOP 默认是基于动态代理来实现的,那么先化繁为简,只要搞懂最基本的动态代理天然就明白以前的缘由了,这里直接以 JDK 动态代理为例来演示一下上面的状况。代理
因为 JDK 动态代理必定须要接口类,因此首先声明一个IUserService
接口日志
public interface IUserService{ User save(User user); void sendEmail(User user); User register(User user); }
编写实现类
public class UserService implements IUserService{ @Override public User save(User user){ //省略代码 } @Override public void sendEmail(User user){ //省略代码 } //注册 @Override public void register(User user){ //保存用户 this.save(user); //发送邮件给用户 this.sendEmail(user); } }
编写日志处理动态代理实现
public static class ServiceLogProxy { public static Object getProxy(Class<?> clazz, Object target) { return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = method.invoke(target, args); System.out.println("after execute method:" + method.getName()); return ret; } }); } }
运行代码
public class Main{ public static void main(String[] args) { //获取代理类 IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService()); userService.save(new User()); userService.sendEmail(new User()); userService.register(new User()); } }
结果以下:
after execute method:save after execute method:sendEmail after execute method:register
能够发现和以前 Spring AOP 的状况同样,register
方法中调用的save
和sendEmail
方法一样的没有被动态代理拦截到,这是为何呢,接下来就看看下动态代理的底层实现。
其实动态代理就是在运行期间动态的生成了一个class
在 jvm 中,而后经过这个class
的实例调用真正的实现类的方法,伪代码以下:
public class $Proxy0 implements IUserService{ //这个类就是以前动态代理里的new InvocationHandler(){}对象 private InvocationHandler h; //从接口中拿到的register Method private Method registerMethod; @Override public void register(User user){ //执行前面ServiceLogProxy编写好的invoke方法,实现代理功能 h.invoke(this,registerMethod,new Object[]{user}) } }
回到刚刚的main
方法,那个userService
变量的实例类型其实就是动态生成的类,能够把它的 class 打印出来看看:
IUserService userService = (IUserService) ServiceLogProxy.getProxy(IUserService.class, new UserService()); System.out.println(userService.getClass());
输出结果为:
xxx.xxx.$Proxy0
在了解这个原理以后,再接着解答以前的疑问,能够看到经过代理类的实例
执行的方法才会进入到拦截处理中,而在InvocationHandler#invoke()
方法中,是这样执行目标方法的:
//注意这个target是new UserService()实例对象 Object ret = method.invoke(target, args); System.out.println("after execute method:" + method.getName());
在register
方法中调用this.save
和this.sendEmail
方法时,this
是指向自己new UserService()
实例,因此本质上就是:
User user = new User(); UserService userService = new UserService(); userService.save(user); userService.sendEmail(user);
不是动态代理生成的类去执行目标方法,那必然不会进行动态代理的拦截处理中,明白这个以后原理以后,就能够改造下以前的方法,让方法内调用本类方法也能使动态代理生效,就是用动态代理生成的类去调用方法就行了,改造以下:
public class UserService implements IUserService{ //注册 @Override public void register(User user){ //获取到代理类 IUserService self = (IUserService) ServiceLogProxy.getProxy(IUserService.class, this); //经过代理类保存用户 self.save(user); //经过代理类发送邮件给用户 self.sendEmail(user); } }
运行main
方法,结果以下:
after execute method:save after execute method:sendEmail after execute method:save after execute method:sendEmail after execute method:register
能够看到已经达到预期效果了。
一样的,只要使用代理类来执行目标方法就行,而不是用this
引用,修改后以下:
@Service public class UserService{ //拿到代理类 @Autowired private UserService self; //注册 public void register(User user){ //经过代理类保存用户 self.save(user); //经过代理类发送邮件给用户 self.sendEmail(user); } }
好了,问题到此就解决了,可是须要注意的是Spring
官方是不提倡这样的作法的,官方提倡的是使用一个新的类来解决此类问题,例如建立一个UserRegisterService
类:
@Service public class UserRegisterService{ @Autowired private UserService userService; //注册 public void register(User user){ //经过代理类保存用户 userService.save(user); //经过代理类发送邮件给用户 userService.sendEmail(user); } }