自研框架-AOP模块

 

 

 

 


 

前言

容器是OOP的高级工具:

  以低耦合低侵入的方式打通从上到下的开发通道

    按部就班填充代码逻辑实现业务功能,每层逻辑都可无缝替换

    OOP将业务程序分解成各个层次的对象,通过对象联动完成业务

    无法很好地处理分散在各业务里的通用系统需求

系统需求

  码农才去关系的需求

    添加日志信息:为每个方法添加统计时间

    添加系统权限校验:针对某些方法进行限制

软件工程中有个基本原则:关注点分离 Concern Separation

  不同的问题交给不同的部分去解决,每部分专注解决自己的问题

    Aspect Oriented Programming就是其中一种关注点分离的技术

    通用化功能的代码实现即切面Aspect

    Aspect之于AOP,就相当于Class之于OOP,Bean之于Spring

 

框架搭建图:

 

 

 


 

AOP的子民们

  切面Aspect:将横切关注点逻辑进行模块化封装的实体对象

  通知Advice:好比是Class里面的方法,还定义了织入逻辑的时机

  连接点Joinpoint,允许使用Advice的地方(对于SpringAOP来讲默认只支持方法级别的Joinpoint)

  切入点Pointcut:定义一系列规则对Joinpoint进行筛选

  目标对象Target:符合Pointcut条件,要被织入横切逻辑的对象

 

 

AOP是OOP里的"寄生虫"

  织入:将Aspect模块化的横切关注点集成到OOP里

  织入器︰完成织入过程的执行者,如ajc

  Spring AOP则会使用一组类来作为织入器以完成最终的织入操作

 

Advice的种类

  BeforeAdvice:在JoinPoint前被执行的Advice(该方法并不能阻止JoinPoint的执行,除非抛出异常)

  AfterAdvice: 就好比try..catch..finaly里面的finaly(无论Joinpoint能否正常执行,都会执行到)

  AfterReturningAdvice:在Joinpoint执行流程正常返回后被执行(如果Joinpoint抛出异常,则不会执行)

  AfterThrowingAdvice:在Joinpoint执行过程中抛出异常才会触发(如果trycatch了,是不会到这的)

  AroundAdvice:在Joinpoin前和后都执行,最常用的Advice(常用)

  

 

 

 

 

 

 

 

 


 Introduction——引入型Advice

  为目标类引入新接口,而不需要目标类做任何实现

  使得目标类在使用的过程中转型成新接口对象,调用新接口的方法 

注:

  这种方式的操作,对于代码的阅读理解是比较复杂的

 

使用方法:

首先声明好一个接口,一个实现类

public interface LittleUniverse {
    void burningup();
}
public class LittleUniverseImpl implements LittleUniverse {
    @Override
    public void burningup() {
        System.out.println("燃烧吧小宇宙");
    }
}

 

然后在Aspect中添加上如下代码:

@DeclareParents(value = "com.imooc.controller..*",defaultImpl = com.imooc.introduction.impl.LittleUniverseImpl.class)
    public LittleUniverse littleUniverse;//这样子我们便赋予了每个Controller都能变身为littleUniverse的权力

 

此时我们在容器中getBean获取到了Controller之后,便可以将他强转类型并调用接口的方法。

public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Entrance.class);
        HiController hiController = (HiController)applicationContext.getBean("hiController");
        ((LittleUniverse)hiController).burningup();
}

 


 

SpringAOP 的 实 现 原 理

 

在学习SpringAOP内部的实现原理之前,首先我们要了解代理模式

 

 

通常代理模式由(抽象主题)(被代理角色)(代理角色)三个模块完成

  抽象主题:可以是抽象类或者抽象接口(用户只需要只需要面向抽象主题即可)

  代理类:实现或者继承自抽象主题(实例化的时候使用代理类)(SpringAOP需要做的便是生成这么个代理类,替换掉真正的实现类来提供服务)

  被代理类:实现或者继承自抽象主题

 

代码演示:

首先我们声明一个(个人支付)接口及其接口实现类 (被代理对象)

public interface toCPayment {
    void pay();
}
public class toCPaymentImpl implements toCPayment {
    @Override
    public void pay() {
        System.out.println("以用户的名义进行支付");
    }
}

