JAVA 代理模式

来源:互联网 发布:八爪鱼数据抓取 知乎 编辑:程序博客网 时间:2024/06/14 17:15

代理模式的应用场景主要有四种。
1) 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中(例:WebService, WCF, RPC 之类的)。
2) 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建(例:浏览器分阶段载入信息,先文字再图片)。
3) 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限(例:多级权限系统)。
4) 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果(例:访问频繁时用的缓冲机制)。
5) 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数、方法的执行记录下来等。

实际上,各种应用场景的实现方式是类似的,下面以智能引用代理场景为例对代理模式进行介绍。假设我们有一个服务是用户登录到网站的操作。其接口及实现类如下。

public interface UserLogin {    boolean login(User user);}public class UserLoginImpl implements UserLogin {    @Override    public boolean login(User user) {        //validateUser 检查是否是合法用户        if(validateUser(user))             return true;        return false;    }}

现在需要在用户登录前,打印用户登录日志,那么我们可以创建一个 UserLoginProxy 代理类,在其中添加打印日志的操作。代理类需要实现被代理类的接口,这样代理类才能替代被代理类而被客户端调用。

public class UserLoginProxy implements UserLogin {    private UserLogin userLogin;    //1. 控制用户对 UserLoginImpl 的访问权限,在代理类中生成被代理对象    public UserLoginProxy(){        userLogin = new UserLoginImpl();    }    //2. 采用注入的方式得到被代理的对象,Spring AOP 即用注入获得被代理对象    public UserLoginProxy(UserLogin userLogin){        this.userLogin = userLogin;    }    @Override    public boolean login(User user) {        System.out.println("User " + user.getUserId() + " is logging in.");        return userLogin.login(user);    }}

客户端调用 UserLogin 的地方改为调用其代理类 UserLoginProxy ,即实现了代理。

public class UserLoginService {    public boolean consumer(UserLogin userLogin, User user){        return userLogin.login(user);    }       public static void main(String[] args) {        UserLoginService userLoginService = new UserLoginService();        User user = new User("John Smith");        UserLoginProxy userLogin = new UserLoginProxy();        userLoginService.consumer(userLogin, user);    }}

代理类 UserLoginProxy 和被代理类 UserLoginImpl 实现相同的接口,从客户端消费者角度来看,二者并没有区别,因此代理类可以出现在原本需要被代理类的地方,达到了记录执行日志的目的。这种手动写出代理类的方式我们称之为静态代理,其实用性并不高,我们不可能对每个需要记录执行日志的类手动创建一个代理类。
Java 提供了动态代理的方式,使得开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。
首先,我们提供一个统一的代理类生成类(实现 InvocationHandler 接口),并在其中写明代理类需要增加的操作(例如打印执行日志)。注意,DynamicProxy 定义的是代理行为而非代理类本身。实际上代理类及其实例是在运行时通过反射动态创建出来的。

import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class DynamicProxy implements InvocationHandler {    //也可以采用注入的方式得到被代理的对象    private Object proxied;    public DynamicProxy(Object proxied){        this.proxied = proxied;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if("login".equals(method.getName())){            User user = (User) args[0];            System.out.println("User "+ user.getUserId() + " is logging in.");        }else{            System.out.println("Before " + proxy.getClass().getSimpleName() + " " + method + " method");        }        Object rtn = method.invoke(proxied, args);        return rtn;    }}

在客户端消费者需要使用被代理类时,采用如下的方式进行调用。

public class UserLoginService {    public boolean consumer(UserLogin userLogin, User user){        return userLogin.login(user);    }       public static void main(String[] args) {        UserLoginService userLoginService = new UserLoginService();        UserLogin userLogin = (UserLogin) Proxy.newProxyInstance(UserLogin.class.getClassLoader(),        new Class[]{UserLogin.class}, new DynamicProxy(new UserLoginImpl()))         User user = new User("John Smith");        userLoginService.consumer(userLogin, user);    }}

不止 UserLogin ,当需要对其它的方法进行执行日志记录时,我们可以用同样的方式,动态生成其代理类。
从以上的例子可以看出,使用 JDK 生成动态代理类要求被代理类至少实现了一个接口,如果被代理类实现了多个接口,可以在 proxy.newProxyInstance() 时对多个接口同时实现代理,使代理类实现相同的每一个接口,这样在每一个用到了被代理类的地方,都能使用代理类替代。
如果被代理类没有实现任何接口,而又需要对其进行代理时,可以借助一个高性能的代码生成库 cglib 来实现。Spring AOP 也引入了 cglib 工具,当被代理类实现了接口时,Spring AOP 使用 JDK 自带的动态代理方式进行 AOP 操作;如果被代理类没有实现任何接口,Spring AOP 则借助于 cglib 生成目标的动态代理类,实现 AOP 操作。

原创粉丝点击