动态代理学习笔记

来源:互联网 发布:网络考试系统软件 编辑:程序博客网 时间:2024/06/07 11:41

动态代理学习笔记

一、动态代理

说明:学习动态代理前,先了解设计模式:代理模式!代理模式是什么?可以先看看这篇文章:Java的三种代理模式

网址:https://www.cnblogs.com/cenyu/p/6289209.html

1、学习动态代理的目的

动态代理技术都是在框架中使用,例如:Struts1、Struts2、Spring和Hibernate中都使用了动态代理技术。如果你不想自己写个框架,那么你基本上是用上不动态代理技术的。

我们学习动态代理技术的目的是为了更好的理解框架内部的原理,也就是说是为了将来我们学习框架打基础!

动态代理技术有点小难度!而且明白了动态代理技术可能一时也想不到他适合在什么情况下使用它。这些东西都会在学习框架时渐渐明白。

2、学习明确两个概念

明确两个概念:

代理对象存在的价值:主要用于拦截对真实业务对象的访问。

代理对象有什么方法?

3、如何编写生成代理对象的类

现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个类来生成,所以首先要编写用于生成代理对象的类。

如何编写生成代理对象的类,两个要素:

【1】代理谁?

设计一个类变量,以及一个构造函数,记住代理类,代理哪个对象。

【2】如何生成代理对象?

设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点)

4、Proxy类可生成代理对象

Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:

1.生成代理对象使用哪个类装载器

2.生成哪个对象的代理对象,通过接口指定

3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。

5、初学者必须必须记住的2件事情

初学者必须必须记住的2件事情:

Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。

由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。

二、动态代理须知

1、运行时实现指定的接口

想实现某个接口,你需要写一个类,然后在类名字的后面给出“implements”XXX接口。这才是实现某个接口:

public interface MyInterface {

    void fun1();

    void fun2();

}

public class MyInterfaceImpl implements MyInterface {

    public void fun1() {

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

    }

 

    public void fun2() {

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

    }

}

 

上面的代码对我们来说没有什么新鲜感,我们要说的是动态代理技术可以通过一个方法调用就可以生成一个对指定接口的实现类对象。

       Class[] cs = {MyInterface.class};

       MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);

 

上面代码中,Proxy类的静态方法newProxyInstance()方法生成了一个对象,这个对象实现了cs数组中指定的接口。没错,返回值miMyInterface接口的实现类。

你不要问这个类是哪个类,你只需要知道mi是MyInterface接口的实现类就可以了。你现在也不用去管loader和h这两个参数是什么东东,你只需要知道,Proxy类的静态方法newProxyInstance()方法返回的方法是实现了指定接口的实现类对象,甚至你都没有看见实现类的代码。

动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有.java文件,是在运行时生成的,你也不用去关心它是什么类型的,你只需要知道它实现了哪些接口即可。

 

2、newProxyInstance()方法的参数

Proxy类的newInstance()方法有三个参数:

l  ClassLoader loader:它是类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了:MyInterface.class.getClassLoader()就可以获取到ClassLoader对象,没错,只要你有一个Class对象就可以获取到ClassLoader对象;

l  Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs = {MyInterface.class};

l  InvocationHandler h:它是最重要的一个参数!它是一个接口!它的名字叫调用处理器!你想一想,上面例子中mi对象是MyInterface接口的实现类对象,那么它一定是可以调用fun1()和fun2()方法了,难道你不想调用一下fun1()和fun2()方法么,它会执行些什么东东呢?其实无论你调用代理对象的什么方法,它都是在调用InvocationHandler的invoke()方法!

 

    public static void main(String[] args) {

       Class[] cs = {MyInterface.class};

       ClassLoader loader = MyInterface.class.getClassLoader();

       InvocationHandler h = new InvocationHandler() {

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

                  throws Throwable {

              System.out.println("无论你调用代理对象的什么方法,其实都是在调用invoke()...");

              return null;

           }

       };

       MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);

       mi.fun1();

       mi.fun2();

    }

 

  InvocationHandler接口只有一个方法,即invoke()方法!它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用InvocationHandlerinvoke()方法。

 

想象中的类:

class X implements MyInterface {

    private InvocationHandlerh;

    public X(InvocationHandler h) {

       this.h = h;

    }

   

    public void fun1() {

       h.invoke();

    }

    public void fun2() {

       h.invoke();

    }

}

 

  注意,X类是我们用来理解代理对象与InvocationHandler之间的关系的,但它是不存在的类。是我们想象出来的!也就是说,它是用来说明,无论你调用代理对象的哪个方法,最终调用的都是调用处理器的invoke()方法。

 

3、InvocationHandler的invoke()方法

InvocationHandler的invoke()方法的参数有三个:

l  Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;

l  Method method:表示当前被调用方法的反射对象,例如mi.fun1(),那么method就是fun1()方法的反射对象;

l  Object[] args:表示当前被调用方法的参数,当然mi.fun1()这个调用是没有参数的,所以args是一个零长数组。

  最后要说的是invoke()方法的返回值为Object类型,它表示当前被调用的方法的返回值,当然mi.fun1()方法是没有返回值的,所以invoke()返回的就必须是null了。

    public static void main(String[] args) {

       Class[] cs = {MyInterface.class};

       ClassLoader loader = MyInterface.class.getClassLoader();

       InvocationHandler h = new InvocationHandler() {

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

                  throws Throwable {

              System.out.println("当前调用的方法是:" + method.getName());

               return null;

           }

       };

       MyInterface mi = (MyInterface)Proxy.newProxyInstance(loader, cs, h);

       mi.fun1();

       mi.fun2();

    }

当前调用的方法是:fun1

当前调用的方法是:fun2

 

三、动态代理的用途

