啰里吧嗦式讲解java静态代理动态代理模式

一.为啥写这个

文章写的比较啰嗦,有些东西可以不看,因为想看懂框架, 想了解SSH或者SSM框架的设计原理和设计思路,

又去重新看了一遍反射和注解,

然后看别人的博客说想要看懂框架得先看懂设计模式,于是遇到了动态代理这个大坑,

写博客等于是对自己学习过程的一个回顾和总结

本文主要参考欧阳锋的10分钟看懂动态代理设计模式


二.理解和弄懂代理的前期准备

2.1.什么是代理

简单来说就是有活不自己干,让别人干, 比如你不想写作业, 让同学帮你写,然后写上自己的名字,

这个同学就是你的代理, 帮你处理一些事情

2.2.如何利用代码生成一个java文件

生成一个java文件不过就是字符串的拼接,最后利用流输出到一个目录下面,以.java结尾,不需要从头开始写,

有个好用的工具包javaPoet,可以去GitHub上下载,也可以通过maven的方式引入

<dependency>
<groupId>com.squareup</groupId> <artifactId>javapoet</artifactId>
<version>1.8.0</version>
</dependency>

2.3.如何使用代码将生成的java文件编译成class文件

//编译桌面 proxy文件夹下的Proxy.java文件 会生成一个Proxy.class文件 JavaCompiler.compile(new File(sourcePath + "/proxy/Proxy.java"));

我的地址(sourcePath)输出在桌面:"C:/Users/你的计算机登陆用户名/Desktop"

2.4.如何利用代码将class 使用 类加载器加载进内存,并创建对象

利用类加载器,将该目录下的class文件加载进内存中

// 获得类加载器    使用反射load到内存URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file:C:\\Users\\保密\\Desktop\\") }); Class clazz1 = classLoader.loadClass("proxy.Proxy");
//通过Class的对象 clazz1 的getConstructor方法, 得到类Proxy的构造器对象, InvocationHandler.class是入参 Constructor constructor = clazz1.getConstructor(InvocationHandler.class); //通过构造器对象的newInstance 方法, 去创建一个类的对象 Object obj = constructor.newInstance(handler);

2.5.方法的反射知识

java有个Method类,可以通过method.invoke执行该方法

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 

public class Person { 

  public void buy() {     System.out.println("买买买");    } 

   public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 

     new Person().buy();     //拿到Person类下面 public void buy(){} 方法对象Method method = Person.class.getMethod("buy"){};     Method m = Person.class.getMethod("buy");    //通过method方法的invoke方法可以执行该方法, 入参是该方法属于的类的对象,和参数 method.invoke(person,new Object[] {}) 效果等同于new Person.buy()     m.invoke(new Person(), new Object[] {});   } }
买买买 买买买

写代码的过程中会遇到各种各样的错误,比如什么NoSuchException,遇到这类问题不要慌,大胆的用System.out.println打印每个对象的信息,去分析可能出现的原因

三.静态代理

前面的知识知道也行,不知道也无所谓,不影响了解静态代理

假设一个jar包下面有个接口Servlet, Servlet接口里有个方法比如上传文件,

有个实现类HttpServlet,实现了这个功能

public interface Servlet {   //假设一个java jar包下面的接口 有这样一个功能就是上传文件   void uploadFile(); } 

public class HttpServlet implements Servlet{ 

