发表于: 2020-10-17 23:39:04

1 1462


今天完成的事情:
遇到的问题:
使用Mapper代理
了解一下代理是什么

Java静态代理与动态代理模式的实现

前言:    在现实生活中,考虑以下的场景:小王打算要去租房,他相中了一个房子,准备去找房东洽谈相关事宜。但是房东他很忙,平时上班没时间,总没有时间见面,他也没办法。后来,房东想了一个办法,他找到了一个人代替自己和小王洽谈,房东本人不用出面,他只要把他的对房客的要求告诉他找的那个人,那个人和你商量就可以了,这样就可以完成租房这件事了。这种现实场景比比皆是,所呈现出来的其实就是代理模式的原型的一种。我们把焦点转向编程,你是否在编程中经常遇见这样一个问题,对于访问某个对象,我们希望给它的方法前加入一个标记,比如对象的方法开始执行、结束等等(比如日志记录)。怎么办呢,这个时候只要我们编写一个复制的类,然后把这个对象传给这个类,再对这个类进行操作,不就可以了吗。这就是代理模式,复制的类就是代理对象,通过代理对象与我们进行打交道就可以对它原来的对象进行改造。对于有些时候现有的对象不能满足我们的需求的时候,如何对它进行扩展,对方法进行改造,使其适用于我们所面临的问题,这就是代理模式的思维出发点。

感觉代理类似副本(对标正本),就是类似我现在写的东西,改变不了他人博客的类容,复制他人博客的内容,然后再需要的地方加一个记号,让我自己能更好的理解
一:代理模式的介绍
二:实现静态代理
三:代理的进阶:实现动态代理
四:总结
接下来按照目录,我们来依次讲解本篇博客:
一:代理模式的介绍
1.1:目标
   为其他对象提供一种代理以控制对这个对象的访问
解释:在实际编程中我们会产生一个代理对象,然后去引用被代理对象,对被代理对象进行控制与访问,实现客户端对原代理对象的访问,详情见下面的代码示例。
1.2:适用性

在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy 模式。下面是一 些可以使用Proxy 模式常见情况:
1.2.1:远程代理(Remote Proxy )为一个对象在不同的地址空间提供局部代表。 NEXTSTEP[Add94] 使用N X P r o x y 类实现了这一目的。
1.2.2:虚代理(Virtual Proxy )根据需要创建开销很大的对象。在动机一节描述的I m a g e P r o x y 就是这样一种代理的例子。
1.2.3: 保护代理(Protection Proxy )控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候
1.2.4: 智能指引(Smart Reference )取代了简单的指针,它在访问对象时执行一些附加操作。 

1.3:结构
二:实现静态代理
2.1:代码场景
    假如我们现在由以下的场景:文件编辑器要对一个图像文件进行操作,遵循以下顺序:加载,绘制,获取长度和宽度,存储四个步骤,但是有个问题,需要被加载的图片非常大,每次加载的时候都要耗费很多时间。并且我们希望对图片的操作可以记录出来操作的步骤,比如第一步、第二步这样便于我们去理解为了解决这个问题,我们可以先考虑解决第一个问题就是利用代理模式去新建一个代理对象,然后在代理对象里去实现一个缓存,这样下次我们直接可以去缓存里面取对象,而不用去新建,这样就省去了新建对象消耗的资源。另一方面,我们可以考虑去引用原来的方法,再给这方法基础上添加我们所要做的记录。接下里我们用java代码来实现这个场景:
2.2:代码示范
2.2.1:首先新建一个接口,命名为Graphic,其中主要规范了我们进行操作的步骤
package GraphicTest;
public interface Graphic {
       
       void load();//加载
       
       void Draw();//绘制
       
       Extent GetExtent();//获取长度和宽度
       
       void Store();
}
2.2.2:然后去新建一个Image类,用于实现接口,对操作进行具体控制,注意为了其中的Extent是对宽度和长度的封装(省略get和set方法)
package GraphicTest;
public class Image implements Graphic {
       