(1)动态代理的用途与装饰模式很相似,就是为了对某个对象进行增强。所有使用装饰者模式的案例都可以使用动态代理来替换。

(2)在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:解决web工程乱码、拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

四、动态代理案例

1、案例一:

下面我们用一个例子来说明动态代理的用途!

我们来写一个Waiter接口,它只有一个serve()方法。MyWaiter是Waiter接口的实现类:

public interface Waiter {

    public void serve();

}

public class MyWaiter implements Waiter {

    public void serve() {

       System.out.println("服务...");

    }

}

 

现在我们要对MyWaiter对象进行增强,要让它在服务之前以及服务之后添加礼貌用语,即在服务之前说“您好!”,在服务之后说:“很高兴为您服务!”。

 

说明:此种用法自己实现了InvocationHandler接口

public class MainApp1 {

    public static void main(String[] args) {

       ClassLoader loader = MainApp1.class.getClassLoader();

       Class[] cs = {Waiter.class};

       Waiter target = new MyWaiter();

       MyInvocationHandler h = new MyInvocationHandler(target);

       Waiter waiter = (Waiter)Proxy.newProxyInstance(loader, cs, h);

       waiter.serve();

    }

}

 

class MyInvocationHandlerimplements InvocationHandler {

    public Waitertarget;

    public MyInvocationHandler(Waiter target) {

       this.target = target;

    }

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

           throws Throwable {

       System.out.println("您好!");

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

        System.out.println("很高兴为您服务!");

       return result;

    }

}

 

 

2、案例二:

代码组成:Person接口+ Singer(真实业务对象)+ SingerProxy(代理对象)+ ProxyDemo(测试类)

其中:Singer实现了Person接口

用途: SingerProxy(代理对象)进行了Singer(真实业务对象)的功能增强

(1)Person接口

packageproxy2;//Person接口public interface Person {    public String sing(String name);   public String dance(String name);}

(2)Singer(真实业务对象)

package proxy2;//歌手对象public class Singer implements Person{@Overridepublic String sing(String name) {System.out.println("歌手 开始唱歌了...");return "唱起来";}@Overridepublic String dance(String name) {System.out.println("歌手 开始跳舞了...");return "跳起来";}}

(3)SingerProxy(代理对象)

package proxy2;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;//歌手对象的代理对象public class SingerProxy{ final Singer singer=new Singer();//歌手对象(真实业务对象)//返回歌手对象的代理public Object getProxy(){//疑问:如何获得代理对象?获得代理对象后该如何进行方法的加强//Proxy类,jdk中提供好的而立,用于返回真实业务对象的代理对象//约定,真实的业务对象和代理对象都必须要有一个共同的接口//loader:类加载器(:任意的类加载器都行)ClassLoader loader = SingerProxy.class.getClassLoader();//interfaces:实现的接口(要的是真是的要产生是哪个类的代理对象)的数组//Class<?>[] interfaces = SingerProxy.class.getInterfaces();//使用错误Class<?>[] interfaces = Singer.class.getInterfaces();//使用正确//Class<?>[] interfaces2 = singer.getClass().getInterfaces();//使用正确//【new InvocationHandler()】handler:处理器,用来指定产生的代理对象用来干什么//代理对象:singerProxyPerson singerProxy = (Person)Proxy.newProxyInstance(loader, interfaces, new InvocationHandler(){/* * Object proxy:代理对象 * Method method:调用代理对象的方法 * Object[] args:调用代理对象的方法时传递的参数 */@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {//注意:真实业务对象:singer  代理对象:singerProxy(this)//当前方法为sing时if(method.getName().equals("sing")){//说明调用的是sing方法,在此添加功能增强代码System.out.println("唱歌功能增强.....");//代理对象自己的功能加强代码return method.invoke(singer, args);//返回真是业务对象:singer对象的方法//return method.invoke(singer, args)等价于:singer.sing(args)方法执行了}else if(method.getName().equals("dance")){System.out.println("跳舞功能增强...");//功能加强return method.invoke(singer, args);//调用singer.dance(args)方法}else{System.out.println("没有此业务..........");return null;}}});//返回了代理对象//【只要调用代理对象的任何方法,都会走到处理器的invoke里面的逻辑】//代理对象可以使用真实业务对象的方法,而且可以在那些方法上添加功能,进行方法的功能加强return singerProxy;}}

(4)ProxyDemo(测试类)

package proxy2;import org.junit.Test;/* * 动态代理 *    为了去实现拦截对真实业务对象的直接访问 *     * 动态代理不是常规编程,是基于反射去实现的。 * 查看源代码,是通过反射拿到字节码,然后拿到构造函数,创建了另外一个实例对象,这个实例对象就是代理对象; * 当调用了代理对象的方法时,会去调用真实的业务对象的对应的方法,从而实现了拦截对真实业务对象的直接访问 * 【只要调用代理对象的任何方法,都会走到处理器的invoke里面的逻辑,都通invoke方法就可以调用真实业务对象中名字和参数与代理对象一一对应的方法】 */public class ProxyDemo {@Testpublic void test1(){System.out.println("【真实业务对象实现】");System.out.println("-----------------------------------");Singer s=new Singer();s.sing("时间");s.dance("小天鹅");System.out.println();System.out.println("【代理对象实现】");System.out.println("-----------------------------------");SingerProxy pro=new SingerProxy();Person singerProxy=(Person) pro.getProxy();singerProxy.sing("时间");singerProxy.dance("小天鹅");}}

(5)测试结果

【真实业务对象实现】

-----------------------------------

歌手 开始唱歌了...

歌手 开始跳舞了...

 

【代理对象实现】

-----------------------------------

唱歌功能增强.....

歌手 开始唱歌了...

跳舞功能增强...

歌手 开始跳舞了...

 

原创粉丝点击