• Spring基础知识之Aop动态代理


    代理模式:

      代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息,代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务,简单的说就是,我们在访问实际对象时,是通过代理对象来访问的.代理模式就是在访问实际对象时,引入一定程度的间接性,因为这种间接性,可以附加多种用途.

    代理模式结构图(图片来自<<大话设计模式>>)

     静态代理:

      静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来,在程序运行之前,代理类.class文件已经生成.

    静态代理简单实现:

      1.首先创建一个接口,这个接口是代理类和被代理类公共的接口,它们有一个共同的实现方法.

    1 public interface Person {
    2     // 上交班费
    3     void giveMoney();
    4 }

      2.创建两个实现类

     1 public class Student implements Person {
     2     private String name;
     3     public Student(String name){
     4         this.name = name;
     5     }
     6 
     7     public void giveMoney() {
     8         System.out.println("student: "+name+" giveMoney");
     9     }
    10 }
     1 public class StudentProxy implements Person {
     2     //被代理的学生
     3     Student student;
     4     public StudentProxy(Person student){
     5         // 只代理学生对象
     6         if(student.getClass() == Student.class){
     7             this.student = (Student)student;
     8         }
     9     }
    10     // 实现被代理类的方法
    11     public void giveMoney() {
    12         student.giveMoney();
    13     }
    14 }

      3.测试类

     1 public class Test {
     2     public static void main(String[] args) {
     3         //被代理对象
     4         Person student = new Student("张三");
     5         //生成代理对象,并将张三传给代理对象
     6         Person monitor = new StudentProxy(student);
     7         //代理对象调用方法,间接调用被代理对象的方法
     8         monitor.giveMoney();
     9     }
    10 }

      4.测试结果

       这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式.

      代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。

     1 public class StudentProxy implements Person {
     2     //被代理的学生
     3     Student student;
     4     public StudentProxy(Person student){
     5         // 只代理学生对象
     6         if(student.getClass() == Student.class){
     7             this.student = (Student)student;
     8         }
     9     }
    10     // 实现被代理类的方法
    11     public void giveMoney() {
    12         System.out.println("在执行被代理类方法之前执行");
    13         student.giveMoney();
    14     }
    15 }

      可以看到,只需要在代理类中执行被代理方法之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

    动态代理:

       代理类在程序运行时创建的代理方式被称为动态代理。动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。

    动态代理的实现:

      在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

      1.创建一个接口

    1 public interface Person {
    2     void giveMoney();
    3 }

      2.创建一个需要被代理的实际类

    public class Student implements Person {
        private String name;
    
        public Student(String name) {
            this.name = name;
        }
    
        public void giveMoney() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("student "+name +" giveMoney");
        }
    }

      3定义一个检测方法执行时间的工具类

     1 public class MonitorUtil {
     2     private static ThreadLocal<Long> t1 = new ThreadLocal<Long>();
     3     public static void start(){
     4         t1.set(System.currentTimeMillis());
     5     }
     6     //结束打印时耗时
     7     public static void finish(String methodName){
     8         long finishTime = System.currentTimeMillis();
     9         System.out.println(methodName+"方法耗时:"+(finishTime - t1.get())+"ms");
    10     }
    11 }

      4.创建StudentInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。再在invoke方法中执行被代理对象target的相应方法.

     1 public class StudentInvocationHandler<T> implements InvocationHandler {
     2     // invocationHandler持有的被代理对象
     3     T target;
     4 
     5     public StudentInvocationHandler(T target) {
     6         this.target = target;
     7     }
     8 
     9     /**
    10      *
    11      * @param proxy 代表动态代理对象
    12      * @param method 代表正在执行的方法
    13      * @param args 代表调用目标方法时传入的实参
    14      * @return
    15      * @throws Throwable
    16      */
    17     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    18         System.out.println("代理执行"+method.getName()+"方法");
    19         //代理过程插入检测方法,计算方法耗时
    20         MonitorUtil.start();
    21         Object result = method.invoke(target,args);
    22         MonitorUtil.finish(method.getName());
    23         return result;
    24     }
    25 }

      5.创建测试类:

     1 public class Test {
     2     public static void main(String[] args) {
     3         //创建一个实例对象,这个对象是被代理的对象
     4         Person student = new Student("张三");
     5         //创建一个与代理对象相关联的invocationHandler
     6         InvocationHandler stuHandler = new StudentInvocationHandler<Person>(student);
     7         //创建一个代理对象stuProxy来代理student,代理对象的每个执行方法都会替换执行InvocationHandler的invoke()方法
     8         Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class[]{Person.class},stuHandler);
     9         //代理执行方法
    10         stuProxy.giveMoney();
    11     }
    12 }

      6.上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法.

       7.总结:

      动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了,动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,因为动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的

      Java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。

    静态代理和动态代理的区别:

      静态代理:编译时将增强代码植入class文件,因为是编译期进行的增强,所以代码运行时效率比动态代理高,

      动态代理:运行时生成代理类并加载,效率比静态代理要低,spring中使用了JDK动态代理和cglib两种动态代理的方式来实现代理类的生成.

    CGlib代理:

      CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口

      CGLIB是一个强大的高性能的代码生成包。它广泛的被许多AOP的框架使用,例如Spring AOP为他们提供方法的interception(拦截)。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

      CGLIB是无法代理final修饰的方法的.

    CGlib的简单实现:

      1.创建一个业务类,这个类没有实现任何接口.

     1 public class HelloService {
     2  
     3     public HelloService() {
     4         System.out.println("HelloService构造");
     5     }
     6  
     7     /**
     8      * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
     9      */
    10     final public String sayOthers(String name) {
    11         System.out.println("HelloService:sayOthers>>"+name);
    12         return null;
    13     }
    14  
    15     public void sayHello() {
    16         System.out.println("HelloService:sayHello");
    17     }
    18 }

      2.自定义一个MethodInterceptor方法实现MethodInterceptor接口;

     1 public class MyMethodInterceptor implements MethodInterceptor{
     2  
     3     /**
     4      * sub:cglib生成的代理对象
     5      * method:被代理对象方法
     6      * objects:方法入参
     7      * methodProxy: 代理方法
     8      */
     9     @Override
    10     public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    11         System.out.println("======插入前置通知======");
    12         Object object = methodProxy.invokeSuper(sub, objects);
    13         System.out.println("======插入后者通知======");
    14         return object;
    15     }
    16 }

      3.测试类:

     1 public class Client {
     2     public static void main(String[] args) {
     3         // 代理类class文件存入本地磁盘方便我们反编译查看源码
     4         System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\code");
     5         // 通过CGLIB动态代理获取代理对象的过程
     6         Enhancer enhancer = new Enhancer();
     7         // 设置enhancer对象的父类
     8         enhancer.setSuperclass(HelloService.class);
     9         // 设置enhancer的回调对象
    10         enhancer.setCallback(new MyMethodInterceptor());
    11         // 创建代理对象
    12         HelloService proxy= (HelloService)enhancer.create();
    13         // 通过代理对象调用目标方法
    14         proxy.sayHello();
    15     }
    16 }

      4.运行结果:

       5.总结:

      实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口,这个接口只有一个intercept()方法,这个方法有4个参数:

      1)obj表示增强的对象,即实现这个接口类的一个对象;

      2)method表示要被拦截的方法;

      3)args表示要被拦截方法的参数;

      4)proxy表示要触发父类的方法对象;

      在上面的Client代码中,通过Enhancer.create()方法创建代理对象
    JDK动态代理和CGlib代理的区别:

      JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理.

      CGlib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理.

    AOP的实现:

      1.如果目标对象实现了接口,默认情况下会采用JDK动态代理实现AOP

      2.如果目标对象实现了接口,可以强制使用CGlib实现AOP,实现方法

        1.添加CGlib库,SPRING_HOME/cglib/*.jar

        2,在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

      3.如果目标对象没有实现接口,必须采用CGlib库,Spring会自动在JDK动态代理和CGlib之间转换.

  • 相关阅读:
    Pytest单元测试框架——Pytest+Allure+Jenkins的应用
    Postman+Newman+Git+Jenkins接口自动化测试
    Pytest单元测试框架——Pytest简介
    unittest单元测试框架
    Postman学习笔记(二)
    CukeTest+Puppeteer的Web自动化测试(二)
    Postman学习笔记(一)
    CukeTest+Puppeteer的Web自动化测试(一)
    Puppeteer笔记(八):Puppeteer执行自定义Javascript方法
    Puppeteer笔记(七):Puppeteer切换浏览器TAB页
  • 原文地址:https://www.cnblogs.com/wk-missQ1/p/12643353.html
Copyright © 2020-2023  润新知