• Java中的代理--proxy


      讲到代理,好像在之前的springMVC,还是spring中或者是hibernate中学习过,并没有特别在意,这次好好理解一下。(原来是在spring中的AOP,面向切面 Aspect Oriented Program,无语了,这都忘了)

    一、代理的概念和作用

    1、程序中的代理

    要为已存在的多个具有相同接口目标类的各个方法增加一些系统功能,例如:异常处理、日志、计算方法的运行时间、事务管理等等,

     1 class x{
     2   void sayHello(){System.out.print("Hello world")}  
     3 }
     4 
     5 // 作为x的代理类除了打印Hello World 还要计算程序执行的时间
     6 class XProxy{
     7   void sayHello(){
     8     startTime;
     9     System.out.print("Hello World")
    10     endTime;
    11   }
    12 }

    注意:当调用目标方法的时候,直接调用代理类,既能完成目标方法,还能做一些额外的事情,这就是代理类的作用吧,也就是代理类和目标类具有相同的方法,但是在调用方法时,会加上系统功能的其他代码,下面的图应该更好理解一点:(具有相同的方法,但是调用的时候加上了系统代码)

    2、采用工厂模式和配置文件的方式进行管理,则不需要修改客户端的程序,在配置文件中是使用目标类,还是代理类,这样,以后很用以切换

    3、代理是实现AOP 技术的核心和关键技术

    4、动态代理技术

    (1)JVM可以在运行期间动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类

    (2)JVM动态生成的类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理

    (3)CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库

    (4)代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标方法的结果外,还可以在代码中的如下位置加上系统代码:

    A:调用目标方法之前

    B:调用目标方法之后

    C:在调用目标方法前后

    D:在处理目标方法异常的catch块中

    二、分析JVM动态生成的类

    1、创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass()方法的各个参数

    2、编码列出动态类中的所有构造方法和参数名称

    3、编码列出动态类中所有方法和参数名称

     1         Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
     2         System.out.println(clazzProxy1.getName());
     3 
     4         System.out.println("------begin constructors list-------------");
     5         Constructor[] constructors = clazzProxy1.getConstructors();
     6         for (Constructor constructor : constructors) {
     7             String name = constructor.getName();
     8             StringBuilder sBuilder = new StringBuilder(name);
     9             sBuilder.append("(");
    10             Class[] clazzParams = constructor.getParameterTypes();
    11             for (Class clazzParam : clazzParams) {
    12                 sBuilder.append(clazzParam.getName()).append(",");
    13             }
    14             if (clazzParams != null && clazzParams.length > 0) {
    15                 sBuilder.deleteCharAt(sBuilder.length() - 1);
    16             }
    17             sBuilder.append(")");
    18             System.out.println(sBuilder.toString());
    19         }
    20         System.out.println("------end constructors list-------------");
    21 
    22         System.out.println("------begin methods list-------------");
    23         Method[] methods = clazzProxy1.getMethods();
    24         for (Method method : methods) {
    25             String name = method.getName();
    26             StringBuilder sBuilder = new StringBuilder(name);
    27             sBuilder.append("(");
    28             Class[] clazzParams = method.getParameterTypes();
    29             for (Class clazzParam : clazzParams) {
    30                 sBuilder.append(clazzParam.getName()).append(",");
    31             }
    32             if (clazzParams != null && clazzParams.length > 0) {
    33                 sBuilder.deleteCharAt(sBuilder.length() - 1);
    34             }
    35             sBuilder.append(")");
    36             System.out.println(sBuilder.toString());
    37         }
    38         System.out.println("------end methods list-------------");

    4、创建动态类的实例对象

    A:用反射获得构造方法

    B:编写一个简单的InvocationHandler类

    C:调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去

    D:打印创建的对象和调用对象的没有返回值的方法和getClass()方法,演示调用其他有返回值的方法出现了异常

    E:将创建动态类的实例对象的代理改成匿名内部类的形式编写

    5、让JVM创建动态类及实例对象,需要给它提供哪些信息?

    A:生成的类中有哪些方法,通过让其实现哪些接口的方式告知

    B:产生的类字节码必须有一个关联的类加载器对象

    C:生成的类中方法的代码是怎么样的,也得由我们提供,把我们的代码写在一个约定好了接口对象的方法中,把对象传给他,即是相当于插入了我的代码,提供执行代码的对象就是那个InvocationHandler对象,他是在创建动态类的实例对象的构造方法的时候传递进去的,在上面的InvocationHandler类中的Invoke方法中加入代码,就可以看到这些代码被调用执行了

            Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
            System.out.println(clazzProxy1.getName());
    
            System.out.println("------begin creat instance list-------------");
            Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
    
            // 普通的方法进行实例化
            class myInvokeHandler1 implements InvocationHandler {
                @Override
                public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                    return null;
                }
            }
    
            Collection proxy1 = (Collection) constructor.newInstance(new myInvokeHandler1());
            System.out.println(proxy1);
            proxy1.clear();
            //proxy1.size();
    
            // 用内部类的方式进行实例化
            Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {
                @Override
                public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                    return null;
                }
            });
    
            // 直接一步到位 其实本质还是一样的,只不过写法不一样
            Collection proxy3 = (Collection) Proxy.newProxyInstance(
                            Collection.class.getClassLoader(), 
                            new Class[]{Collection.class}, 
                            new InvocationHandler() {
                                
                                List target = new ArrayList();
                                @Override
                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                    
                                    long beginTime = System.currentTimeMillis();
                                    // 这里就是Collection类或者是子类中的方法执行 也就是调用了add()方法 同时我还做了一些其他事情。计算程序运行时间
                                    Object retVal = method.invoke(target, args);
                                    long endTime = System.currentTimeMillis();
                                    System.out.println(method.getName() + "running time:" + (endTime - beginTime));
                                    return retVal;
                                }
                            });
    
            System.out.println("------end creat instance list-------------");
    
            proxy3.add("aaa");
            proxy3.add("bbb");
            proxy3.add("ccc");
            System.out.println(proxy3.size());

    三、动态生成类的运行原理分析

    1、先分析一下InvocationHandler对象中的invoke方法的三个参数的意义:

    A:当前代理对象

    B:代理对象执行哪个方法

    C:方法中需要哪些参数

    2、动态代理的工作原理图

    在InvocationHandler类中执行invoke()方法的时候,如何来编写可配置的代码,让程序在运行的时候,将代码以参数的形式进行传递,这种解决办法,在java中是不常用的,一般的做法是将对象传递给InvocationHandler的invoke()方法中去执行传递的对象中的方法,这样的话,我们就可以,也算是动态的去改变执行的代码了,这就是最关键的部分,最巧妙的部分,值得去学习:这就是面向切面编程,就是把代码封装到一个对象中,将这个对象传递给需要执行的方法,让方法去执行传递中的对象中的方法,传递进来的对象也就是那个切面,去执行切面中的方法,改造之前的代码:

     1 // 这个就是需要传入的参数对象的抽象接口,同时传入了目标方法
     2 public interface Advice {
     3 
     4     void beforeMethod(Method method);
     5     void afterMethod(Method method);
     6     
     7 }
     8 
     9 // 参数接口的实例化对象
    10 public class MyAdvice implements Advice {
    11 
    12     long beginTime = 0;
    13     long endTime = 0;
    14     
    15     @Override
    16     public void beforeMethod(Method method) {
    17         System.out.println("Advice中的方法开始执行了。。。");
    18         beginTime = System.currentTimeMillis();
    19     }
    20 
    21     @Override
    22     public void afterMethod(Method method) {
    23         endTime = System.currentTimeMillis();
    24         System.out.println(method.getName() + "running time:" + (endTime - beginTime));
    25         System.out.println("Advice中的方法结束执行了。。。");
    26     }
    27 
    28 }
     1 // 改造之后的方法
     2 private static Object getProxy(final Object target, final Advice advice) {
     3         Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),
     4                 target.getClass().getInterfaces(), new InvocationHandler() {
     5 
     6                     @Override
     7                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     8 
     9                         /*long beginTime = System.currentTimeMillis();
    10                         // 这里就是Collection类或者是子类中的方法执行 也就是调用了add()方法 同时我还做了一些其他事情。计算程序运行时间
    11                         Object retVal = method.invoke(target, args);
    12                         long endTime = System.currentTimeMillis();
    13                         System.out.println(method.getName() + "running time:" + (endTime - beginTime));
    14                         return retVal;*/
    15                         
    16                         // 这里就是Collection类或者是子类中的方法执行 也就是调用了add()方法 同时我还做了一些其他事情。计算程序运行时间
    17                         advice.beforeMethod(method);
    18                         Object retVal = method.invoke(target, args);
    19                         advice.afterMethod(method);
    20                         
    21                         return retVal;
    22                         
    23                     }
    24                 });
    25         return proxy3;
    26 }
    27 // 实际调用,验证正确性
    28 public static void main(String[] args){
    29         final List target = new ArrayList();
    30         // 直接一步到位 其实本质还是一样的,只不过写法不一样
    31         Collection proxy3 = (Collection) getProxy(target, new MyAdvice());
    32 
    33         proxy3.add("aaa");
    34         proxy3.add("bbb");
    35         proxy3.add("ccc");
    36         System.out.println(proxy3.size());
    37 }

    总结:这就是传说中的spring框架的雏形,真的是这样子的吗?传说中的spring就是这样的啊!

  • 相关阅读:
    设计模式之工厂模式
    东方通 部署项目 报错 内存溢出解决
    java8提取对象集合中的一项属性
    vue 函数节流
    ALINK(三十一):特征工程(十)特征选择(二)卡方选择器 (ChiSqSelectorBatchOp)
    ALINK(三十):特征工程(九)特征选择(一)主成分分析(PcaTrainBatchOp/PcaPredictBatchOp)
    ALINK(二十九):特征工程(八)特征组合与交叉(三)Hash Cross特征 (HashCrossFeatureBatchOp)
    ALINK(二十八):特征工程(七)特征组合与交叉(二)Cross特征预测/训练 (CrossFeaturePredictBatchOp)
    ALINK(二十七):特征工程(六)特征组合与交叉(特征组合也叫特征交叉)(一)
    ALINK(二十六):特征工程(五)特征离散化(五)二值化 (BinarizerBatchOp)
  • 原文地址:https://www.cnblogs.com/ssh-html/p/10841489.html
Copyright © 2020-2023  润新知