关于aop:AOP如何实现及实现原理

3次阅读

共计 8102 个字符,预计需要花费 21 分钟才能阅读完成。

1. AOP 简介

置信大家或多或少的理解过 AOP,都晓得它是面向切面编程,在网上搜寻能够找到很多的解释。这里我用一句话来总结:AOP 是可能让咱们在不影响原有性能的前提下,为软件横向扩大性能。那么横向扩大怎么了解呢,咱们在 WEB 我的项目开发中,通常都恪守三层准则,包含管制层(Controller)-> 业务层(Service)-> 数据层(dao), 那么从这个构造下来的为纵向,它具体的某一层就是咱们所说的横向。咱们的 AOP 就是能够作用于这某一个横向模块当中的所有办法。

咱们在来看一下 AOP 和 OOP 的区别:AOP 是 OOP 的补充,当咱们须要为多个对象引入一个公共行为,比方日志,操作记录等,就须要在每个对象中援用公共行为,这样程序就产生了大量的反复代码,应用 AOP 能够完满解决这个问题。

接下来介绍一下提到 AOP 就必须要理解的知识点:

切面 :拦截器类,其中会定义切点以及告诉
切点 :具体拦挡的某个业务点。
告诉:切面当中的办法,申明告诉办法在指标业务层的执行地位,告诉类型如下:

1. 前置告诉:@Before 在指标业务办法执行之前执行
2. 后置告诉:@After 在指标业务办法执行之后执行
3. 返回告诉:@AfterReturning 在指标业务办法返回后果之后执行
4. 异样告诉:@AfterThrowing 在指标业务办法抛出异样之后
5. 盘绕告诉:@Around 功能强大,可代替以上四种告诉,还能够控制目标业务办法是否执行以及何时执行

2. 代码中实现举例

下面曾经大略的介绍了 AOP 中须要理解的基本知识,也晓得了 AOP 的益处,那怎么在代码中实现呢?给大家举个例子:咱们当初有个学校管理系统,曾经实现了对老师和学生的增删改,又新来个需要,说是对老师和学生的每次增删改做一个记录,到时候校长能够查看记录的列表。那么问题来了,怎么样解决是最好的解决办法呢?这里我列举了三种解决办法,咱们来看下他的优缺点。

- 最简略的就是第一种办法,咱们间接在每次的增删改的函数当中间接实现这个记录的办法,这样代码的反复度太高,耦合性太强,不倡议应用。

- 其次就是咱们最长应用的,将记录这个办法抽离进去,其余的增删改调用这个记录函数即可,显然代码反复度升高,然而这样的调用还是没有升高耦合性。

- 这个时候咱们想一下 AOP 的定义,再想想咱们的场景,其实咱们就是要在不扭转原来增删改的办法,给这个零碎减少记录的办法,而且作用的也是一个层面的办法。这个时候咱们就能够采纳 AOP 来实现了。

咱们来看下代码的具体实现:

  1. 首先我定义了一个自定义注解作为切点
@Target(AnnotationTarget.FUNCTION)  // 注解作用的范畴,这里申明为函数
@Order(Ordered.HIGHEST_PRECEDENCE)  // 申明注解的优先级为最高,假如有多个注解,先执行这个
annotation class Hanler(val handler: HandlerType)  // 自定义注解类,HandlerType 是一个枚举类型,外面定义的就是学生和老师的增删改操作,在这里就不展现具体内容了
  1. 接下来就是要定义切面类了

    @Aspect   // 该注解申明这个类为一个切面类
    @Component
    class HandlerAspect{
     
      @Autowired
     private lateinit var handlerService: HandlerService
     
     @AfterReturning("@annotation(handler)")   // 当有函数正文了注解,将会在函数失常返回后在执行咱们定义的办法
     fun hanler(hanler: Hanler) {handlerService.add(handler.operate.value)   // 这里是真正执行记录的办法
     }
    }
  2. 最初就是咱们原本的业务办法了

    /**
     * 删除学生办法
     */
    @Handler(operate= Handler.STUDENT_DELETE)   // 当执行到删除学生办法时,切面类就会起作用了, 当学生失常删除后就会执行记录办法,咱们就能够看到记录办法生成的数据
    fun delete(id:String) {studentService.delete(id)
    }
    
  3. AOP 实现原理
    咱们当初理解了代码中如何实现,那么 AOP 实现的原理是什么呢?之前看了一个博客说到,提到 AOP 大家都晓得他的实现原理是动静代理,显然我之前就是不晓得的,哈哈,然而置信阅读文章的你们肯定是晓得的。

