coding++:Spring 中的 AOP 原理

为什么使用 AOP 如下场景:

现在有一个情景:

我们要把大象放进冰箱,步骤为:打开冰箱->放入大象->关闭冰箱

若是再把大象拿出来,步骤为:打开冰箱->拿出大象->关闭冰箱

代码如下:

 public void put() {
        System.out.println("打开冰箱...");
        System.out.println("放入大象...");
        System.out.println("关闭冰箱...");
    }
 
    public void get() {
        System.out.println("打开冰箱...");
        System.out.println("拿出大象...");
        System.out.println("关闭冰箱...");
    }

 

我们需要在每一个拿进拿出操作前后都要举行打开冰箱和关闭冰箱的操作,造成了代码重复。

而若是要拿进拿出其他动物,那么每一个动物的操作都需要加入打开冰箱关闭冰箱的操作,十分繁琐杂乱。

解决方式就是AOP,将这些打开冰箱和关闭冰箱的操作单独抽取出来,做成一个切面,之后挪用任何方式,都插入到方式前后即可。

先来看一些基本概念再来解决这个问题。

基本概念:

AOP   即Aspect Oriented Program,面向切面编程

使用AOP手艺,可以将一些系统性相关的编程事情,自力提取出来,自力实现,然后通过切面切入进系统。

从而避免了在营业逻辑的代码中混入许多的系统相关的逻辑——好比权限治理,事物治理,日志纪录等等。

这些系统性的编程事情都可以自力编码实现,然后通过AOP手艺切入进系统即可。从而达到了 将差别的关注点分离出来的效果。

切面(Aspect):实在就是共有功效的实现。

          如日志切面、权限切面、事务切面等。

           在现实应用中通常是一个存放共有功效实现的通俗Java类,之以是能被AOP容器识别成切面,是在设置中指定的。

通知/增强(Advice):是切面的详细实现。以目的方式为参照点,凭据放置的地方差别,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与围绕通知(Around)5种。

            在现实应用中通常是切面类中的一个方式,详细属于哪类通知,同样是在设置中指定的。

连接点(Joinpoint):就是程序在运行历程中能够插入切面的地址。

            例如,方式挪用、异常抛出或字段修改等,但Spring只支持方式级的连接点。

切入点(Pointcut):用于界说通知应该切入到哪些连接点上。

          差别的通知通常需要切入到差别的连接点上,这种精准的匹配是由切入点的正则表达式来界说的。

目的工具(Target):就是那些即将切入切面的工具,也就是那些被通知的工具。

                    这些工具中已经只剩下干干净净的焦点营业逻辑代码了,所有的共有功效代码守候AOP容器的切入。

署理工具(Proxy):将通知应用到目的工具之后被动态建立的工具。

          可以简朴地理解为,署理工具的功效即是目的工具的焦点营业逻辑功效加上共有功效。

           署理工具对于使用者而言是透明的,是程序运行历程中的产物。

织入(Weaving):将切面应用到目的工具从而建立一个新的署理工具的历程。

         这个历程可以发生在编译期、类装载期及运行期,固然差别的发生点有着差别的前提条件。

         譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态署理机制来动态实现。

AOP 原理:

AOP 署理可分为静态署理和动态署理两大类,

静态署理:使用 AOP 框架提供的下令举行编译,从而在编译阶段就可天生 AOP 署理类,因此也称为编译时增强;

HashMap源码剖析

动态署理:在运行时借助于 JDK 动态署理、CGLIB(code generate libary)字节码天生手艺 等在内存中“暂且”天生 AOP 动态署理类,因此也被称为运行时增强

Spring AOP接纳的是动态署理,在运行时代对营业方式举行增强,以是不会天生新类。

对于动态署理手艺,Spring AOP提供了对JDK动态署理的支持以及CGLib的支持。

前者是基于反射手艺的实现,后者是基于继续的机制实现。

若是目的工具有实现接口,使用jdk署理。

若是目的工具没有实现接口,则使用Cglib署理。

 

JDK:动态署理:

JDK动态署理需要获得被目的类的接口信息(应用Java的反射),天生一个实现了署理接口的动态署理类(字节码),再通过反射机制获得动态署理类的组织函数,行使组织函数天生动态署理类的实例工具,在挪用详细方式前挪用

invokeHandler方式来处置。

主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方式。

JDK动态署理要求被署理的类实现一个接口,只有接口中的方式才能够被署理 。