  @Override public void uploadFile() {        System.out.println("我要开始上传文件啦......");         System.out.println("真的很复杂啦......");   } }

你现在不能动jar包的源码, 你想要在上传文件前或者后干点自己的事情,

就好比作业让HttpServlet帮你干,但最后写上自己的名字,

这里我们假设你需要计算上传文件的时间那么

思路1 在每个该方法的调用前加入一段记录时间的代码 public static void main(String[] args) {   long t1 = System.currentTimeMillis();     new HttpServlet().uploadFile();    long t2 = System.currentTimeMillis;    system.out.println("花费的时间" + t2-t1); } 

我要开始上传文件啦...... 真的很复杂啦...... 1 ------------------ 这种方式显而易见的弊端就是,只要调用uploadFile方法,就要加两行代码计算,代码重复了N份,以后如果又不需要计算时间了 改起来会非常麻烦

  

思路2 通过继承的方式重写uploadFile()的方法 

public class HttpServletForCalTime extends HttpServlet{   @Override    public void uploadFile() {     System.out.println("统计时间....");        super.uploadFile();        System.out.println("统计时间...."); } 

public static void main(String[] args) {        HttpServletForCalTime a = new HttpServletForCalTime();        a.uploadFile();  } } 统计时间.... 我要开始上传文件啦...... 真的很复杂啦...... 统计时间.... 

--------------------- 这种写法的好处是统计文件上传时间用HttpServletForCalTime 类就行了,但是弊端就是不好扩展,比如我想在统计时间前 先对文件的关键字进行屏蔽处理,把王八蛋换成空格,那么又得继承 如果我想先计算时间 在屏蔽关键字,在上传文件,那么又得创建一个新的继承类 类无限的扩展,因为继承是一种包裹关系,不够灵活思路3---静态代理 使用聚合,动态注入所要代理的对象, 原文中有句非常经典的话:其实设计模式,多多少少都跟多态有关 
我理解的多态就是java的具体执行哪个方法不是在编译期绑定,而是在运行期动态绑定 

//创建一个代理类, 有个成员变量, 执行uploadFile的内容 取决于你传入什么Servlet对象 public class ServletTimeProxy implements Servlet{   private Servlet servlet;     public ServletTimeProxy(Servlet servlet) {     this.servlet = servlet;   } 

   @Override    public void uploadFile() {       System.out.println("计算时间...");           servlet.uploadFile();           System.out.println("计算时间...");    } } 

