面向切面编程
面向切面编程(Aspect Oriented Programming),可以将与业务无关但是被各个业务模块共同调用的逻辑抽取出来,以切面的方式切入到代码中,从而降低系统中代码的耦合度,减少重复的代码。
Spring AOP 是通过预编译方式和运行期间动态代理实现程序面向切面编程。
试想我们的项目中有一个接口,它的代码逻辑是这样的:
public R api() {
查询数据库;
返回数据;
}
现在我们需要对该接口进行登录验证,只有登录了的用户才能访问该接口,如果用户没有登录,那么返回一个错误结果。此时,最简单的方式就是使用 if-else
进行判断,添加到代码逻辑中。但如果这种接口数量一多,那我们的工作量就势必加大了。
如果后续开发中,我们还需要给接口添加权限验证,只有具有某种权限的用户才能访问接口,那我们又需要添加大量重复代码。
这种应用场景,例如登录校验、权限校验、日志处理等这种多个模块可能会共同调用的代码,我们完全可以使用切面的方式,将逻辑切入到业务模块中。
AOP 的底层实现原理
AOP 底层使用动态代理完成需求,为需要增加增强功能的类生成代理类,有两种生成代理类的方式,对于被代理类(即需要增强的类),如果:
- 实现了接口,使用 JDK 动态代理,生成的代理类会使用其接口
- 没有实现接口,使用 CGlib 动态代理,生成的代理类会继承被代理类
简单看看 JDK 动态代理的实现方式,可以看到使用了设计模式-代理模式:
// 我们定义一个接口,声明一个登录功能的方法
public interface UserService {
void login(String username, String password);
}
// 有一个实现类,实现登录功能
public class UserServiceImpl implements UserService{
@Override
public void login(String username, String password) {
System.out.println("登录功能, username="+ username + ",password=" + password);
}
}
// 创建一个代理类,完成代理,增强被代理类的功能
public class UserServiceProxy implements InvocationHandler {
// 被代理类的实例,传递进来的就是 UserServiceImpl 的实例
private Object obj;
public UserServiceProxy(Object obj) {
this.obj = obj;
}
// 定义如何增强功能
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("login")) {
System.out.println("执行主体功能之前,增强功能.......");
System.out.println("执行方法:" + method.getName() + ",方法参数: {" + Arrays.toString(args) + "}");
// 增强功能:给用户名添加后缀,实际情况中,可能我们可以判断以下请求的 IP 地址是否在运行范围内
args[0] += "123123";
// 如果我们直接 return method.invoke, 不编写其他代码,那么就等于没有增强功能
// 调用 method.invoke 就是方法执行后的返回结果,如果不调用 method.invoke,就不会执行主体功能
Object res = method.invoke(obj, args);
Sy