Java代理模式

Posted 有梦就能实现

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java代理模式相关的知识,希望对你有一定的参考价值。

1,什么是代理模式?

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。

2,代理模式有什么好处?

在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

3,代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。

代理角色:代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象,同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。


真实角色:定义了代理对象所代表的目标对象代理角色所代表的真实对象,是我们最终要引用的对象,定义了代理对象所代表的目标对象。

代理实现可以分为静态代理和动态代理。

静态代理

静态代理模式其实很常见,比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票。在代码实现中相当于为一个委托对象realSubject提供一个代理对象proxy,通过proxy可以调用realSubject的部分功能,并添加一些额外的业务处理,同时可以屏蔽realSubject中未开放的接口。


 

1、RealSubject 是委托类,Proxy 是代理类;
2、Subject 是委托类和代理类的接口;
3、request() 是委托类和代理类的共同方法;

具体代码实现如下:

interface Subject {
    void request();
}

class RealSubject implements Subject {
    public void request(){
        System.out.println("RealSubject");
    }
}

class Proxy implements Subject {
    private Subject subject;

    public Proxy(Subject subject){
        this.subject = subject;
    }
    public void request(){
        System.out.println("begin");
        subject.request();
        System.out.println("end");
    }
}

public class ProxyTest {
    public static void main(String args[]) {
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

静态代理实现中,一个委托类对应一个代理类,代理类在编译期间就已经确定。

动态代理

动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理,下面通过一个例子看看如何实现jdk动态代理。

1、定义业务逻辑

public interface Service {  
    //目标方法 
    public abstract void add();  
} 

public class UserServiceImpl implements Service {  
    public void add() {  
        System.out.println("This is add service");  
    }  
}

2、利用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口定义代理类的实现。

class MyInvocatioHandler implements InvocationHandler {
    private Object target;

    public MyInvocatioHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-----before-----");
        Object result = method.invoke(target, args);
        System.out.println("-----end-----");
        return result;
    }
    // 生成代理对象
    public Object getProxy() {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        return Proxy.newProxyInstance(loader, interfaces, this);
    }
}

3、使用动态代理

public class ProxyTest {
    public static void main(String[] args) {
        Service service = new UserServiceImpl();
        MyInvocatioHandler handler = new MyInvocatioHandler(service);
        Service serviceProxy = (Service)handler.getProxy();
        serviceProxy.add();
    }
}

执行结果:

-----before-----
This is add service
-----end-----

代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:
1、ProxyGenerator.generateProxyClass方法负责生成代理类的字节码,生成逻辑比较复杂,有兴趣的同学可以继续分析源码sun.misc.ProxyGenerator

// proxyName:格式如 "com.sun.proxy.$Proxy.1";
// interfaces:代理类需要实现的接口数组;
// accessFlags:代理类的访问标识;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

2、native方法Proxy.defineClass0负责字节码加载的实现,并返回对应的Class对象。

Class clazz = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

3、利用clazz.newInstance反射机制生成代理类的对象;

反编译代理类
为了更清楚的理解动态代理,通过以下方式把代理类字节码生成class文件。

byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.1", service.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.1.class");
out.write(classFile);
out.flush();

使用 反编译工具 jad jad com.sun.proxy.$Proxy.1 看看代理类如何实现,反编译出来的java代码如下:

public final class $proxy1 extends Proxy implements Service {

    public $proxy1(InvocationHandler invocationhandler) {
        super(invocationhandler);
    }

