为什么要用AOP
AOP : 面向切面编程
image
- 背景
- oop面向对象编程的思想是继承、封装、多态。所以我们在软件开发中会进行抽象、分割成各个模块或对象。
- 但是!当我们需要日志记录,异常处理时,我们只能讲日志代码嵌套再各个对象上面。
- 不仅不能专注日志本身,还会有大量的代码冗余(重复代码),而且和核心业务毫无关联
- 为了能更好的讲系统级别的代码抽离出来,去掉和对象的耦合,就有了aop。
AOP核心概念
aop的工作重心
是如何将增强(advice)织入到目标对象的连接点上,包括
- 通过pointcut和advice定位到特定joinpoint上
- 在advice中编写切面代码
基本概念
示例
@Component
//定义一个切面
// 1 仅仅使用Aspect注解,并不能讲java对象转换成bean,所以需要@Component之类的注解
// 2 被Aspect标注之后,这个类就会被排除再 auto-proxying机制之外。就不能成为其他aspect的目标对象了。
@Aspect
public class AdviseDefine {
// 定义一个 Pointcut, 使用 切点表达式函数 来描述对哪些 Join point 使用 advise.
// UserService下所有的方法都使用该advice
@Pointcut("execution(* com.xys.service.UserService.*(..))")
public void myPointcut() {
}
// 定义 advise
//在执行myPointcut()方法之前执行该方法
@Before("myPointcut()")
public void doBeforeAccessCheck(JoinPoint joinPoint) {
System.out.println("*Before advise*");
}
//myPointcut()方法正常返回之后执行该方法
@AfterReturning(value = "myPointcut()")
public void AfterReturningThree(){
System.out.println("后置通知");
}
}
当有多个advice时,可以定义优先级
@Aspect
public class AspectOne implements Ordered {
//定义优先级,值越低,优先级越高
@Override
public int getOrder() {
return 1;
}
}
@Aspect
public class AspectTwo implements Ordered {
@Override
public int getOrder() {
return 2;
}
}
Aspect(切面)
- aop的基本单元
- 由pointcut和advice组成,既包含了横切逻辑的定义,也包括连接点的定义
advice(增强/通知)
在连接点上执行的行为,就是“做什么”
- Before:
- 在方法被调用之前调用
- After:
- 在方法完成后调用通知,无论一个方法是正常退出还是发生了异常, 都会被执行
- After-returning:
- 在方法成功执行之后调用通知
- After-throwing:
- 在方法抛出异常后调用通知
- Around:
- 在方法执行前和执行后都会执行
join point(连接点)
是方法连接点,所有的方法都可以认为是joinpoint,例如下面的excu就是
point cut(切点)
我们不希望给所有的方法都添加advice。那么point cut就会提供一组规则来匹配joinpoint,给满足规则的joinpoint添加advice
join point和point cut 区别: advice是在joinpoint上执行的,point cut是规定了哪些joinpoint可以执行advice
Introduction(引入)
- 可以动态的为类添加方法或字段。
- 为目标对象引入音的接口(必须是一个实现)到目标对象
Target(目标对象)
- 织入advice的目标对象,也叫adviced object
- 指的不是原来的类,而是代理类!!!
aop proxy(代理类)
- 融合了原类和增强逻辑的代理类
- 一个代理类是一个JDK动态代理对象或者CGLIB代理对象(下面重点讲代理)
Weaving(织入)
- 织入是一个过程
- 是讲切面应用到目标对象,从而创建出代理对象的过程
- 织入方式有三种
- 编译器织入
- 类装载织入
- 动态代理织入
关于这些概念的具体实现,看这里
AOP动态代理
AspectJ用到了静态织入技术。spring aop用到的技术是动态代理主要有
- Java JDK动态代理
- 前提:目标类基于统一的接口
- 实现:java内部的反射机制
- 优点:生成类的过程比较高效
- 机制:委托机制
- CGLIB动态代理
- 前提:必须依赖CGLIB类库
- 实现:借助asm实现
- 优点:生成类之后的相关执行比较高效
- 机制:继承机制
JDK动态代理
- JDK的动态代理必须具备四个条件:
- 目标接口
- 目标类
- 拦截器
- 代理类
- 实现
- 实现InvocationHandler接口
- 实现接口中的invoke方法
public class MyInvocationHandler implements InvocationHandler {
private Object target;
MyInvocationHandler() {
super();
}
MyInvocationHandler(Object target) {
super();
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//在invoke方法中加入切面逻辑
// 程序执行前加入逻辑,MethodBeforeAdviceInterceptor
System.out.println("before----");
// 程序执行 目标类方法的执行
Object result = method.invoke(target, args);
// 程序执行后加入逻辑,MethodAfterAdviceInterceptor
System.out.println("after--------");
return result;
}
}
目标接口
public interface Service {
/**
* add方法
*/
public void add();
}
目标类
public class AService implements Service {
/*
* (non-Javadoc)
*
* @see jdkproxy.Service#add()
*/
public void add() {
System.out.println("AService add>>>>>>>>>>>>>>>>>>");
}
}
测试类
public class Test {
public static void main(String[] args) {
Service aService = new AService();
MyInvocationHandler handler = new MyInvocationHandler(aService);
// Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例 利用反射
Service aServiceProxy = (Service) Proxy.newProxyInstance(
aService.getClass().getClassLoader(),
aService.getClass().getInterfaces(),
handler);
// 由动态生成的代理对象来aServiceProxy 代理执行程序,其中aServiceProxy 符合Service接口
aServiceProxy.add();
}
}
CGLIB动态代理
- 实现
- 实现MethodInterceptor接口
- 实现intercept方法
1
public class CglibProxy implements MethodInterceptor {
2
public Object intercept(Object object, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
3
// 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。
System.out.println("before-------------");
// 执行目标类add方法
proxy.invokeSuper(object, args);
// 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。
System.out.println("after--------------");
return null;
}
}
==获取增强的目标类 (核心)==
public class Factory {
/**
* 获得增强之后的目标类,即添加了切入逻辑advice之后的目标类
*
* @param proxy
* @return
*/
public static Base getInstance(CglibProxy proxy) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Base.class);
//回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法
enhancer.setCallback(proxy);
// 此刻,base不是单纯的目标类,而是增强过的目标类
Base base = (Base) enhancer.create();
return base;
}
}
目标类
public class Base {
/**
* 一个模拟的add方法
*/
public void add() {
System.out.println("add ------------");
}
}
测试类
public class Test {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
// base为生成的增强过的目标类
Base base = Factory.getInstance(proxy);
base.add();
}
代理对比
名称 | 实现接口 | 实现类 | 实现方法 |
---|---|---|---|
JDK动态代理 | InvocationHandler 接口 | invoke | Proxy.newProxyInstance() |
CGLIB动态代理 | MethodInterceptor 接口 | intercept | enhancer.setCallback() |
应用
啰嗦了这么多,终于到正题了!
- 访问控制:HTTP 接口鉴权
- 我们在使用HTTP restful接口的时候,很多接口都需要权限验证,如果我们每个接口都写的话,就会有很多的代码冗余。如果要修改权限,也是很痛苦。
- 日志记录
- 方法调用时,需要记录调用的参数及返回结果、
- 异常
- 当调用方法出现异常时,可以用aop统一处理
- 性能监控:方法耗时统计
- 如果我们需要看很多接口的执行耗时。aop很合适
- 缓存
- 事务管理
- @Transactional 注解就是基于AOP实现的
具体实现 ,参考这篇文章
- @Transactional 注解就是基于AOP实现的