讲到动静代理就不得不说代理模式了,代理模式的定义:给某一个对象提供一个代理,并由代理对象管制对原对象的援用。代理模式蕴含如下角色:subject:形象主题角色,是一个接口。该接口是对象和它的代理共用的接口; RealSubject:实在主题角色,是实现形象主题接口的类; Proxy: 代理角色,外部含有对实在对象 RealSubject 的援用,从而能够操作实在对象。代理对象提供与实在对象雷同的接口,以便代替实在对象。同时,代理对象能够在执行实在对象操作时,附加其余的操作,相当于对实在对象进行封装。如下图所示:

那么代理又分为动态代理和动静代理,这里写两个小的 demo,动静代理采纳的就是 JDK 代理。举个例子就是当初一个班上的学生须要交作业,当初由班长代理交作业,那么班长就是代理,学生就是被代理的对象。

3.1 动态代理
首先,咱们创立一个 Person 接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就能够让班长来代理执行。


/**

 * Created by Mapei on 2018/11/7

 * 创立 person 接口

 */

public interface Person {

    // 交作业

    void giveTask();}

Student 类实现 Person 接口,Student 能够具体实施交作业这个行为


/**

 * Created by Mapei on 2018/11/7

 */

public class Student implements Person {

    private String name;

    public Student(String name) {this.name = name;}



    public void giveTask() {System.out.println(name + "交语文作业");

    }

}

StudentsProxy 类,这个类也实现了 Person 接口,然而还另外持有一个学生类对象,那么他能够代理学生类对象执行交作业的行为。


/**

 * Created by Mapei on 2018/11/7

 * 学生代理类,也实现了 Person 接口,保留一个学生实体,这样就能够代理学生产生行为

 */

public class StudentsProxy implements Person{

    // 被代理的学生

    Student stu;



    public StudentsProxy(Person stu) {

        // 只代理学生对象

        if(stu.getClass() == Student.class) {this.stu = (Student)stu;

        }

    }



    // 代理交作业,调用被代理学生的交作业的行为

    public void giveTask() {stu.giveTask();

    }

}

上面测试一下,看代理模式如何应用:

/**
 * Created by Mapei on 2018/11/7
 */
public class StaticProxyTest {public static void main(String[] args) {

        // 被代理的学生林浅,他的作业上交有代理对象 monitor 实现

        Person linqian = new Student("林浅");



        // 生成代理对象,并将林浅传给代理对象

        Person monitor = new StudentsProxy(linqian);



        // 班长代理交作业

        monitor.giveTask();}

}

运行后果:

这里并没有间接通过林浅(被代理对象)来执行交作业的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。代理模式就是在拜访理论对象时引入肯定水平的间接性,这里的间接性就是指不间接调用理论对象的办法,那么咱们在代理过程中就能够加上一些其余用处。比方班长在帮林浅交作业的时候想通知老师最近林浅的提高很大,就能够轻松的通过代理模式办到。在代理类的交作业之前退出办法即可。这个长处就能够使用在 spring 中的 AOP,咱们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个办法。这些办法所在类必定就是被代理了,在代理过程中切入了一些其余操作。

3.2 动静代理

动静代理和动态代理的区别是,动态代理的的代理类是咱们本人定义好的,在程序运行之前就曾经变异实现,然而动静代理的代理类是在程序运行时创立的。相比于动态代理,动静代理的劣势在于能够很不便的对代理类的函数进行对立的解决,而不必批改每个代理类中的办法。比方咱们想在每个代理办法之前都加一个解决办法,咱们下面的例子中只有一个代理办法,如果还有很多的代理办法,就太麻烦了,咱们来看下动静代理是怎么去实现的。

首先还是定义一个 Person 接口:


/**
 * Created by Mapei on 2018/11/7
 * 创立 person 接口
 */
public interface Person {

    // 交作业

    void giveTask();}

接下来是创立须要被代理的理论类,也就是学生类:


/**

 * Created by Mapei on 2018/11/7

 */

public class Student implements Person {

    private String name;

    public Student(String name) {this.name = name;}



    public void giveTask() {System.out.println(name + "交语文作业");

    }

}

创立 StuInvocationHandler 类,实现 InvocationHandler 接口,这个类中持有一个被代理对象的实例 target。InvocationHandler 中有一个 invoke 办法,所有执行代理对象的办法都会被替换成执行 invoke 办法。

