zoukankan      html  css  js  c++  java
  • 动态代理

    AOP的原理就是java的动态代理机制

    通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:

    • 优点一:可以隐藏委托类的实现;

    • 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。

    静态代理:

      若代理类在程序运行前就已经存在,那么这种代理方式被成为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下,静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。

           静态代理可以通过聚合来实现,让代理类持有一个委托类的引用即可。

    动态代理:

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

           比如: 现在,假设我们要实现这样一个需求:在执行委托类中的方法之前输出“before”,在执行完毕后输出“after”。通过静态代理实现我们的需求需要我们在每个方法中都添加相应的逻辑,这里只存在两个方法所以工作量还不算大,假如包含上百个方法呢?这时候使用静态代理就会编写许多冗余代码。通过使用动态代理,我们可以做一个“统一指示”,从而对所有代理类的方法进行统一处理,而不用逐一修改每个方法

      有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

    每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }

    proxy:指代我们所代理的那个真实对象

    method:指代的是我们所要调用真实对象的某个方法的Method对象

    args:指代的是调用真实对象某个方法时接受的参数

    Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

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

    loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载

    interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了

    h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

    好了,在介绍完这两个接口(类)以后,我们来通过一个实例来看看我们的动态代理模式是什么样的:

    接口:在java中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口:

    package dynamicProxy;
    
    public interface Subject {
        void rent();
    
        void hello(String str);
    }

    委托类(被代理类)实现接口:

    package dynamicProxy;
    
    public class RealSubject implements Subject {
        @Override
        public void rent() {
            System.out.println("I want to rent my house");
        }
    
        @Override
        public void hello(String str) {
            System.out.println("hello: " + str);
        }
    }

    代理类(一定要实现InvocationHandler , 但是不需要实现委托类的接口Subject):

      其包含一个委托类的引用,在构造函数中为其赋值,代理类是在运行时构造的。invoke函数中转为对实际委托类的调用,并且在调用之前和之后添加一些自己的操作。

    package dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class DynamicProxy implements InvocationHandler {
    
        // 这个就是我们要代理的真实对象
        private Object subject;
        //    构造方法,给我们要代理的真实对象赋初值
        public DynamicProxy(Object subject)
        {
            this.subject = subject;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //  在代理真实对象前我们可以添加一些自己的操作
            System.out.println("before rent house");
    
            System.out.println("Method:" + method);
    
            //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            method.invoke(subject, args);
    
            //  在代理真实对象后我们也可以添加一些自己的操作
            System.out.println("after rent house");
    
            return null;
        }
    }

    最后看,是如何调用的:

    package dynamicProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Client {
    
        public static void main(String[] args) {
            // 我们要代理的真实对象
            Subject realSubject = new RealSubject();
    
            // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
            InvocationHandler handler = new DynamicProxy(realSubject);
    
            /*
             * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
             * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
             * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
             * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
             */
            Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);
    
            System.out.println(subject.getClass().getName());
            subject.rent();
            subject.hello("world");
        }
    }

     上面需要重点说明的:

      1 通过"Proxy"类提供的一个newProxyInstance方法用来创建一个对象的代理对象. 注意生成的subject不是代理类实例,是一个临时生成的对象。

      newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。所以我们只需要调用newProxyInstance方法就可以得到某一个对象的代理对象了。

      2 在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

      3 为什么我们这里可以将newProxyInstance返回的东西转化为Subject类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是Subject类型,所以就可以将其转化为Subject类型了。

      4 通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

      5 这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行

  • 相关阅读:
    JS LeetCode 1423. 可获得的最大点数简单题解
    SpringBoot 学集 (第六章) Docker
    Linux 学记 (第三章)
    Linux 学记 (第二章)
    Linux 学记 (第一章)
    SpringBoot 学集 (第五章) Web开发续
    SpringBoot 学集 (第四章)Web开发
    SpringBoot 学集 (第三章) 日志框架
    SpringBoot 学集 (第二章) 配置文件
    SpringBoot 学集 (第一章)
  • 原文地址:https://www.cnblogs.com/liufei1983/p/9750362.html
Copyright © 2011-2022 走看看