共计 5045 个字符,预计需要花费 13 分钟才能阅读完成。
背景
首先请思考一下以下代码执行的后果:
- LogAop.java
// 申明一个 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;}
}
}
- UserService.java
@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);
}
}
- UserServiceTest.java
@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
办法后,控制台输入为:
after execute method:save
在执行 sendEmail
办法后,控制台输入为:
after execute method:sendEmail
请问在执行 register()
办法后会打印出什么内容?
反直觉
这个时候可能很多人都会和我之前想的一样,在 register
办法里调用了 save
和sendEmail
,那 AOP 会解决 save
和sendEmail
,输入:
after execute method:save
after execute method:sendEmail
after execute method:register
然而事实并不是这样的,而是输入:
after execute method:register
在这种认知的状况下,很可能就会写出有 bug
的代码,例如:
@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
办法的事务管理曾经不失效了,在产生异样时就会呈现问题。
了解 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: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);
不是动静代理生成的类去执行指标办法,那必然不会进行动静代理的拦挡解决中,明确这个之后原理之后,就能够革新下之前的办法,让办法内调用本类办法也能使动静代理失效,就是用动静代理生成的类去调用办法就好了,革新如下:
- 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:save
after execute method:sendEmail
after execute method:save
after execute method:sendEmail
after execute method:register
能够看到曾经达到预期成果了。
Spring AOP 中办法调用本类办法的解决方案
同样的,只有应用代理类来执行指标办法就行,而不是用 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);
}
}
附录
从 JVM 中拿到动静代理生成的 class 文件
aop-understanding-aop-proxies