       public Image() {
             try {
                    Thread.sleep(2000);//模拟创建需要花费很久的时间
                    System.out.println("正在创建对象");
             } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
             }
             
       }
       @Override
       public void load() {
             
             System.out.println("进行加载。。。");
       }
       
       @Override
       public void Draw() {
             
             System.out.println("进行绘画。。。");
       }
       
       @Override
       public Extent GetExtent() {
             Extent extent = new Extent("100","200");
             
             System.out.println("获取图片的属性是:" + extent.toString());
             
             return extent;
       }
       
       
       @Override
       public void Store() {
             
             System.out.println("图片进行存储在硬盘里。。。");
       }
       
}
Extent
package GraphicTest;
public class Extent {
       private String width;
       private String length;
       public Extent() {
             
       }
       
       public Extent(String width, String length) {
             super();
             this.width = width;
             this.length = length;
             
       }
       public String getWidth() {
             return width;
       }
       public void setWidth(String width) {
             this.width = width;
       }
       public String getLength() {
             return length;
       }
       public void setLength(String length) {
             this.length = length;
       }
       @Override
       public String toString() {
             return "Extent [width=" + width + ", length=" + length + "]";
       }
       
}
2.2.3:接下来就是很关键的一步了,新建我们的代理类,我们新建一个类叫做ImageProxy,然后实现缓存与记录的效果:
package GraphicTest;
import java.util.HashMap;
import java.util.Map;
public class ImageProxy implements Graphic{
    
    private Image image;
    
    private Map<String, Image> cache = new HashMap<String, Image>();//缓存
    
    public ImageProxy() {
        
        init();
    }
    
    public void init() {//只需要初始化一次
        
        if(image==null) {
            
            image = new Image();
            
            cache.put("image", image);//放入缓存
            
        }else {
            
            image=cache.get("image");
        }
    }
    
      @Override
        public void load() {
            
             System.out.println("---第一步开始---");
             
              image.load();
             
             System.out.println("---第一步结束---");
        }
        
        @Override
        public void Draw() {
            
            System.out.println("---第二步开始---");
            
            image.Draw();
            
            System.out.println("---第二步结束---");
        }
        @Override
        public Extent GetExtent() {
            
            System.out.println("---第三步开始---");
            Extent extent = image.GetExtent();
            System.out.println("---第三步结束--");
            
            return extent;
            
        }
        @Override
        public void Store() {
            System.out.println("---第四步开始---");
            
            image.Store();
            
            System.out.println("---第四步结束--");
            
        }
    
    
    
    
}

2.2.4:我们的文档编辑器现在要开始进行文档编辑了,我们来实现具体的代码,我们先来引用一下原对象,看一下原来的对象会出现什么情况:
package GraphicTest;
public class DocumentEditor {
       public static void main(String[] args) {
             // TODO Auto-generated method stub
             
       //     Graphic proxy = new Image();
             
             Graphic proxy = new ImageProxy();
             
             proxy.load();
             
             proxy.Draw();
             
             proxy.GetExtent();
             
             proxy.Store();
       }
}
2.2.5:测试代码

我们可以看出,它会消耗3秒才会出来具体的对象,并且没有我们所需要的记录。好了,我们把2.2.4的引用代码改为: Graphic proxy = new ImageProxy();
我们再来测试一下:
   很明显可以看出,通过访问我们的代理对象,就可以实现对原方法的改造,这就是代理模式的精髓思想。不过到这里你可能会问,为什么不对原对象进行改造呢?为什么要给他新建一个代理对象,这不是很麻烦吗。回答这个问题,首先要提一个代码的设计原则,也就是有名的开闭原则:对扩展开放,对修改关闭。这句话的意思就是不建议对原有的代码进行修改,我们要做的事就是尽量不用动原有的类和对象,在它的基础上去改造,而不是直接去修改它。至于这个原则为什么这样,我想其中一个原因就是因为软件体系中牵一发很动全身的事情很常见,很可能你修改了这一小块,然而与此相关的很多东西就会发生变化。所以轻易不要修改,而是扩展。

三:实现动态代理

3.1:静态代理的不足:

  通过看静态代理可以动态扩展我们的对象,但是有个问题,在我们进行方法扩展的时候,比如我们的日志功能:每个前面都得写第一步、第二步。如果我们要再一些其他的东西,比如权限校验、代码说明,一个两个方法还好,万一方法成百个呢,那我们岂不是要累死。这就是动态代理要解决的问题,只需要写一次就可以,究竟是怎么实现的呢,接下里我们来一探究竟吧。

3.2:动态代理的准备:

动态代理需要用到JDk的Proxy类(代理),通过它的newProxyInstance()方法可以生成一个代理类,我们来通过jdk看一下具体的说明,如何使用它:

public static Object newProxyInstance(ClassLoader loader,

                                      Class<?>[] interfaces,

                                      InvocationHandler h)

                               throws IllegalArgumentException

返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。此方法相当于:

   Proxy.getProxyClass(loader, interfaces).

         getConstructor(new Class[] { InvocationHandler.class }).

         newInstance(new Object[] { handler });

Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。

参数:

loader - 定义代理类的类加载器

interfaces - 代理类要实现的接口列表

h - 指派方法调用的调用处理程序

返回:

一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

抛出:

IllegalArgumentException - 如果违反传递到 getProxyClass 的参数上的任何限制

NullPointerException - 如果 interfaces 数组参数或其任何元素为 null,或如果调用处理程序 h 为 null

  从中可以看出它有三个参数,分别是classlcoder、interface、InvocationHandler.只要我们把这三个参数传递给他,它就可以 返回给我们一个代理对象,访问这个代理对象就可以实现对原对象的扩展。接下来,我们用代码来实现它。

3.3:代码场景

我们来做这样一个场景,我们实现一个计算器,计算器里面有加减乘除方法,然后我们实现这个计算的接口,有具体的类和被代理的类,我们通过动态代理来生成代理类,而不用自己去建了,好了,看接下来的代码:

3.4:动态代理的代码实现

3.4.1:首先我们新建一个接口,命名为Calculator ,声明四个方法:

package DynamicProxyTest;

public interface Calculator {

int add(int i, int j);//加

int sub(int i, int j);//减

int mul(int i, int j);//乘

double div(int i, int j);//除

}

3.4.2:新建一个实现类,命名为CalculatorImpl ,也就是被代理类

package DynamicProxyTest;

public class Calculatorlmpl implements Calculator{

@Override

public int add(int i, int j) {

return i+j;

}

  @Override

    public int sub(int i, int j) {

        

        return i-j;

    }

    @Override

    public int mul(int i, int j) {

        

        return i*j;

    }

    @Override

    public double div(int i, int j) {

        

    return  (i/j);

    

    }

}

3.4.3:新建一个类,命名为CalCulatorDynamicProxy,也就是我们的代理类,用来对上面的类进行代理:

package DynamicProxyTest;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.Arrays;

public class CalCulatorDynamicProxy { //动态代理类

private Calculator calculator;//要代理的对象

public CalCulatorDynamicProxy(Calculator calculator) {

this.calculator = calculator;

}

public Calculator getCalculator() {

Calculator proxy = null;

ClassLoader loader = calculator.getClass().getClassLoader();//获取类加载器

Class[] interfaces = new Class[] {Calculator.class};//代理对象的类型

InvocationHandler h = new InvocationHandler() {//调用处理器

        //proxy:正在返回的代理对象

        //method:被调用的方法

        //args:传入的参数    

            @Override

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("---日志记录开始---");

                String name = method.getName();//获取方法的名字

                System.out.println("方法"+name+"()开始执行了");

                System.out.println("方法中的参数是:"+Arrays.asList(args));

                Object result = method.invoke(calculator, args);

                System.out.println("方法执行后的结果是"+result);

                return result;

            }

};

        proxy=(Calculator)Proxy.newProxyInstance(loader, interfaces, h);//代理对象

return proxy;

}

}

这里要特别强调的问题就是:invoke()方法,注意其中的参数,分别是被代理对象、方法、和对象参数,这里的原理是反射,通过获取原对象的class对象,然后进行处理,我们可以通过method对象拿到被代理对象的方法,也是add()、mul()、sub()、div()方法,也可以通过args对象数组取得传入的参数,比如我们具体传入的数值,再通过method.invoke()方法进行调用,就进行了被代理对象的方法的执行,然后就是返回的结果(如果方法前为void,返回的就是null)

3.4.4:我们来做具体的测试

package DynamicProxyTest;

public class Test {

    public static void main(String[] args) {

        

        Calculator cal = new Calculatorlmpl();

        

        Calculator proxy = new CalCulatorDynamicProxy(cal).getCalculator();

        

        int add = proxy.add(29, 1);

        

        

        int sub = proxy.sub(9, 2);

        

        

        int mul = proxy.mul(3, 7);

        

        

        double div = proxy.div(6,8);

        

    }

}