 public class ServletLogProxy implements Servlet{   private Servlet servlet;    public ServletLogProxy(Servlet servlet) {            this.servlet = servlet;    }   @Override    public void uploadFile() {          System.out.println("打印日志...");          servlet.uploadFile();          System.out.println("打印日志...");    } } 

执行什么内容取决于你传入什么对象,比较灵活,如果想先计算时间再打印日志 就调个个就行了 活还是HttpServlet代替你干, 

public static void main(String[] args) {          HttpServlet impl = new HttpServlet();          ServletTimeProxy a = new ServletTimeProxy(impl);//统计时间          ServletLogProxy b = new ServletLogProxy(a);//打印日志          b.uploadFile(); } ----------------------------------------- 我要打印日志.... 计算时间... 我要开始上传文件啦...... 真的很复杂啦...... 计算时间... 我要打印日志.... 

该方法也有弊端,就是扩展性还是不够好,如果jar包里我有N个类的xx方法都需要计算执行时间,打印日志 那么得实现N个jar包里的接口,注入接口,代理该接口的方法

接下来的神仙方法就来了, 我根据你传过来的接口对象,根据你想自定义的执行方法,

我给你生成java类,我给你编译Java类,我去将class加载进内存中, 我去执行 你指定的方法,

你就不用写那么多份xx implements xxInterface代码了

java.lang.reflect.Proxy @since 1.3

四.动态代理的原理

没时间的童鞋可以不看,我是被10分钟看懂动态代理给坑了,

它让你自己写了java反射类Proxy的大概实现,这时候前面的知识就用得上了思路1,首先我们想通过代码创建一个java类,它长这样package proxy; 

import java.lang.Override; import java.lang.System; import staticProxy.Servlet; 

class Proxy implements Servlet {       private Servlet impl; 

      public Proxy(Servlet impl) { this.impl = impl; } 

      @Override public void uploadFile() {         long start = System.currentTimeMillis(); this.impl.uploadFile(); long end = System.currentTimeMillis();              System.out.println("Fly Time =" + (end - start));       } } 

那么利用javapoet可以这么写,我写Proxy1 是1.0版本 public class Proxy1 { 

            public static Object newProxyInstance() throws IOException {                    //第一步 通过javapoet工具类 创建一个名为Proxy的java类 , 该类实现了Servlet接口                    // == public class Proxy implement Servlet //这里之前少写一个public 导致后面获取构造器对象时报NoSuchException,                    TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy") .addModifiers(Modifier.PUBLIC)//定义类的修饰符//构造一个类,类名                       .addSuperinterface(Servlet.class); 

                   //第二步 创建类里面的属性 我们要注入该接口                    //我们希望的属性是 private Servlet impl //所以指定 属性的类型 和 属性的名字 和 属性的范围                    //注意 Modifier包千万不要导错了,import javax.lang.model.element.Modifier; //之前报错找了好久                    FieldSpec fieldSpec = FieldSpec.builder(Servlet.class, "impl", Modifier.PRIVATE).build(); 

                   //将属性 添加到类中                    typeSpecBuilder.addField(fieldSpec); 

                  //我们希望构造方法中 将该接口的实现类 传到构造器里 方便灵活调用要处理的方法 //Proxy(Servlet impl) { // this.impl = impl; // }                   MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()                        .addModifiers(Modifier.PUBLIC).addParameter(Servlet.class, "impl")                        //构造器传入参数的类型 就是你要创建的java类实现接口的类型, 属性名字                         .addStatement("this.impl = impl")//构造器里面的语句                         .build(); 

           typeSpecBuilder.addMethod(constructorMethodSpec); 

                   //类 ,属性 ,构造器都写完了, 接下来开始写方法 , 你实现的接口有N个方法                    Method[] methods = Servlet.class.getDeclaredMethods();                    for (Method method : methods) { 

                       //在文件中写入方法 和 方法内容 方法里有返回值 入参 方法名                       MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName()) 

                     //给方法添加范围                      .addModifiers(Modifier.PUBLIC)                      //给方法添加 注解 通常都是实现 x接口的覆盖注解                       .addAnnotation(Override.class)                     //给方法添加返回值通过method.getReturnType能拿到该方法return接收的对象.returns(method.getReturnType())                     //给方法添加语句 这里是给每个方法都加上了一个计算时间                                  .addStatement("long start = $T.currentTimeMillis()", System.class) 
                    //添加换行符                     .addCode("\n") //添加执行语句 我们希望的执行语句是 执行传过来的接口的某个方法 //比如 impl.upload()                     .addStatement("this.impl." + method.getName() + "()") .addCode("\n") //$T可以理解为占位符,javapoet会去找对应的类                     .addStatement("long end = $T.currentTimeMillis()", System.class)                     .addStatement("$T.out.println(\"Fly Time =\" + (end - start))", System.class)                     .build(); 

                   typeSpecBuilder.addMethod(methodSpec);             } 

      //生成一个 TypeSpec 前面定义的java文件 第一个参数是包名 这样生成的xx.java文件再次包下       JavaFile javaFile = JavaFile.builder("proxy", typeSpecBuilder.build()).build();       String sourcePath = "C:/Users/自己登陆的电脑用户名/Desktop"; 

      //在桌面上生成一个Proxy.java的文件       javaFile.writeTo(new File(sourcePath)); return null; }

执行 Proxy1.newProxyInstance()方法,发现确实在桌面上生成了一个proxy文件夹,里面有个proxy.java

1.0版本我们写死了Proxy要实现的接口,现在我们把它作为参数传进去,要改动几个点,升级为2.0

//代码只贴了改动点
public class Proxy2 {
  public static Object newProxyInstance(Class clazz) throws IOException {
                 TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy") .addSuperinterface(clazz);
                     //指定属性 是那种类型 ,代理,代理, 就是代理的你要实现的类的接口, 起码现在看来是这样
                   FieldSpec fieldSpec = FieldSpec.builder(clazz, "impl", Modifier.PRIVATE).build();
                    //方法的构造器
                     MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(clazz, "impl")
                      //构造器传入参数的类型 就是你要创建的java类实现接口的类型, 属性名字
                      .addStatement("this.impl = impl")//构造器里面的语句
                      .build();
}

执行Proxy2.newProxyInstance(Servlet.class);

现在虽然我们能实现代理任意接口了,但是目前只能够再任意接口的 任意方法上面加上时间计算, 如何可以实现自定义执行逻辑呢----代理中的代理 Proxy3.0版本

我们希望能执行用户自定义的方法, java大神可能就想到,我定义一个接口, 然后你去实现该接口的方法

我利用方法的反射 来执行你 要自定义的方法逻辑

所以java定义了一个InvocationHandler,所有要使用动态代理的都得先实现该接口 ,

实际上生成的动态代理类 是代理了 InvocationHandler接口, 然后 你自定义实现了InvocationHandler接口的类代理了 你需要代理的类

所谓代理中的代理

话不多说,接着贴代码,我当初看也是一脸懵逼

public interface InvocationHandler {
    void invoke(Object proxy, Method method, Object[] args);
}
package animateProxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import staticProxy.Servlet;

public class MyInvocationHandler implements InvocationHandler {

    private Servlet impl; //为了完成对被代理对象的方法拦截,我们需要在InvocationHandler对象中传入被代理对象实例。

    public MyInvocationHandler(Servlet impl) {
        this.impl = impl;
    }

    //参数1  想要、

    //参数2  这个参数表示  传入接口  中的所有Method对象

    //参数3 这个参数   对应当前method方法   中的参数
    @Override
    public void invoke(Object proxy, Method method, Object[] args) {
        // TODO Auto-generated method stub
        //1.自定义想要加在 动态代理类 的方法的处理 比如
        //统计想要 代理类的 方法的时间
        long start = System.currentTimeMillis();

        System.out.println("proxy:" + proxy); //proxy: proxy.Proxy@636a59bb

        try {
            //方法的反射操作
            //方法的反射操作是method对象来进行方法调用
            //和 执行  impl.upload() 效果完全相同
            method.invoke(impl, new Object[] {});

        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("Fly time = " + (end - start));

    }

}

然后我们希望动态代理工具proxy3.0 能生成一个这样的文件

package proxy;

import animateProxy.InvocationHandler;
import java.lang.Override;
import java.lang.reflect.Method;
import staticProxy.Servlet;

public class Proxy implements Servlet {
  private InvocationHandler handler;

  public Proxy(InvocationHandler handler) {
    this.handler = handler;
  }

  @Override
  public void uploadFile() {
    try {
        Method method = staticProxy.Servlet.class.getMethod("uploadFile");
         this.handler.invoke(this, method, null);
    } catch(Exception e) {
        e.printStackTrace();
    }
  }
}

接下来修改Proxy类

package animateProxy;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

import javax.lang.model.element.Modifier;

import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

/**
 *
 * 现在在proxy2的基础上进化下,
 *
 * 现在想执行  我们自己 想执行的代码 而不是
 *
 * 固定的写死的 计算每个方法时间的代码
 *
 * 这点比较难想到 , 就是代理的逻辑抽取出来, 把打印方法执行时间的逻辑抽取出来
 * 可以自定义个接口 用于处理自己想  加在代理对象上面的方法
 *
 * 定义个接口 ,所有想写自己的代理的类的 处理逻辑的 都必须 实现该接口
 *
 * 然后将实现了 该接口的对象  接口传进来
 *
 *
 * 修改后是为了实现 TimProxy里面的内容
 *
 * this.handler.invoke(this, method, null);
 * 代码理解起来比较吃力  看个图
 *                                                                      InvocationHandler
 *                                  InterfaceT.java                     |·
 *                                  |           |                       聚合了MyInvocationHandler.java
 * Proxy.newProxyInstance----->TimeProxy      interfaceTimpl.java       |
 *                                  |                                   |
 *                                  ------------------------------------
 *
 *
 * TimeProxy.upload()---------->MyInvocationHandler.invoke()---->interfaceTimpl.upload()
 *
 * <p>Title: Proxy1</p>
 * <p>Description: </p>
 * @author 18045153
 * @date 2019年2月27日
 */
public class Proxy3 {

    //只要你在newProxyInstance方法中指定代理需要实现的接口Class clazz
    //jdk1.3 Class<?>[]:第二个参数也和我们的实现版本不一致,这个其实很容易理解,
    //我们应该允许我们自己实现的代理类同时实现多个接口。前面设计只传入一个接口,只是为了简化实现,让你专注核心逻辑实现而已
    public static Object newProxyInstance(Class clazz, InvocationHandler handler) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        //第一步 通过javapoet工具类 创建一个名为TimeProxy的java类 , 该类实现了interfaceT接口
        //InterfaceT.class ===   public class TimeProxy implement InterfaceT

        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder("Proxy")
                .addModifiers(Modifier.PUBLIC)//定义类的修饰符//构造一个类,类名
                .addSuperinterface(clazz);

        //第二步 创建类里面的属性 我们要注入该接口 具体参考interfaJuhe.java 的写法
        //我们希望的属性是 InterfaceT impl
        //所以指定 属性的类型 和 属性的名字 和 属性的范围

        //在生成的代理类中增加成员变量handler 效果就是 private InvocationHandler handler
        FieldSpec fieldSpec = FieldSpec.builder(InvocationHandler.class, "handler", Modifier.PRIVATE).build();

        //将属性 添加到类中
        typeSpecBuilder.addField(fieldSpec);

        //我们希望构造方法中 将该接口的实现类 传到构造器里 方便灵活调用要处理的方法
        //interfaJuhe(InterfaceT impl) {
        //    this.impl = impl;
        //  }

        //效果就是 public TimeProxy(InvocationHandler handler ) {
        //              this.handler = handler;
        //}
        MethodSpec constructorMethodSpec = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(InvocationHandler.class, "handler")//构造器传入参数的类型 就是你要创建的java类实现接口的类型, 属性名字
                .addStatement("this.handler = handler")//构造器里面的语句
                .build();

        typeSpecBuilder.addMethod(constructorMethodSpec);

        //类 ,属性 ,构造器都写完了, 接下来开始写方法 , 你实现的接口有N个方法
        //本来方法是写死的 , 现在该为
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {

            //在文件中写入方法 和 方法内容  方法里有返回值 入参 方法名
            MethodSpec methodSpec = MethodSpec.methodBuilder(method.getName())
                     //给方法添加范围
                    .addModifiers(Modifier.PUBLIC)
                     //给方法添加 注解  通常都是实现 x接口的覆盖注解
                    .addAnnotation(Override.class)
                     //给方法添加返回值    通过method.getReturnType能拿到该方法return接收的对象
                    .returns(method.getReturnType())
                     //给方法添加语句  这里是给每个方法都加上了一个计算时间

                    .addCode("try {\n")

                     //Method method = staticProxy.Servlet.class.getMethod("uploadFile");
                     //这样写是为了拿到 要代理的那个接口类的 xx 方法
                    .addStatement("\t$T method = " + clazz.getName() + ".class.getMethod(\"" + method.getName() + "\")", Method.class)
                     // 为了简单起见,这里参数直接写死为空

                     //这样写诗为了执行 实现了InvocationHandler接口的 java类自己的处理方法
                     // this.handler.invoke(this, method, null);
                    .addStatement("\t this.handler.invoke(this, method, null)")
                    .addCode("} catch(Exception e) {\n")
                    .addCode("\te.printStackTrace();\n")
                    .addCode("}\n")

                    .build();
            //给每个类加一个方法
            typeSpecBuilder.addMethod(methodSpec);
        }

        //生成一个 TypeSpec 前面定义的java文件  第一个参数是包名 这样生成的xx.java文件再次包下
        //package animateProxy;

        String sourcePath = "C:/Users/"+System.getProperties().getProperty("user.name")+"/Desktop";

        JavaFile javaFile = JavaFile.builder("proxy", typeSpecBuilder.build()).build();
        // 为了看的更清楚,我将源码文件生成到桌面

        javaFile.writeTo(new File(sourcePath));

        // 编译
        JavaCompiler.compile(new File(sourcePath + "/proxy/Proxy.java"));

        // 使用反射load到内存
        URLClassLoader classLoader = new URLClassLoader(new URL[] { new URL("file:C:\\Users\\"+System.getProperties().getProperty("user.name")+"\\Desktop\\") });

        Object obj = null;

        //Classloader:类加载器,你可以使用自定义的类加载器,我们的实现版本为了简化,直接在代码中写死了Classloader。

        Class clazz1 = classLoader.loadClass("proxy.Proxy");

        System.out.println(clazz1);
        System.out.println(clazz1.getDeclaredConstructors().getClass());

        //将生成的TimeProxy编译成class 使用类加载器加载进内存中 再通过反射或者该类的构造器
        //再通过构造器将其代理类 TimeProxy 构造出来

        //NoSuchException  打印classz信息 发现 刚开始创建类 没有使用public
        Constructor constructor = clazz1.getConstructor(InvocationHandler.class);
        System.out.println("constructor" + constructor);
        obj = constructor.newInstance(handler);

        return obj;

    }

}

执行

        Servlet a = (Servlet) Proxy3.newProxyInstance(Servlet.class, handler);

        System.out.println("返回的代理对象" + a);//返回的代理对象proxy.TimeProxy@5b2936fa

        //handler.invoke(a, a.getClass().getDeclaredMethod("uploadFile", null), null);
        //Proxy.newProxyInstance(Flyable.class, new MyInvocationHandler(new Bird()));
        // 代理了interfaceTimpl类 并且 写了自己的实现
        a.uploadFile();

如果报NoSuchException错误,  将自己桌面上生成的Proxy.java放到项目proxy包下面

五.JDK动态代理类的用法

上面的只是解释了动态代理的一种实现思路 , 童鞋们平时只要会用的话可以不用关注 ,需要关注有3点

1.通过jdk反射包 Proxy.newProxyInstance 方法可以完成 动态代理, 参数第一个是类加载器, 第二个是要代理的类的接口

第三个是实现了InvocationHandler接口的自定义类

2.实现InvocationHandler接口, 里面有3个参数 ,

第一个参数就是生成的动态代理类Proxy.java,

原文中说是 :

如果你的接口中有方法需要返回自身,如果在invoke中没有传入这个参数,将导致实例无法正常返回。

在这种场景中,proxy的用途就表现出来了。简单来说,这其实就是最近非常火的链式编程的一种应用实现。

这个暂时没看懂

第二个参数很简单,就是你要代理的 接口实现类 的方法对象,  通过method.invoke 可以执行原来的方法不受影响

第三个参数就是代理的方法的参数了

3.动态代理的设计思路,为什么要有动态代理, 因为上面的代码也展示了,在不动用原来代码逻辑的情况下, 可以加入自己的逻辑

这在java中是一种AOP思想, 这种思想的应用场景有 比如 日志管理, 权限管理, 事务管理

六.动态代理的实际运用

示例来自https://blog.csdn.net/yerenyuan_pku/article/details/52598220李阿昀的博客

在动态代理技术里,由于不管用户调用代理对象的什么方法,

都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用),

比如如果有个方法 c.create(HttpServlet server);需要传入一个实现了HttpServlet接口的参数 

那么可以利用动态代理技术 完成对被代理对象方法的拦截

Proxy.newProxyInstance实际上就是生成了一个 Proxy implements HttpServlet 这样一个java类

相当于传入一个c.create(Proxy)

而Proxy实现了HttpServlet接口,自然实现了该接口里的所有方法, 在每个方法里实际上执行的是

Method method = staticProxy.Servlet.class.getMethod("xxxx");
this.handler.invoke(this, method, null);

这样两句话

真正执行的方法是 xx  implements InvocationHandler 里的 invoke方法, 并且给你传入了你要 代理的对象的 方法对象

并且开发人员通过invoke方法的参数,还可以在拦截的同时,

知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,

例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

package 动态代理的实际运用;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 利用动态代理技术编写一个用于处理全站中文乱码的过滤器
 *
 * 实现了对HttpServlet.getParamter方法的拦截
 *
 * <p>Description: </p>
 * @author 18045153
 * @date 2019年2月28日
 */
public class CharacterEncodingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        request.setCharacterEncoding("UTF-8"); // 解决post方式请求下的中文乱码问题    get:request.getParameter()

        // servlet-----> requestRroxy.getCookie()
        //传入自定义的 ServletRequest 对象
        chain.doFilter((ServletRequest) Proxy.newProxyInstance(
                CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
                //类加载器,你可以使用自定义的类加载器                      HttpServletRequest接口 , 这样动态生成的代理类 实现了该接口下的所有方法

            //传入的method对象 实际就表示是  HttpServletRequest接口下的所有方法的对象
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                /*
                 * 判断在Servlet那端调用的是不是getParameter方法,
                 * 如不是则不需要拦截,直接放行,直接调用tomcat的request(真的request)帮你把事情干了,
                 * 并且args参数还要接着往下传
                 */
                if (!method.getName().equals("getParameter")) {
                    return method.invoke(request, args); // 由于是内部类,所以request需要声明为final(但在Java8中没这个必要了)
                }
                // 判断客户端的请求方式是不是get
                if (!request.getMethod().equalsIgnoreCase("get")) {
                    return method.invoke(request, args);
                }

                //执行完 HttpServletRequest 的public abstract String getParameter(String s);方法拿到的返回值
                String value = (String) method.invoke(request, args);
                if (value == null) {
                    return null;
                }

                // return new String(value.getBytes("ISO8859-1"), "UTF-8");
                return new String(value.getBytes("UTF-8"), "UTF-8");
            }
        }), response);

    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

