• java代理Proxy以及实际PRC场景中的使用


    代理(Proxy)

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.

    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

    定义:给目标对象提供一个代理对象,并且由代理对象控制对目标对象的引用

    目的

        ①:通过代理对象的方式间接的访问目标对象,防止直接访问目标对象给系统带来不必要的复杂性
        ②:通过代理业务对原有业务进行增强
    
    • 1
    • 2

    java当中有三种方式来创建代理对象:

    • 静态代理
    • 基于jdk(接口)的动态代理
    • 基于CGLLIB(父类)的动态代理。

    静态代理

    创建一个 Image 接口和实现了 Image 接口的实现类。ProxyImage 是一个代理类,屏蔽具体的实现过程并且提供比真实实现过程更多的前后增强操作。

    ProxyPatternDemo 类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示

    image.png

    public interface Image { 
        void display(); 
    }
    
    • 1
    • 2
    • 3
    public class RealImage implements Image { 
        private String fileName; 
        
        public RealImage(String fileName){ 
        this.fileName = fileName; loadFromDisk(fileName); 
        } 
        
        @Override public void display() {
        System.out.println("Displaying " + fileName);
        }
          private void loadFromDisk(String fileName){
        System.out.println("Loading " + fileName);
        } 
     }
     
     
     /**proxy**/
     public class ProxyImage implements Image{ 
     private RealImage factory; 
     private String fileName; 
     
    	public proxy(String fileName) {
    		// TODO Auto-generated constructor stub
                    if(realImage == null){
                     factory = new RealImage(fileName); 
                     } 
    		this.fileName = fileName;
    	}
    
     
             @Override public void display() { 
                 //before
                 System.out.println("image before.... ");
                 realImage.display(); 
                 System.out.println("image after.... ");
                 //after
                 
             } 
     
     }
     
     public class ProxyTestDemo { 
         public static void main(String[] args) { 
         Image image = new ProxyImage("test_10mb.jpg"); // 图像将从磁盘加载 image.display();  
         // 图像不需要从磁盘加载 
         image.display(); 
         } 
     }
     
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    细节:我们的真实实现对象必须实现我们的接口,同时代理对象也必须实现这一接口

    静态代理存在哪些问题?

    静态代理类优缺点:

    优点:可以在不修改目标代码的情况下,扩展额外功能

    缺点:因为代理类和目标类必须实现相同的接口,所以会有很多代理类,类太多,同时,一旦接口增加方法,目标对象很代理对象都需要维护

    违反了开闭原则:程序对访问开放,对修改关闭,换句话来说,当需求发生变化时,我们可以增加新模块来解决新需求,而不是通过改变原来的代码来解决我们的新需求,这里我们的新需求都是通过ProxyImage中添加代码实现的。

    假设image是一个定义的播放工具,如同数据库工具定义的规范。现在出了播放图片,又要新增一个播放视频的功能vedioImage,当前ProxyImage里面定义的RealImage是写死的。难道要新增一个vedioImage变量么,类似的在数据库驱动程序中也存在类似现象,javajdk只会定义广义统一的流程接口规范,每个数据库厂商根据该jdk接口自己去实现自家数据库的链接驱动。因此调用驱动时底层实现链接逻辑并不指定具体的实现类型,而是由上层启动类在外部指定,从而动态的加载相应的驱动类,并执行对应的方法流程。

    JDK动态代理

    动态代理的主要特点就是能够在程序运行时JVM才为目标对象生成代理对象。

    常说的动态代理也叫做JDK 代理也是一种接口代理,之所以叫做接口代理,是因为被代理的对象也就是目标对象必须实现至少一个接口,没有实现接口不能使用这种方式生成代理对象,JDK中生成代理对象的代理类就是Proxy,所在包是java.lang.reflect.

    import java.lang.annotation.Target;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
     
    /**
    *动态代理工厂:不具体指定代理对象,代理类也不用实现代理接口,由底层JDK根据代理接口类型自动生成代理对象
     * @author bamboo
     * @description:
     * @date 2020/12/14
     */
    public class ProxyFactory implements InvocationHandler {
     
        private  Object target;
        public StarInvocationHandler(Object target) {
            this.target = target;
        }
     
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     
            System.out.println("展示前工作");
            //调用目标对象的目标方法
            method.invoke(target,args);
            System.out.println("展示后工作");
            return null;
        }
        
        //代理对象初始化:利用反射创建代理对象
        public Object getProxyInstance() {
    		// TODO Auto-generated method stub
    		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    	}
    }
    
    
    
    public class JdkTest {
    	public static void main(String[] args) {
                    //由调用层决定具体由谁来执行
                    RealImage realImage = new RealImage();//1
    		Image image =(Image) new ProxyFactory(realImage).getProxyInstance();
    		image.display("d://image.jpg");
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    JDK代理方式不需要代理对象实现接口,但是实现对象一定要实现接口,但是我们在项目中有很多需要代理的类并没有实现接口,所以这也算是这种代理方式的一种缺陷

    优点:代理类和实现类脱离了依赖关联,不用强绑定,完全由调用方使用时动态反射出具体的实现类
    缺点:代理的对象必须有实现类并且重写了该接口,否则无法实现代理。

    比如,我们在JdkTest中//1行并不知道具体实现类,更确切说压根就没有实现类,那么通用驱动就不用写了吗?

    cglib动态代理

    如果我们 需要给没有是实现任何接口的目标类生成代理对象,JDK方式是做不到的。这是就可以使用继承目标类以目标对象子类的方式实现代理,这种方法就叫做Cglib代理,也叫做子类代理,他是在内存中构建一个子类对象从而 实现对目标对象功能的扩展。

    Cglib是一个强大的高性能代码生成包,他可以在运行期扩展java类和扩展java接口。它广泛的被许多AOP框架使用,例如Sring AOP和synaop,为他们提供方法的intercepion(拦截)

    Cglib包的底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类。

    Cglib子类代理实现方法:

    1.需要引入cglib的jar文件 cglib-2.2.2.jar asm-3.3.1.jar,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可.
    2.引入功能包后,就可以在内存中动态构建子类
    3.代理的类不能为final,否则报错
    4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.

    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    
    
    import java.lang.reflect.Method;
    
    /**
    *cglib代理:不需要实现类,只需要提供普通类,剩下的工作有底层完成
    * @author bamboo
    * @description:
    * @date 2020/12/14
    */
    public class CglibProxyFactory implements MethodInterceptor {
    
       //维护目标对象
       private Object target;
    
       public CglibProxyFactory(Object target) {
           this.target = target;
       }
    
       /**
        * @Description:获得目标类的代理对象
       */
       public Object getProxyObject(){
           //1、工具类
           Enhancer enhancer = new Enhancer();
           //2、设置父类
           enhancer.setSuperclass(target.getClass());
           //3、设置回调
           enhancer.setCallback(this);
           //4、创建代理类
           Object proxyObject = enhancer.create();
           return proxyObject;
    
       }
    
       @Override
       public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
           System.out.println("工作前");
           //执行目标对象的目标方法
           method.invoke(target,objects);
           System.out.println("工作后");
           return null;
       }
    
    }
    
    //普通类
    class Image {
       void display() {
           System.out.println("display");
       }
    }
    
    
    
    public class CglibTest {
    
       public static void main(String[] args) {
           Image image = new Image();
           Image proxyObject = (Image)new CglibProxyFactory(image).getProxyObject();
           proxyObject.sing();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    总结:其实cglib可jdk的动态代理很类似,最终都是通过被代理类执行原始方法,只是jdk使用的是反射,cglib使用的fastcalss机制

    到这三种代理方式我们都介绍完了,下面总结一下:

    1、如果目标对象实现了接口,我们就采用JDK方式实现动态代理

    2、如果目标对象没有实现接口,我们就需要采用cglib方式实现动态代理;

    扩展在实际项目中使用样例:接口+注解的动态代理

    mybatis中Map接口定义了方法,不用具体实现接口和xml的sql语句映射,代理类自动完成这些工作。
    springCloud中对于feign调用微服务时只依赖了服务方提供的interface方法包,调用方这边并没有imp相关的接口实现类,这个过程也是由代理类完成,从而实现http访问服务方的接口返回结果给调用方

    这里以微服务client端为例

    
    /**
     * @author bamboo
     * @description: 接口类
     * @date 2020/12/14
     */
    public @interface UserService {
    
        String  getById(Integer id) ;
    }
    
    
    
    /**
     * @author bamboo
     * @description: 接口方法执行拦截器:执行接口方法时做增强,使用http调用远程服务并返回值
     * @date 2020/12/14
     *
    public class CustomerInvocationHandler implements InvocationHandler {
        //被代理类
        private final Class<?> serviceClass;
    
        public CustomerInvocationHandler(Class<?> serviceClass) {
            this.serviceClass = serviceClass;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("proxy = " + serviceClass.getName() + ", method = " + method.getName() + ", args = " + Arrays.deepToString(args));
            Socket socket = new Socket("127.0.0.1", 8889);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            //数据打包
            RpcRequest rpcRequest = new RpcRequest(serviceClass.getName(), method.getName(), method.getParameterTypes(), args);
            //通过socket发送数据
            objectOutputStream.writeObject(rpcRequest);
            //接收返回数据
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            return inputStream.readObject();
        }
    }
    
    
    
    
    import java.lang.reflect.Proxy;
     
    /**
     * @author bamboo
     * @description: 代理工厂
     * @date 2020/12/1411:47
     */
    public class  ProxyFactory {
        public static Object getProxyObject( ClassLoader target,
                                             Class<?>[] interfaces,
                                             InvocationHandler h){
            return Proxy.newProxyInstance(target,interfaces,h);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    通过RPC远程调用

    • 创建代理类的InvocationHandler,相当于AOP拦截器,使得调用代理类的方法都会转入invoke中执行。
    • 创建代理类(调用方法已经在invoke中完成)。
    • 调用相应的方法。
    public class TestRpc {
    
        public static void main(String[] args) {
            //创建代理类的InvocationHandler拦截器
            CustomerInvocationHandler customerInvocationHandler=new CustomerInvocationHandler(UserService.class);
            //创建代理类
            //代理类会声明被代理类的接口
            UserService userService=(UserService) ProxyFactory.getProxyObject(UserService.class.getClassLoader(),new Class<?>[]{UserService.class},customerInvocationHandler);
            //调用
            String a=userService.getById(1);
            System.out.println("RPC CONNECTED!"+a);
        }
    }
    
    
    
    -------------------------------------------------------------------------------------
    proxy = com.bamboo.demo.rpc.UserService, method = getById, args = [1]
    RPC CONNECTED!bamboo
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上过程可以看出代理在我们常用场景中的重要性,不过这种使用也屏蔽了底层具体实现的细节,比如我们使用Mybatis如果不去看源码不关注底层其实直接一个接口就可以完成工作。同样的微服务中,我们只是使用其实只用给调用方一个接口它直接拿着用就可以了,连TestRpc注释调用之前的代码都不需要关注,是我们更加关注业务本身,从而抽繁化简,这才是编程工具使用称心的本心和初衷。

  • 相关阅读:
    一文读懂先验概率和后验概率
    zfaka 虎皮椒微信+支付宝插件(内附说明)
    为什么5G 要分离 CU 和DU?(4G分离RRU 和BBU)
    微擎模块 疯狂诗词大会小程序 2.0 前端+后端 优化答题正确提示页面样式
    小程序开发时:getLocation:fail require permission desc
    IOT Core-设备接入网关
    Node基础
    【C语言】程序环境和预处理
    MySQL_关于JSON数据的查询
    群辉 NAS 配置 iSCSI 存储
  • 原文地址:https://blog.csdn.net/zjcjava/article/details/125443422