背景
首先请思考一下以下代码执行的后果:
- LogAop.java
//申明一个AOP拦挡service包下的所有办法@Aspectpublic 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; } }}
- UserService.java
@Servicepublic class UserService{ public User save(User user){ //省略代码 } public void sendEmail(User user){ //省略代码 } //注册 public void register(User user){ //保留用户 this.save(user); //发送邮件给用户 this.sendEmail(user); }}
- UserServiceTest.java
@SpringBootTestpublic 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
办法后,控制台输入为:
after execute method:save
在执行sendEmail
办法后,控制台输入为:
after execute method:sendEmail
请问在执行register()
办法后会打印出什么内容?
反直觉
这个时候可能很多人都会和我之前想的一样,在register
办法里调用了save
和sendEmail
,那 AOP 会解决save
和sendEmail
,输入:
after execute method:saveafter execute method:sendEmailafter execute method:register
然而事实并不是这样的,而是输入:
after execute method:register
在这种认知的状况下,很可能就会写出有bug
的代码,例如:
@Servicepublic 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
办法的事务管理曾经不失效了,在产生异样时就会呈现问题。
了解 AOP
咱们晓得 Spring AOP 默认是基于动静代理来实现的,那么先化繁为简,只有搞懂最根本的动静代理天然就明确之前的起因了,这里间接以 JDK 动静代理为例来演示一下下面的状况。
因为 JDK 动静代理肯定须要接口类,所以首先申明一个IUserService
接口
- IUserService.java
public interface IUserService{ User save(User user); void sendEmail(User user); User register(User user);}
编写实现类
- UserService.java
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); }}
编写日志解决动静代理实现
- ServiceLogProxy.java
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; } }); }}
运行代码
- Main.java
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:saveafter execute method:sendEmailafter 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);
不是动静代理生成的类去执行指标办法,那必然不会进行动静代理的拦挡解决中,明确这个之后原理之后,就能够革新下之前的办法,让办法内调用本类办法也能使动静代理失效,就是用动静代理生成的类去调用办法就好了,革新如下:
- UserService.java
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:saveafter execute method:sendEmailafter execute method:saveafter execute method:sendEmailafter execute method:register
能够看到曾经达到预期成果了。
Spring AOP 中办法调用本类办法的解决方案
同样的,只有应用代理类来执行指标办法就行,而不是用this
援用,批改后如下:
@Servicepublic class UserService{ //拿到代理类 @Autowired private UserService self; //注册 public void register(User user){ //通过代理类保留用户 self.save(user); //通过代理类发送邮件给用户 self.sendEmail(user); }}
好了,问题到此就解决了,然而须要留神的是Spring
官网是不提倡这样的做法的,官网提倡的是应用一个新的类来解决此类问题,例如创立一个UserRegisterService
类:
@Servicepublic class UserRegisterService{ @Autowired private UserService userService; //注册 public void register(User user){ //通过代理类保留用户 userService.save(user); //通过代理类发送邮件给用户 userService.sendEmail(user); }}
附录
从JVM中拿到动静代理生成的class文件
aop-understanding-aop-proxies