       //在web.xml文件中配置CharacterEncodingFilter。
    //<filter>
   // <filter-name>CharacterEncodingFilter</filter-name>
  //  <filter-class>cn.itcast.web.filter.CharacterEncodingFilter</filter-class>
//</filter>
//<filter-mapping>
  //  <filter-name>CharacterEncodingFilter</filter-name>
 //   <url-pattern>/*</url-pattern>
//</filter-mapping>

}

原文地址:https://www.cnblogs.com/tom-kang/p/10455474.html

时间: 2024-10-12 00:45:43

啰里吧嗦式讲解java静态代理动态代理模式的相关文章

代理模式(静态代理+动态代理)——JAVA

代理模式是常用的java设计模式,他的特征是代理类与目标类有同样的接口,代理类主要负责为目标类预处理消息.过滤消息.把消息转发给目标类,以及事后处理消息等.代理类与目标类之间通常会存在关联关系,一个代理类的对象与一个目标类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用目标类的对象的相关方法,来提供特定的服务. 结构图如下: 按照代理的创建时期,代理类可以分为静态代理和动态代理. 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类(Proxy)的.clas

【SSH系列】静态代理&amp;&amp;动态代理

从设计模式说起 代理模式是二十三中设计模式中的一种,代理模式就是指由一个代理主题来操作真实的主题,真实的主题执行具体的业务操作,而代理主题负责其她相关业务,简而言之,代理模式可以由以下三个部分组成: a.抽象角色:通过接口或抽象类声明真实角色实现的业务方法. b.代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作. c.真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用.第一次接触代理模式的是在学习大话设计模式的时候,首先

