JAVA 代理模式

代理模式的应用场景主要有四种。

  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");
		UserLogin userLogin = new UserLoginProxy();

		userLoginService.consumer(userLogin, user);
	}
}

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LoggingDynamicProxy implements InvocationHandler {
	//也可以采用注入的方式得到被代理的对象
	private Object proxied;
	public LoggingDynamicProxy(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 LoggingDynamicProxy(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 操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值