AOP
AOP(Aspect Oriented Programming,面向切面编程)是OOPs(面向对象编程)的补充
可以做: 事务管理、权限、日志、安全等。
AOP让你可以使用简单可插拔的配置,在实际逻辑执行之前、之后或周围动态添加横切关注点。这让代码在当下和将来都变得易于维护
方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象
连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。
JoinPoint: 程序在执行流程中经过的一个个时间点,这个时间点可以是方法调用时,
或者是执行方法中异常抛出时,也可以是属性被修改时等时机,
在这些时间点上你的切面代码是可以(注意是可以但未必)被注入的
Pointcut:
JoinPoints 只是切面代码可以被织入的地方,但我并不想对所有的 JoinPoint 进行织入,
这就需要某些条件来筛选出那些需要被织入的 JoinPoint,
Pointcut 就是通过一组规则(使用 AspectJ pointcut expression language 来描述)
来定位到匹配的 joinpoint
通知(Advice):
在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、
“before”和“throws”通知,切面代码真正被执行的地方,主要有五个织入时机
Advice: 代码织入(也叫增强),Pointcut 通过其规则指定了哪些 joinpoint 可以被织入,
而 Advice 则指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方,
主要有五个织入时机
1 Before Advice: 在 JoinPoints 执行前织入
2 After Advice: 在 JoinPoints 执行后织入(不管是否抛出异常都会织入)
3 After returning advice: 在 JoinPoints 执行正常退出后织入(抛出异常则不会被织入)
4 After throwing advice: 方法执行过程中抛出异常后织入
5 Around Advice: 这是所有 Advice 中最强大的,它在 JoinPoints 前后都可织入切面代码,
可以选择是否执行原有正常的逻辑,如果不执行原有流程,它甚至可以用自己的返回值代替原有的返回值,
甚至抛出异常
列子:
@Aspect @Component public class TestAdvice { // 1. 定义 PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. 定义应用于 JoinPoint 中所有满足 PointCut 条件的 advice, 这里我们使用 around advice,在其中织入增强逻辑 @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { System.out.println("吃萝卜前洗手"); // 原来的 TestServiceImpl.eatCarrot 逻辑,可视情况决定是否执行 point.proceed(); System.out.println("吃萝后买单"); } }
说到 PointCut 的 AspectJ pointcut expression language 声明式表达式,
这个表达式支持的类型比较全面,可以用正则,注解等来指定满足条件的 joinpoint ,
比如类名后加 .*(..) 这样的正则表达式就代表这个类里面的所有方法都会被织入,
使用 @annotation 的方式也可以指定对标有这类注解的方法织入代码
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface GlobalErrorCatch { }
//下面这个方法需要加上try catch(){} 可以通过注解加进去 对代码无侵入
public class TestServiceImpl implements TestService { @Override @GlobalErrorCatch public ServiceResultTO<Boolean> test() { // 此处写服务里的执行逻辑 boolean result = xxx; return ServiceResultTO.buildSuccess(result); } }
//只要配置了AOP 的pointcut @Aspect @Component public class TestAdvice { // 1. 定义所有带有 GlobalErrorCatch 的注解的方法为 Pointcut (这个注解是自定义的哈) @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)") private void globalCatch(){} // 2. 将 around advice 作用于 globalCatch(){} 此 PointCut @Around("globalCatch()") public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable { try { return point.proceed(); //业务代码执行的语句 可以在前后加上东西 如try catch(){} } catch (Exception e) { System.out.println("执行错误" + e); return ServiceResultTO.buildFailed("系统错误"); } } }
// AOP原理
@SpringBootApplication @EnableAspectJ*** public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); TestService testService = context.getBean(TestService.class); // 这个 bean 的 class 居然不是 TestServiceImpl!而是com.example.demo.impl.TestServiceImplEnhancerBySpringCGLIB$$705c68c7! System.out.println("testService = " + testService.getClass()); } }
静态代理动态代理区别
Java 源代码经过编译生成字节码,然后再由 JVM 经过类加载,连接,初始化成 Java 类型,
可以看到字节码是关键,静态和动态的区别就在于字节码生成的时机
静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。在编译时已经将接口,
被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了
动态代理:在程序运行后通过反射创建生成字节码再由 JVM 加载而成
JDK静态代理实现:
缺点 代码量多 多个实例需要多个代理对象
public interface Subject { public void request(); }
public class RealSubject implements Subject { @Override public void request() { // 卖房 System.out.println("卖房"); } }
public class Proxy implements Subject { private RealSubject realSubject; public Proxy(RealSubject subject) { this.realSubject = subject; } @Override public void request() { // 执行代理逻辑 System.out.println("卖房前"); // 执行目标对象方法 realSubject.request(); // 执行代理逻辑 System.out.println("卖房后"); } public static void main(String[] args) { // 被代理对象 RealSubject subject = new RealSubject(); // 代理 Proxy proxy = new Proxy(subject); // 代理请求 proxy.request(); } }
动态代理(JDK)
// 委托类 public class RealSubject implements Subject { @Override public void request() { // 卖房 System.out.println("卖房"); } }
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private Object target;// 维护一个目标对象 public ProxyFactory(Object target) { this.target = target; } // 为目标对象生成代理对象 public Object getProxyInstance() { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { //在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("计算开始时间"); // 执行目标对象方法 method.invoke(target, args); System.out.println("计算结束时间"); return null; } }); } public static void main(String[] args) { RealSubject realSubject = new RealSubject(); System.out.println(realSubject.getClass()); Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance(); System.out.println(subject.getClass()); subject.request(); } 打印结果如下: ```shell 原始类:class com.example.demo.proxy.staticproxy.RealSubject 代理类:class com.sun.proxy.$Proxy0 计算开始时间 卖房 计算结束时间 InvocationHandler: 委托对象所有接口方法调用都会转发到 InvocationHandler.invoke(), 在 invoke() 方法里我们可以加入任何需要增强的逻辑 主要是根据委托类的接口等通过反射生成的 }
CGLib动态代理实现
CGlib 动态代理也提供了类似的 Enhance 类,增强逻辑写在 MethodInterceptor.intercept() 中,
也就是说所有委托类的非 final 方法都会被方法拦截器拦截,在说它的原理之前首先来看看它怎么用的
//定义 public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //System.out.println("目标类增强前!!!"); //注意这里的方法调用,不是用反射哦!!! Object object = proxy.invokeSuper(obj, args); //System.out.println("目标类增强后!!!"); return object; } }
// 使用 public class CGlibProxy { public static void main(String[] args) { //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer = new Enhancer(); //设置目标类的字节码文件 enhancer.setSuperclass(RealSubject.class); //设置回调函数 enhancer.setCallback(new MyMethodInterceptor()); //这里的creat方法就是正式创建代理类 RealSubject proxyDog = (RealSubject) enhancer.create(); //调用代理类的eat方法 proxyDog.request(); } } 代理类:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5 目标类增强前!!! 卖房 目标类增强后!!!
可以看到它并不要求委托类实现任何接口,而且 CGLIB 是高效的代码生成包,
底层依靠 ASM(开源的 java 字节码编辑类库)操作字节码实现的,性能比 JDK 强,
所以 Spring AOP 最终使用了 CGlib 来生成动态代理