Java学习笔记——动态代理

所谓动态,也就是说这个东西是可变的,或者说不是一生下来就有的.提到动态就不得不说静态,静态代理,个人觉得是指一个代理在程序中是事先写好的,不能变的,就像上一篇"Java学习笔记--RMI"中的远程代理,其中客户端服务对象就是一个远程服务对象的代理,这个代理可以使得客户在操作时感觉像在操作本地对象一样,远程对象对于客户是透明的.我们可以看出这里的远程代理,是在程序中事先写好的,而本节我们要讨论的远程代理,是由JVM根据反射机制,在程序运行时动态生成的.(以上是本人的理解,如果有不正确的地

java反射与动态代理

Java反射与动态代理 Java反射机制可以动态地获取类的结构,动态地调用对象的方法,是java语言一个动态化的机制.java动态代理可以在不改变被调用对象源码的前提下,在被调用方法前后增加自己的操作,极大地降低了模块之间的耦合性.这些都是java的基础知识,要想成为一名合格的程序猿,必须掌握! Java反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为

spring 代理(静态代理&amp;动态代理&amp;cglib代理)

介绍spring AOP之前 先介绍三种常见的代理方式:静态代理,动态代理,cglib代理 代理概述: 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式:即通过代理访问目标对象.这样好处: 可以在目标对象实现的基础上,增强额外的功能操作.(扩展目标对象的功能). 举例:明星(邓紫棋)<------经纪人<-------用户 目标        (代理) 一.静态代理 1)代理的对象要实现与目标对象一样的接口 2)举例:保存用户(模拟) Dao,直接保存 DaoProxy,给保存