具体的测试结果:

可以看出动态代理模式轻松完成了对被代理对象的日志记录功能,并且只用写一次,这样即便有成百上千的方法我们也不怕,这就是动态代理领先于静态代理之处,虽然实现起来有点麻烦,但是其方便,动态的给被代理对象添加功能。我们所写的重复代码更少,做的事情更少。

四:总结

    本篇博客介绍了动态代理和静态代理的概念,并对其进行了代码实现,在实际的工作中,我们会经常遇到需要代理模式的地方,希望能多多思考,促进我们形成一定的思维模式。并且动态代理作为SpringAop的实现原理封装了动态代理,让我们实现起来更加方便,对于这部分内容可以只做了解,理解其背后的运行机制即可,并不需要具体实现,如果需要实现,直接使用spring的Aop功能即可。

toString()不太理解,去了解了一下toString(),发现toString()是java.lang.Object类下的一个方法顺便了解了一下Object类

也了解了一下实体类,原来实体类就是Javabean


不知道Map在这里的意思,去了解了一下Map


最后了解了一下反射

编写一个person类


public class Person {
private String name;
   public int age;

   public Person(String name, int age) {
this.name = name;
       this.age = age;
   }

private Person(String name) {
this.name = name;
   }

public Person() {
}

public void show(){
System.out.println("你好,我是况博凯");
   }

private String showNation(String nation){
System.out.println("我的国籍是:" + nation);
       return nation;
   }


public String getName() {
return name;
   }

public void setName(String name) {
this.name = name;
   }

public int getAge() {
return age;
   }

public void setAge(int age) {
this.age = age;
   }

@Override
   public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
   }
}

编写一个测试类

import org.junit.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionTest {

//反射之前,对于Person的操作
   @Test
   public void test1() {

//1.创建person类的对象
       Person p1 = new Person("Tom", 12);

       //2.通过对象,调用其内部的属性,方法
       p1.age = 10;
       System.out.println(p1.toString());

       p1.show();

       //Person类外部,不可以通过Person类的对象调用其内部私有结构
       //比如name,showNation()以及私有(private)的构造器

   }

//反射之后,对于Person的操作
   @Test
   public void test2() throws Exception {
Class clazz = Person.class;
       //1.通过反射,创建Person类的对象
       Constructor cons = clazz.getConstructor(String.class, int.class);
       Object obj = cons.newInstance("Tom", 12);
       Person p = (Person) obj;
       System.out.println(p.toString());
       //2.通过反射,调用对象指定的属性、方法
       //调用属性
       Field age = clazz.getDeclaredField("age");
       age.set(p, 10);
       System.out.println(p.toString());

       //调用方法
       Method show = clazz.getDeclaredMethod("show");
       show.invoke(p);

       System.out.println("*******************************");

       //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
       //调用私有的构造器
       Constructor cons1 = clazz.getDeclaredConstructor(String.class);
       cons1.setAccessible(true);
       Person p1 = (Person) cons1.newInstance("MEKBK");
       System.out.println(p1);

       //调用私有的属性
       Field name = clazz.getDeclaredField("name");
       name.setAccessible(true);
       name.set(p1, "魏思静");
       System.out.println(p1);

       //调用私有的方法
       Method showNation = clazz.getDeclaredMethod("showNation", String.class);
       showNation.setAccessible(true);
       String nation = (String) showNation.invoke(p1, "中国");//相当于String nation = p1.showNation("中国")
       System.out.println(nation);
   }
}

没有反射之前,通过对象来无法调用Person类的其内部私有结构

知道反射可以调用person的私有结构,也就是封装(private),顺便了解一下封装

封装 private

封装表现:

  1、方法就是一个最基本封装体。

  2、类其实也是一个封装体。

从以上两点得出结论,封装的好处:

  1、提高了代码的复用性。

  2、隐藏了实现细节,还要对外提供可以访问的方式。便于调用者的使用。这是核心之一,也可以理解为就是封装的概念。

  3、提高了安全性。



明天计划的事情:

做完Mapper代理,然后学习Debug模式,练习调试,学会查看单步执行时的变量值

获:以上




返回列表 返回列表
评论

    分享到