然后我们对于(个人支付)接口的实现类定义一个类(代理对象)

public class AlipayToC implements toCPayment {
    toCPayment toCPayment;
    public AlipayToC(toCPayment toCPayment){
        this.toCPayment = toCPayment;
    }
    @Override
    public void pay() {
        beforPay();
        toCPayment.pay();
        afterPay();
    }
    private void afterPay() {
        System.out.println("支付给慕课网");
    }
    private void beforPay() {
        System.out.println("从招行取款");
    }
}

 

这时候我们去调用支付方法

public class ProxyDemo {
    public static void main(String[] args) {
       toCPayment toCProxy = new AlipayToC(new toCPaymentImpl());
       toCProxy.pay();
    }
}

执行结果:

  从招行取款
  以用户的名义进行支付
  支付给慕课网

 

-----------------------

上面这种代理模式是静态的代理模式,如果我们要添加多一个(企业支付)接口,即使支付前后的方法一样,只是里头的pay()方法改变,那么我们又要重新按照刚刚的流程走一步。这样也是相对比较麻烦的

这种模式的思路是没有问题的,只是相关的实现方式需要改进。

 

寻求改进:

在改进前我们重新回顾一下ClassLoader(类加载器) 

  溯源ClassLoader

    通过带有包名的类来获取对应class文件的二进制字节流

    根据读取的字节流,将代表的静态存储结构转化为(方法区的)运行时数据结构

    生成一个代表该类的Class对象,作为方法区该类的数据访问入口

 

在复习完ClassLoader之后呢,我们可以根据ClassLoader对切入点进行改进:

  根据一定规则去改动或者生成新的字节流,将切面逻辑织入其中(既然字节流能定义类的行为,我们将切面逻辑织入其中,使其动态生成天生织入了切面逻辑的类。)

     行之有效的方案就是取代被代理类的动态代理机制

      根据接口或者目标类,计算出代理类的字节码并加载到JVM中去

      

SpringAOP的实现原理之JDK动态代理

  JDK1.3之后,java就为我们引入了JDK的动态代理机制

    静态代理,需要在编译前实现,编译完成后,代理类是一个实际的Class文件

    JDK动态代理,是在运行时动态生成的,编译完成后并没有实际的Class文件,而是在运行时动态生成类的字节码,并加载到JVM中(要求【被代理的类】必须实现接口)(并不要求【代理对象】去实现接口,所以可以复用代理对象的逻辑)

    (采用JDK动态代理相比静态代理,逻辑可复用)

其中涉及两个比较重要的类:

  java.lang.reflect.InvocationHandler:并非代理类,只是用来统一管理横切逻辑的Aspect。

  java.lang.reflect.Proxy:真正的代理类是由Proxy创建出来的。该类最关键的方法就是newProxyInstance方法,该方法最终会返回创建好的动态代理实例。

代码演示:

首先创建管理横切逻辑的,将被包装类通过构造函数传递进来。

public class AlipayInvocationHandler implements InvocationHandler {

    private Object targetObject;
    public AlipayInvocationHandler(Object targetObject){
        this.targetObject = targetObject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        beforPay();
        Object result = method.invoke(targetObject,args);
        afterPay();
        return result;
    }
    private void afterPay() {
        System.out.println("支付给慕课网");
    }
    private void beforPay() {
        System.out.println("从招行取款");
    }
}

 

创建工具类,去创建出真正的Proxy

public class jdkDynamicProxyUtil {

    public static <T>T newProxyInstance(T targetObject, InvocationHandler handler){
        ClassLoader classLoader = targetObject.getClass().getClassLoader();
        Class<?>[] interfaces = targetObject.getClass().getInterfaces();
        return (T)Proxy.newProxyInstance(classLoader,interfaces,handler);
    }


}

最后main方法调用

public class ProxyDemo {
    public static void main(String[] args) {
   
        toCPayment toCPayment = new toCPaymentImpl();//首先创建被代理的类
        InvocationHandler handler = new AlipayInvocationHandler(toCPayment);
        toCPayment toCProxy = jdkDynamicProxyUtil.newProxyInstance(toCPayment,handler);
        toCProxy.pay();


        ToBPayment toBPayment = new ToBPaymentImpl();
        InvocationHandler handlerToB = new AlipayInvocationHandler(toBPayment);
        ToBPayment toBProxy =jdkDynamicProxyUtil.newProxyInstance(toBPayment,handlerToB);
        toBProxy.pay();
    }
}

 