/**
 * Created by Mapei on 2018/11/7
 */
public class StuInvocationHandler<T> implements InvocationHandler {

    //invocationHandler 持有的被代理对象

    T target;



    public StuInvocationHandler(T target) {this.target = target;}



    /**

     * proxy: 代表动静代理对象

     * method:代表正在执行的办法

     * args:代表调用指标办法时传入的实参

     */

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理执行" +method.getName() + "办法");

        Object result = method.invoke(target, args);

        return result;

    }

}

那么接下来咱们就能够具体的创立代理对象了。


/**
 * Created by Mapei on 2018/11/7
 * 代理类
 */
public class ProxyTest {public static void main(String[] args) {



        // 创立一个实例对象,这个对象是被代理的对象

        Person linqian = new Student("林浅");



        // 创立一个与代理对象相关联的 InvocationHandler

        InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);



        // 创立一个代理对象 stuProxy 来代理 linqian,代理对象的每个执行办法都会替换执行 Invocation 中的 invoke 办法

        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);



        // 代理执行交作业的办法

        stuProxy.giveTask();}

}

咱们执行代理测试类,首先咱们创立了一个须要被代理的学生林浅,将林浅传入 stuHandler 中,咱们在创立代理对象 stuProxy 时,将 stuHandler 作为参数,那么所有执行代理对象的办法都会被替换成执行 invoke 办法,也就是说,最初执行的是 StuInvocationHandler 中的 invoke 办法。所以在看到上面的运行后果也就天经地义了。

那么到这里问题就来了,为什么代理对象执行的办法都会通过 InvocationHandler 中的 invoke 办法来执行,带着这个问题,咱们须要看一下动静代理的源码,对他进行简略的剖析。

下面咱们应用 Proxy 类的 newProxyInstance 办法创立了一个动静代理对象,看一下他的源码:


public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {Objects.requireNonNull(h);



        final Class<?>[] intfs = interfaces.clone();

        final SecurityManager sm = System.getSecurityManager();

        if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);

        }



        /*

         * Look up or generate the designated proxy class.

         */

        Class<?> cl = getProxyClass0(loader, intfs);



        /*

         * Invoke its constructor with the designated invocation handler.

         */

        try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);

            }



            final Constructor<?> cons = cl.getConstructor(constructorParams);

            final InvocationHandler ih = h;

            if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);

                        return null;

                    }

                });

            }

            return cons.newInstance(new Object[]{h});

        } catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);

        } catch (InvocationTargetException e) {Throwable t = e.getCause();

            if (t instanceof RuntimeException) {throw (RuntimeException) t;

            } else {throw new InternalError(t.toString(), t);

            }

        } catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);

        }

  }

而后,咱们须要重点关注 Class<?> cl = getProxyClass0(loader, intfs)这句代码,这里产生了代理类,这个类就是动静代理的要害,因为是动静生成的类文件,咱们将这个类文件打印到文件中。


 byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());

        String path = "/Users/mapei/Desktop/okay/65707.class";



        try{FileOutputStream fos = new FileOutputStream(path);

            fos.write(classFile);

            fos.flush();

            System.out.println("代理类 class 文件写入胜利");

        }catch (Exception e) {System.out.println("写文件谬误");

        }

对这个 class 文件进行反编译,咱们看看 jdk 为咱们生成了什么样的内容:

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.UndeclaredThrowableException;

import proxy.Person;



public final class $Proxy0 extends Proxy implements Person

{

  private static Method m1;

  private static Method m2;

  private static Method m3;

  private static Method m0;

  

  /**

  * 留神这里是生成代理类的构造方法,办法参数为 InvocationHandler 类型,看到这,是不是就有点明确

  * 为何代理对象调用办法都是执行 InvocationHandler 中的 invoke 办法,而 InvocationHandler 又持有一个

  * 被代理对象的实例,就能够去调用真正的对象实例。*/

  public $Proxy0(InvocationHandler paramInvocationHandler)

    throws 

  {super(paramInvocationHandler);

  }

  

  // 这个动态块原本是在最初的,我把它拿到后面来,不便形容

   static

  {

    try

    {

      // 看看这儿动态块儿外面的住 giveTask 通过反射失去的名字 m3,其余的先不论

      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {Class.forName("java.lang.Object") });

      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

      m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);

      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

      return;

    }

    catch (NoSuchMethodException localNoSuchMethodException)

    {throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

    }

    catch (ClassNotFoundException localClassNotFoundException)

    {throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

    }

  }
正文完
 0