使用Java中的动态代理实现数据库连接池

2002 年 12 月 05 日 作者通过使用JAVA中的动态代理实现数据库连接池,使使用者可以以普通的jdbc连接的使用习惯来使用连接池. 数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈.我们可以在 互联网上找到很多关于数据库连接池的源程序,但是都发现这样一个共同的问题:这些连接池的实现方法都不同程度地增加了与使用者之间的耦合度.很多的连接池 都要求用户通过其规定的方法获取数据库的连接,这一点我们可以理解,毕竟目前

细说java系统之动态代理

代理模式 在深入学习动态代理之前,需要先掌握代理模式.只有深刻理解了代理模式的应用,才能充分理解Java动态代理带来的便利. 在生活中存在许多使用"代理模式"的场景,比如:村里的张三今年已经30岁了,但是还没结婚,可把他老妈给愁坏了,于是就拜托村东头的王媒婆给儿子找个媳妇. 在这里,要娶媳妇的人是张三,但是他不能直接跑到女方家把人家闺女直接带回来,需要中间人王媒婆上门说媒,在这里王媒婆就是一个代理. 另外,我们上大学的时候都知道,学校的机房都是通过一个代理服务器上网的,因为只有一个外网

十分钟理解Java中的动态代理

十分钟帮助大家理解Java中的动态代理,什么是动态代理?感兴趣的小伙伴们可以参考一下 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. 一.概述1. 什么是代理我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家"委托"代理为其销售商品.关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,"委托

java中的动态代理(二)

上一节我介绍了什么是静态代理.在静态代理中的代理对象是直接定义在代码中的,这样会导致代码不能复用并且工作量也会成倍的增加所以在日常的开发中我们更多使用的是动态代理模式.在动态代理中,代理类在是程序运行中动态生成的,在java中一般有两种方式来实现动态代理模式,它们分别是javaSDK动态代理和第三方类库cglib动态代理. 今天我介绍的是java SDK动态代理.首先我们先来看一下如何使用java SDK实现动态代理模式: public class JavaSDKProxyTest { stat