其方式是将被署理工具注入到一个中心工具,而中心工具实现InvocationHandler接口,在实现该接口时,可以在被署理工具挪用它的方式时,在挪用的前后插入一些代码。

而 Proxy.newProxyInstance() 能够行使中心工具来生产署理工具。

插入的代码就是切面代码。以是使用JDK动态署理可以实现AOP。

现在演示一下若何使用JDK动态署理实现开头的情景

JDK动态署理需要被署理类实现一个接口,先写一个接口。

public interface AnimalOperation {
    public void put();
    public void get();
}

再写一个类(要被署理的类),实现这个接口

public class ElephantOperation implements AnimalOperation{
 
    public void put() {
        System.out.println("放入大象...");
    }
 
    public void get() {
        System.out.println("拿出大象...");
    }
}

然后写一个类来实现InvocationHandler接口,在该类中对被署理类的方式做增强,并编写天生署理工具的方式

public class FridgeJDKProxy implements InvocationHandler{
    //被署理的工具,之后用反射挪用被署理方式的时刻需要被署理工具的引用
    private Object target;
 
    //InvocationHandler接口的方式,
    // proxy是署理工具,method是被署理的方式,args是被署理方式的参数,返回值是原方式的返回
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        openDoor();//挪用被署理方式做一些操作
        Object result = method.invoke(target, args);//执行被署理工具的方式,若是方式有返回值则赋值给result
        closeDoor();//挪用被署理方式后做一些操作
        return result;
    }
    private void openDoor(){
        System.out.println("打开冰箱...");
    }
    private void closeDoor(){
        System.out.println("关闭冰箱...");
    }
    public Object getProxy(Object target){
        this.target=target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

其中Proxy.newProxyInstance()方式需要的参数分别为,类加载器ClassLoader loader,接口数组Class<?>[] interfaces,与 InvocationHandler 

测试代码为:

  public static void main(String args[]) {
      AnimalOperation elephantOperation =(AnimalOperation) new FridgeJDKProxy().getProxy(new ElephantOperation());
      elephantOperation.put();
      elephantOperation.get();
  }

打印效果:

coding++:Spring 中的 AOP 原理

 

CGLIB 动态署理:

字节码天生手艺实现AOP,实在就是继续被署理工具,然后Override需要被署理的方式,在笼罩该方式时,自然是可以插入我们自己的代码的。

CGLib动态署理需要依赖asm包,把被署理工具类的class文件加载进来,修改其字节码天生子类。

由于需要Override被署理工具的方式,以是自然CGLIB手艺实现AOP时,就 必须要求需要被署理的方式不能是final方式,由于final方式不能被子类笼罩 。

现在演示一下若何使用CGLIB动态署理实现开头的情景

CGLIB动态署理不要求被署理类实现接口,先写一个被署理类。

public class MonkeyOperation {
    public void put() {
        System.out.println("放入猴子...");
    }
 
    public void get() {
        System.out.println("拿出猴子...");
    }
}

在写一个类实现MethodInterceptor接口,并在接口方式intercept()里对被署理工具的方式做增强,并编写天生署理工具的方式

public class FridgeCGLibProxy implements MethodInterceptor {
 
    public String name="hahaha";
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        openDoor();//挪用被署理方式做一些操作
        Object result = methodProxy.invokeSuper(proxy,args);//执行被署理工具的方式,若是方式有返回值则赋值给result
        closeDoor();//挪用被署理方式后做一些操作
        return result;
    }
    private void openDoor(){
        System.out.println("打开冰箱...");
    }
    private void closeDoor(){
        System.out.println("关闭冰箱...");
    }
    public Object getProxy(Class cls){//参数为被署理的类工具
        Enhancer enhancer = new Enhancer();//建立增强器,用来建立动态署理类
        enhancer.setSuperclass(cls);//设置父类,即被署理的类工具
        enhancer.setCallback(this);//设置回调,指定为当前工具
        return enhancer.create();//返回天生的署理类
    }
}

测试代码:

  public static void main(String args[]) {
      MonkeyOperation monkeyOperation =(MonkeyOperation)new FridgeCGLibProxy().getProxy(MonkeyOperation.class);
      monkeyOperation.put();
      monkeyOperation.get();
  }

打印效果:

coding++:Spring 中的 AOP 原理

spring实现AOP,若是被署理工具实现了接口,那么就使用JDK的动态署理手艺,反之则使用CGLIB来实现AOP,以是 Spring默认是使用JDK的动态署理手艺实现AOP的 。

 

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/4983.html