    public final boolean equals(Object obj) {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void add() {
        try {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode() {
        try {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("zzzzzz.Service").getMethod("add", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception) {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception) {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

从上述代码可以发现:
1、生成的$proxy1继承自Proxy类,并实现了Service接口。
2、执行代理对象的方法,其实就是执行InvocationHandle对象的invoke方法,传入的参数分别是当前代理对象,当前执行的方法和参数。

super.h.invoke(this, m3, null);

jdk动态代理使用的局限性
通过反射类ProxyInvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。

 

    这篇博客对应上篇博客《静态代理模式》,我们来说一下动态代理,静态代理之所以扩展和维护比较困难,是因为代码写的太死,没有可替换的余地;针对代码写得死能想到什么解决办法?对,就是反射。

    使用反射可以很到的解决决定加载哪个代理类的问题,避免了每个代理类都要重复写的问题,话不多说,来看代码。

动态代理

 

    接口UserManager

[java] view plain copy
 
 print?
  1. /*** 
  2.  * 用户控制接口 
  3.  * @author Administrator 
  4.  * 
  5.  */  
  6. public interface UserManager {  
  7.   
  8.     public void addUser(String userId,String userName);  
  9.     public void modifyUser(String userId,String userName);  
  10.     public void delUser(String userId);  
  11.     public String findUser(String userId);  
  12. }  

    实现类UserManagerImpl

[java] view plain copy
 
 print?
  1. /**** 
  2.  * 用户管理真正的实现类 
  3.  * @author Administrator 
  4.  * 
  5.  */  
  6. public class UserManagerImpl implements UserManager {  
  7.   
  8.     /***** 
  9.      * 添加用户 
  10.      */  
  11.     public void addUser(String userId, String userName) {  
  12.             System.out.println("正在添加用户,用户为:"+userId+userName+"……");  
  13.     }  
  14.     /***** 
  15.      * 删除用户 
  16.      */  
  17.     public void delUser(String userId) {  
  18.         System.out.println("delUser,userId="+userId);  
  19.     }  
  20.     /*** 
  21.      * 查找用户 
  22.      */  
  23.     public String findUser(String userId) {  
  24.         System.out.println("findUser,userId="+userId);  
  25.         return userId;  
  26.     }  
  27.   
  28.     public void modifyUser(String userId, String userName) {  
  29.         System.out.println("modifyUser,userId="+userId);  
  30.     }  
  31. }  

    代理类LogHandler

 

 

[java] view plain copy
 
 print?
  1. import java.lang.reflect.InvocationHandler;  
  2. import java.lang.reflect.Method;  
  3. import java.lang.reflect.Proxy;  
  4.   
  5. public class LogHandler implements InvocationHandler {  
  6.       
  7.     private Object targetObject;  
  8.       
  9.     public Object newProxyInstance(Object targetObject) {  
  10.         this.targetObject = targetObject;  
  11.         return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),  
  12.                                targetObject.getClass().getInterfaces(), this);  
  13.     }  
  14.       
  15.     public Object invoke(Object proxy, Method method, Object[] args)  
  16.             throws Throwable {  
  17.         Object ret = null;  
  18.               
  19.         try {  
  20.             System.out.println("正在进行操作前的准备工作……");  
  21.             //调用目标方法  
  22.             ret = method.invoke(targetObject, args);  
  23.             System.out.println("操作成功,正在进行确认处理……");  
  24.         } catch (Exception e) {  
  25.             e.printStackTrace();  
  26.             System.out.println("error-->>" + method.getName());  
  27.             throw e;  
  28.         }  
  29.         return ret;  
  30.     }  
  31. }  

 

 

    客户端Client

 

[java] view plain copy
 
 print?
  1. public class Client {  
  2.   
  3.     /** 
  4.      * @param args 
  5.      */  
  6.     public static void main(String[] args) {  
  7.         LogHandler logHandler = new LogHandler();  
  8.         UserManager userManager = (UserManager)logHandler.newProxyInstance(new UserManagerImpl());  
  9.         userManager.findUser("0001");  
  10.     }  
  11. }  

 

    运行结果

    

    时序图

    
 
 

总结

    动态代理模式通过使用反射,可以在运行期决定加载哪个类,避免了一个类对应一个代理的问题;同时,通过统一的invoke方法,统一了代理类对原函数的处理过程,使用动态代理很大程度上减少了重复的代码,降低了维护的复杂性和成本。
 

主要用来做方法的增强,让你可以在不修改源码的情况下,增强一些方法,在方法执行前后做任何你想做的事情(甚至根本不去执行这个方法),因为在InvocationHandler的invoke方法中,你可以直接获取正在调用方法对应的Method对象,具体应用的话,比如可以添加调用日志,做事务控制等。

还有一个有趣的作用是可以用作远程调用,比如现在有Java接口,这个接口的实现部署在其它服务器上,在编写客户端代码的时候,没办法直接调用接口方法,因为接口是不能直接生成对象的,这个时候就可以考虑代理模式(动态代理)了,通过Proxy.newProxyInstance代理一个该接口对应的InvocationHandler对象,然后在InvocationHandler的invoke方法内封装通讯细节就可以了。具体的应用,最经典的当然是Java标准库的RMI,其它比如hessian,各种webservice框架中的远程调用,大致都是这么实现的。
动态代理是JAVA的一大特性。
动态代理的优势就是实现无侵入式的代码扩展。
目前动态代理主要分为JAVA自己提供的动态代理和CGLIB类似框架。
JAVA自带的动态代理是需要接口的。CGLIB这种则是直接修改字节码。
 
 

一、代理模式的概念及分类

代理模式的概念:为其它对象提供一种代理,以控制对这个对象的访问。

分类:远程代理、智能引用代理、保护代理、虚拟代理。

远程代理:为不同地理的对象提供局域网代表对象。

虚拟代理:根据需要将资源消耗很大的对象进行延迟,真正需要的时候进行创建。

保护代理:控制对一个对象的访问权限。

智能引用代理:提供对目标对象额外的一些服务。

二、举个栗子

我们以智能引用代理为例,分别采用静态代理和动态代理的方式实现。

静态代理:代理和被代理对象在代理之前是确定的。他们都实现相同的接口或者继承相同的抽象类。

假设一辆小车有一个行驶的方法,然后我们通过代理实现这个行驶的方法,同时增加记录行驶时间的方法。

 

首先定义接口Moveable

public interface Moveable{

      void move();

}

然后定义Car并实现Moveable接口

public class Car implements Moveable {

 

      @Override

      public void move() {

         try {

            System.out.println("汽车行驶中...");

            Thread.sleep(new Random().nextInt(1000));

         } catch (InterruptedException e) {

            e.printStackTrace();

         }

      }

}

1、通过继承的方式定义Car2

 

public class Car2 extends Car {

 

      @Override

      public void move() {

         long startTime = System.currentTimeMillis();

         System.out.println("汽车开始行驶");

         super.move();

         long endTime = System.currentTimeMillis();

         System.out.println("汽车停止行驶,行驶时间:" + (endTime - startTime)+"毫秒");

      }

}

通过上面的代码我们定义了Car2并继承了Car,在Car2的move方法中我们调用了父类的move方法。

 

2、通过聚合的方式

 

public class Car3 implements Moveable {

 

      private Car car;

 

      public Car3(Car car) {

         super();

         this.car = car;

      }

 

      @Override

      public void move() {

         long startTime = System.currentTimeMillis();

         System.out.println("汽车开始行驶");

         car.move();

         long endTime = System.currentTimeMillis();

         System.out.println("汽车停止行驶,行驶时间:" + (endTime - startTime) + "毫秒");

      }

}

 

我们通过继承和聚合的方式分别实现了汽车行驶过程中记录行驶时间的方法,如果需要进一步的拓展,比如:记录日志、权限等方法,通过继承的方式就需要定义Car4、Car5等,显然这种做法不值得推荐。

例如:我们要实现汽车行驶过程中记录日志和行驶时间的功能。

1、记录行驶时间的代理

 

public class CarTimeProxy implements Moveable {

 

   private Moveable m;

 

   public CarTimeProxy(Moveable m) {

      super();

      this.m = m;

   }

 

   @Override

   public void move() {

      long startTime = System.currentTimeMillis();

      System.out.println("汽车开始行驶");

      m.move();

      long endTime = System.currentTimeMillis();

      System.out.println("汽车停止行驶,行驶时间:" + (endTime - startTime) + "毫秒");

   }

 

}

2、记录行驶日志的代理

 

public class CarLogProxy implements Moveable {

 

   private Moveable m;

 

   public CarLogProxy(Moveable m) {

      super();

      this.m = m;

   }

 

   @Override

   public void move() {

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

      m.move();

      System.out.println("日志结束");

   }

}

 

3、测试,先记录日志再记录时间

 

public static void main(String[] args) {

      Carcar = new Car();

      CarTimeProxyctp = new CarTimeProxy(car);

      CarLogProxyclp = new CarLogProxy(ctp);

      clp.move();

}

 

 

通过将记录时间的实例传递给记录日志的实例,实现了先记录日志再记录时间的操作。如果想实现先记录时间在记录日志,将两个实例对象的传递交换一下就能够实现想要的操作。

 

动态代理

Java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

(1)  Interface InvocationHandler:该接口中仅定义了一个方法

public objectinvoke(Object obj, Methond method, Object[] args)在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。

(2)  Proxy:该类即为动态代理类

static ObjectnewProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类在接口中声明过的方法)

 

还是上面的栗子,我们使用动态代理的方式实现。

1、定义TimeHandler实现InvocationHandler接口

 

public class TimeHandler implements InvocationHandler {

 

   private Object target;

 

   public TimeHandler(Object target) {

      super();

      this.target = target;

   }

 

   @Override

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

      // TODO Auto-generated method stub

      long startTime = System.currentTimeMillis();

      System.out.println("汽车开始行驶");

      method.invoke(target);

      long endTime = System.currentTimeMillis();

      System.out.println("汽车停止行驶,行驶时间:" + (endTime - startTime) + "毫秒");

      return null;

   }

 

}

2、测试

public static void main(String[] args) {

      // TODO Auto-generated method stub

      Carcar = new Car();

      InvocationHandlerh = new TimeHandler(car);

      Class<?>cls = car.getClass();

      Moveablem = (Moveable)Proxy.newProxyInstance(cls.getClassLoader(),

            cls.getInterfaces(), h);

      m.move();

}

 

三、代理模式优缺点

优点

1、代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;

2、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了保护目标对象的作用。

 

缺点

1、由于在客户端和真实对象之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢;

2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

四、代理模式总结

1、代理模式是通过使用引用代理对象来访问真实对象,在这里代理对象充当用于连接客户端和真实对象的中介。

2、代理模式主要用于远程代理、虚拟代理和保护代理,其中保护代理可以进行访问权限控制。

以上是关于Java代理模式的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式-代理模式之动态代理(附源代码分析)

java代理模式

JAVA设计模式 -- 代理模式

Java 代理模式讲解

代理模式(动态)

Java业务代理模式~