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

    1、动态代理的目的                                                                      

      动态代理用于代码的分离;将通用代码从各个业务模块中分离出来,不必在每个业务模块中都去实现,这样既减少了开发工作量,也便于维护;

      eg: “吃饭”、“说话”这两个业务都需要用到“张嘴”  “闭嘴”这两行代码,但是张嘴之后和闭嘴之前所做的事情是不一样的,这样将业务模块和通用模块(即 张嘴、闭嘴)分离出来,每次在说话 或者 吃饭的时候,使用通用模块+业务模块 实现吃饭和说话这两件事;

    2、动态代理的应用                          

       最典型的应用是基于动态代理技术实现spring框架中的AOP(面向切面编程);  面向切面编程,通过切面代码层,实现业务代码和通用代码的分离,这样各个业务模块可以共用一个通用代码。大概原理如下:切面层代码提供了一个调用其他方法的接口(姑且命名为 invoke),通过反射机制将类 和 方法等参数传入后,便在invoke 里面执行此方法,传入的方法仅仅是invoke方法的一部分,另一部分便是通用代码,这样就将通用代码和业务代码编制在了一起。(实际上sping中的增强就是这么回事)

    3、动态代理的原理及实现                                                              

       java提供了两种动态代理方式,一种是JDK动态代理,另一种是cglib动态代理;JDK动态代理仅仅实现了对接口的代理,cglib实现的是对对象的代理;二者相比,chlib综合性能更好一点;

      (1) JDK动态代理

      JDK动态代理是基于 “InvocationHandler接口”  和  “Proxy类” 实现的,其技术基础是 JAVA中的反射机制 及 面向对象中的重写;其中 代理类就是继承于InvocationHandler这个接口的,主要是继承他的invoke 方法;Proxy类用于获取反射对象,调用方法。虽然在本来的业务类中没有invoke方法,但是会在Java虚拟机中生成一个$Proxy0文件,这个文件是继承于本来的业务类,在这个文件中会调用invoke方法。

                         

     下面就根据上图中的代理模型,来写一个简单的JDK动态代理

        ① 首先构建业务层接口 java代码如下:

    package Proxy.JDK;
    import java.lang.*;
    public interface SayHelloWorld {
        public void say(String language);
    }
    业务层接口

        ② 基于构建的接口层,创建业务类

    package Proxy.JDK;
    
    public class menSay implements SayHelloWorld {
    
        @Override
        public void say(String language) {
            // TODO Auto-generated method stub
            System.out.println("HelloWorld!");
        }
    
    }
    实际的业务类

        ③ 构建代理类

    package Proxy.JDK;
    
    import java.lang.reflect.*;;
    
    public class proxyClass implements InvocationHandler {
    
        private Object VR_class;
        
        /**
         * 返回代理类
         * @method: proxyClass() -by fjt
         * @TODO:  利用反射机制,被代理业务类产生代理对象
         * @param obj 被代理的业务类
         * @return 
         * @return Object
         */
        public Object getProxy(Object obj)
        {
            this.VR_class = obj;
            return Proxy.newProxyInstance(VR_class.getClass().getClassLoader(), VR_class.getClass().getInterfaces(), this);
        }
        
        @Override
        /**
         * 当调用业务层方法的时候  就会进入这个接口,所以可以认为是覆盖了原先的方法
         * 至于是怎么实现覆盖的,为什么会跳到这儿,还不是很清楚
         */
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // TODO Auto-generated method stub
            System.out.println("张嘴");
            method.invoke(this.VR_class, args);
            System.out.println("闭嘴");
            return null;
        }
    
    }
    代理类

        ④ 代理的应用

    package Proxy.JDK;
    
    public class jdk_proxyTest {
        public static void main(String[] arges)
        {
            SayHelloWorld manSay = new menSay();
            proxyClass proxyMan = new proxyClass();
            //JDK实现的动态代理 是基于接口实现的,因此下面的语句  如果将SayHelloWorld 改为 menSay就会出错
            //$Proxy0 cannot be cast to Proxy.JDK.menSay (因为menSay是一个类 Proxy0)
            SayHelloWorld proxyMenSay = (SayHelloWorld)proxyMan.getProxy(manSay);  
            proxyMenSay.say("hi!");
            
        }
    }
    代理的应用

       通过上述例子不难看出,JDK动态代理,实际上就是接口注入,也仅仅是接口注入,通过代理返回的对象也是接口,所以代理的也只能是接口。

      (2) CGLIB动态代理

       JDK动态代理使用起来不太方便,需要写接口、服务类、代理类等,而且只能代理接口,繁琐许多。相比之下,CGLIB就简单许多了。然而CGLIB创建代理对象的时间要花费的比JDK多。所以如果需要频繁的创建对象,并且是接口代理,使用JDK会更好一点。CGLIB是动态创建子类,所以不能代理final变量和方法。

      CGLIB的使用,需要引入如下几个包:

            

      下面先看一下CGLIB的动态代理的实现吧!

    package Proxy.CGLIB;
    
    public class sayHello {
        public int a = 0;
        public int b = 0;
        public void add()
        {
            System.out.println("计算结果: " + Integer.toString(a+b));
        }
    }
    被代理类
    package Proxy.CGLIB;
    
    import java.lang.reflect.Method;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    public class CglibProxy implements MethodInterceptor {
    
        private Object obj;   //被代理的原始对象
        public Object creatProxy(Object target)
        {
            this.obj = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.obj.getClass());  //设置代理目标
            enhancer.setCallback(this);
            enhancer.setClassLoader(target.getClass().getClassLoader());
            return enhancer.create();
        }
        //阻塞被代理的类的方法调用
        @Override
        public Object intercept(Object arg0, Method arg1, Object[] arg2,
                MethodProxy arg3) throws Throwable {
            // TODO Auto-generated method stub
            System.out.println("调用之前");
            arg3.invokeSuper(arg0, arg2);
            System.out.println("调用之后");
            return null;
        }
    
    }
    代理类
    package Proxy.CGLIB;
    
    public class cglib_proxyTest {
    
         public static void main(String[] args) {
             sayHello calcu = new sayHello();
             CglibProxy cglib_proxy = new CglibProxy();
             sayHello cal_proxy = (sayHello)cglib_proxy.creatProxy(calcu);
             cal_proxy.a = 1;
             cal_proxy.b = 1;
             cal_proxy.add();
         }
    }
    测试代码

      看起来很简单吧。

      CGLIB实现的是代理类,JDK实现的是代理接口;CGLIB是通过intercept这个函数来阻塞代理类函数的调用,也就是说调用被代理类的方法的时候,执行的实际上是intercept这个方法。

  • 相关阅读:
    ARP病毒的分析与防治思路
    sqlserver存储过程参数拼接
    自定义函数
    asp.net 文件流操作
    asp.net 国际化
    一个用户登录权限的基本例子
    更新密码,判断旧密码存储过程
    SQLSerVer计算1100之间所有能被3整除的数的个数及总和
    等待2小时2分零10秒后才执行sql语句
    C#实现按日期命名上传文件代码
  • 原文地址:https://www.cnblogs.com/tengpan-cn/p/4841033.html
Copyright © 2011-2022 走看看