SpringAOP的实现原理之CGLIB动态代理

cgLib代码生成库:Code Generation Library

  不要求被代理类实现接口

  内部主要封装了ASM java字节码操控框架(转化字节码产生新类)

  动态生成子类以覆盖非final的方法,绑定钩子回调自定义拦截器  

 

CGLib库中,和动态代理最相关的就是net.sf.cglib.proxy.Proxy类。

  (1)在net.sf.cglib.proxy包下有个类叫做Callback,这个类就是一个比较核心的底层接口了,看名字就知道该类是用来提供回调实现了,该类只是一个空接口(标记),主要干活的是MethodInterceptor类

  (2)MethodInterceptor类中有一个intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)方法,方法的前三个参数跟JDK动态代理的invork方法一样

      1.动态代理对象 2.被代理的方法实例 3.被代理方法实例里需要的参数数组。第四个参数则是动态代理Method的对象

  (3)有了上面这些类,还需要个核心类来创建出最终代理对象(Enhancer),该类的核心方法create(),既可以用动态的方式调用,也可以通过静态(static)的方式调用。

 

 

代码演示:

首先我们创建被代理对象类:

public class CommonPayment {
    public void pay(){
        System.out.println("个人或者公司都可以走这个支付通道");
    }
}

然后创建切面Aspect类

public class AlipayMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        beforPay();
        Object result = methodProxy.invokeSuper(o,args);
        afterPay();
        return result;
    }
    private void afterPay() {
        System.out.println("支付给慕课网");
    }
    private void beforPay() {
        System.out.println("从招行取款");
    }
}

然后针对cglib创建一个工具类

public class CglibUtil {
    public static <T>T createProxy(T targetObject, MethodInterceptor methodInterceptor){
        return (T)Enhancer.create(targetObject.getClass(),methodInterceptor);
    }
}

最后main方法执行

public static void main(String[] args) {
//CGLib动态代理
        CommonPayment commonPayment = new CommonPayment();
        MethodInterceptor methodInterceptor = new AlipayMethodInterceptor();
        CommonPayment commonPaymentProxy = CglibUtil.createProxy(commonPayment,methodInterceptor);
        commonPaymentProxy.pay();
     //哪怕是切换了代理对象也不需要对应的去创建接口
        toCPayment toCPayment = new toCPaymentImpl();
        toCPayment toCProxy = CglibUtil.createProxy(toCPayment,methodInterceptor);
        toCProxy.pay();

    }

 

 

总结:

实现机制:

  JDK动态代理:基于反射机制实现,要求业务类必须实现接口

  CGLIB:基于ASM机制实现,生成业务类的子类作为代理类

优势:

  JDK动态代理:

    JDK原生,在JVM里运行较为可靠

    平滑支持JDK版本的升级

  CGLIB:

    被代理对象无需实现接口,能实现代理类的无侵入

    

在SpringAOP中的底层机制中靠的就是CGLIB和JDK动态代理共存

默认策略:Bean实现了接口则使用JDK,否则使用CGLIB


 AspectJ框架

AspectJ框架是Eclipse托管给apache基金会的一个开源项目,提供了完整的AOP解决方案。AspectJ是AOP的java实现版本,定义了AOP的语法,可以说是对java的扩展。

 

AspectJ提供了两套强大的机制:

  1.定义切面语法以及切面语法的解析机制

  2.提供了强大的织入工具

 

Spring-AOP与AspectJ框架 所支持的织入点:

 

 

如图所示,我们可以看出SpringAOP只支持方法上的织入点,而AspectJ可以说是支持了所有的连接点(所以我们可以称AspectJ是一套AOP的完整解决方案)

既然AspectJ这么强大,为什么Spring没有完整复用呢。因为在方法上的织入已经可以满足大多数的业务,而且在AspectJ的学习与使用上也远比Spring复杂。

 


 

SpringAOP的脉络分析图:

 

posted @ 2021-02-24 21:47  _kerry  阅读(162)  评论(0编辑  收藏  举报