一 AOP的定义
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
二 AOP核心概念
- 横切关注点:
- 对哪些方法进行拦截,拦截后怎么处理,这些就叫横切关注点。
- 比如权限认证,日志,事务。
- 通知Avice:
- 在特定的切入点上执行的增强处理。
- 做啥?比如你需要记录日志,控制事务,提前编写好通用的模块,需要的地方直接调用。
- 连接点JoinPoint:
- 要用通知的地方,业务流程在运行的过程中需要插入切面的具体位置。
- 一般是方法的调用前后,全部方法都可以是连接点。
- 只是概念,没啥特殊。
切入点Pointcut:
- 不能全部方法都是连接点,通过特定的规则来筛选连接点,就是pointcut,选中那几个你想要的方法。
- 在程序中主要体现为书写切入点表达式(通过通配,正则)过滤出特定的一组JointPoint连接点。
- 过滤出相应的Advice将要发生的joinpoint地方。
- 切面Aspect
- 通常是一个类,里面定义切入点+通知,定义在什么地方,什么时间,做什么事
- 通知advice指明了时间和要做的事情。
- 目标target
- 目标类,真正的业务逻辑,可以在目标类不知情的条件下,增加新的功能到目标类的链路上。
- 织入weaving
- 把切面应用到目标函数的过程称为织入。
- AOP代理
- AOP框架创建的对象,代理就是目标对象的加强。
- Spring中的AOP代理可以使用JDK动态代理,也可以是CGLIB代理。
Spring AOP 通知分类
- @Before前置通知:
- 在执行目标方法之前运行。
- @After后置通知:
- 在目标方法运行结束之后。
- @AfterReturning返回通知:
- 在目标方法正常返回值后运行。
- @AfterThrowing异常通知:
- 在目标方法出现异常后运行。
- Around环绕通知:
- 在目标方法完成前,后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingloinPoint,需要手动执行joinPoint.procced()
Spring AOP 织入时期:
举个例子:
// 目标类target
public class VideoOrderService {
// 新增订单
void addOrder(){};
// 查询订单
void findOrder(){};
// 删除订单
void deleteOrder(){};
// 更新订单
void updateOrder(){};
}
// JoinPoint连接点: addOrder/findOrder/deleteOrder/updateOrder
// PointCut切入点:过滤出那些joinpoint中哪些目标函数进行切入
// Advice通知:在切入点中的函数上执行的动作,如记录日志,权限校验
// Aspect切面:由切入点和通知组合而成,定义通知应用到哪些切入点。
// Weaving织入:把切面的代码应用到目标函数的过程。
三 实现原理
aop 底层是采用动态代理机制实现的:接口+实现类
如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy ,去创建代理对 象。没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。
就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,
只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这
样就不需要修改源代码。
3.1 静态代理
通过将目标类和代理类实现相同的接口,让代理类持有真实类对象,然后在代理类方法中调用真实方法,在调用真实方法的前后增加我们需要的功能来达到增强的功能。
接口类:
public interface PayService {
/**
* 支付
*/
public void pay();
/**
* 回调
*/
public void callback();
}
实现类:
public class PayServiceImpl implements PayService{
@Override
public void pay() {
System.out.println("pay支付");
}
@Override
public void callback() {
System.out.println("支付回调");
}
}
代理类:
public class StaticPayServiceImpl implements PayService{
private PayService payService;
public void StaticPayServiceImpl(PayService payService){
this.payService = payService;
}
@Override
public void pay() {
System.out.println("支付前增强一下");
payService.pay();
System.out.println("支付后增强一下");
}
@Override
public void callback() {
System.out.println("回调前增强一下");
payService.callback();
System.out.println("回调后增强一下");
}
}
缺点:代码冗余,不利维护。
3.2 动态代理:
Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。
我们在使用JDK的动态代理时,需要编写一个类,去实现InvocationHandler这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。
CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。无法对final,private和static方法实现代理。
JDK动态代理:
public class JdkProxy implements InvocationHandler {
private Object objectTarget;
public Object newProxyInstance(Object objectTarget){
this.objectTarget = objectTarget;
return Proxy.newProxyInstance(objectTarget.getClass().getClassLoader(), objectTarget.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 被代理的对象
* @param method 要调用的方法
* @param args 方法调用时所需要的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
System.out.println("通过JDK动态代理调用前" + method.getName());
result = method.invoke(objectTarget, proxy);
System.out.println("通过JDK动态代理调用后" + method.getName());
}catch (Exception e){
e.printStackTrace();
}
return result;
}
}
CGLIB动态代理:
public class CGLibProxy implements MethodInterceptor {
// 目标类
private Object targetObject;
// 绑定关系
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
// 设置代理类的父类(目标类)
enhancer.setSuperclass(this.targetObject.getClass());
// 设置回调函数
enhancer.setCallback(this);
// 创建子类(代理对象)
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
System.out.println("通过CGLIB动态代理调用前" + method.getName());
result = methodProxy.invokeSuper(o, args);
System.out.println("通过CGLIB动态代理调用后" + method.getName());
}catch(Exception e){
}
return result;
}
}
3.3 总结
- 动态代理和静态代理相比较,最大的好处就是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理,解耦和容易维护。
- 两种动态代理的区别
- JDK动态代理:要求目标对象实现一个接口,但是目标对象有时候只是一个单独的对象,并没有实现任何的接口,这时候就需要使用CGLIB动态代理。
- CGLIB动态代理:它是在内存中构建一个子类对象从而实现对目标对象的扩展,基于继承来实现代理,所以无法对final类,private方法,static方法实现代理。
- SpringAop默认使用JDK动态代理。