代理模式
标签 : Java与设计模式
代理模式
为其他对象提供一种代理
以控制对这个对象的访问(可以详细控制访问某个对象的方法, 在调用这个方法[前/后]做[前/后]置处理, 从而实现将统一流程放到代理类中处理).
静态代理
我们模拟请明星唱歌这个过程,但大家都知道要请明星唱歌(比如周杰伦)是一件比较麻烦的事情, 比如唱歌前要签约, 唱歌之后还有收款, 而平时明星们都是比较忙的, 想签约, 收款这些事情一般都是由他的助手来代理完成的,而明星只负责唱歌就行了, 像签约与收款这种事情就可以算作是明星的增强, 虽然这不是明星的主要目的, 但是这个流程是必须要有的.

/**
* 定义真实对象和代理对象的公共接口
* Created by jifang on 15/12/20.
*/
public interface Star {
void signContract();
void singSong();
void collectMoney();
}
public class RealStar implements Star {
/**
* 由于这些事情都委托给代理来做了, 因此我们只是象征性实现就好了
*/
@Override
public void signContract() {
}
@Override
public void collectMoney() {
}
/**
* 但唱歌是要自己真唱的
*/
@Override
public void singSong() {
System.out.println("周杰伦在唱歌");
}
}
- 代理对象
自己并未实现业务逻辑接口,而是调用真实角色来实现:
public class StaticProxy implements Star {
private Star star;
public StaticProxy(Star star) {
this.star = star;
}
@Override
public void signContract() {
System.out.println("代理签约");
}
/**
* 代理可以帮明星做任何事, 但唯独唱歌这件事必须由Star自己来完成
*/
@Override
public void singSong() {
star.singSong();
}
@Override
public void collectMoney() {
System.out.println("代理收钱");
}
}
public class Client {
@Test
public void client() {
Star star = new StaticProxy(new RealStar());
star.signContract();
star.singSong();
star.collectMoney();
}
}
可以看出,客户实际想要调用的是RealStar
的singSong
方法,现在用StaticProxy
来代理RealStar
类,也可以达到同样的目的,同时还封装了其他方法(像singContract``collectMoney
),可以处理一些其他流程上的问题.
如果要按照上述的方法使用代理模式,那么真实角色
必须是事先已经存在的
,并将其作为代理对象的内部属性;但是实际的Java应用中, 如果有一批真实对象, 而毎个代理对象只对应一个真实对象的话,会导致类的急剧膨胀;此外,如果我们事先并不知道真实角色,那么该如何使用编写代理类呢?这个问题可以通过java的动态代理机制
来解决.
动态代理
所谓动态代理是这样一种class
:它是在运行时生成的class,在生成它时你必须提供一组interface
给它,然后该class就宣称它实现了这些 interface.
JDK对动态代理提供了以下支持:
-
java.lang.reflect.Proxy
动态生成代理类和对象
-
java.lang.reflect.InvocationHandler
- 可以通过invoke方法实现对真实角色的代理访问;
- 每次通过Proxy生成代理类对象时都要指定对象的处理器对象.
首先, Star
接口可以精简一下, 只做他该做的事情:
/**
* Star只负责唱歌就行了
* Created by jifang on 15/12/20.
*/
public interface Star {
void singSong();
}
public class RealStar implements Star {
/**
* 唱歌是要自己真唱的
*/
@Override
public void singSong() {
System.out.println("周杰伦在唱歌");
}
}
当执行动态代理对象里的方法时, 实际上会替换成调用InvocationHandler中的invoke方法.
-
InvocationHandler
: 用于实现代理
/**
* 相当于原先的代理需要执行的方法
* Created by jifang on 15/12/20.
*/
public class ProxyHandler implements InvocationHandler {
private Star star;
public ProxyHandler(Star star) {
this.star = star;
}
/**
* 代理对象的实现的所有接口中的方法, 内容都是调用invoke方法
*
* @param proxy 代理对象(Proxy.newProxyInstance返回的对象)
* @param method 当前被调的方法
* @param args 执行当前方法的参数
* @return 执行方法method的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("签约");
Object result = null;
if (method.getName().equals("singSong")) {
result = method.invoke(star, args);
}
System.out.println("收款");
return result;
}
}
public class Client {
@Test
public void client() {
/**
* newProxyInstance方法会动态生成一个代理类, 他实现了Star接口, 然后创建该类的对象.
*
* 三个参数
* 1. ClassLoader: 生成一个类, 这个类也需要加载到方法区中, 因此需要指定ClassLoader来加载该类
* 2. Class[] interfaces: 要实现的接口
* 3. InvocationHandler: 调用处理器
*/
Star proxyStar = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, new ProxyHandler(new RealStar()));
proxyStar.singSong();
}
}
代理工厂实现动态代理
- 动态代理虽然可以使得我们不用在手写代理对象的代码,但是
InvocationHandler
还是面向特定的抽象接口(如Star)的来写的; 而代理工厂可以让我们的代码写的更加抽象
(而不必面向确定的抽象接口写代码).
-
代理工厂的目标是目标对象和增强方法皆可改变
, 这个模式在现实中的表现就是:
a. 明星对代理并不一定是从一而终的, 明星随时都可能会换代理(助手);
b. 明星不一定只会唱歌, 他还有可能会跳舞.
c. 代理可能不只是为一个明星服务
这样, 我们就实现一个代理工厂-可以随意更换代理所做的辅助性工作; 而目标对象也可以随时增加新的方法.

可以看到, ProxyFactory
与Start
是没有任何关系的, 他们之间能够联系其他完全是靠Client来促成.
代理工厂
/**
* Created by jifang on 15/12/21.
*/
public class ProxyFactory {
private BeforeAdvice beforeAdvice;
private Object targetObject;
private AfterAdvice afterAdvice;
public ProxyFactory() {
}
public ProxyFactory(BeforeAdvice beforeAdvice, Object targetObject, AfterAdvice afterAdvice) {
this.beforeAdvice = beforeAdvice;
this.targetObject = targetObject;
this.afterAdvice = afterAdvice;
}
private InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (beforeAdvice != null) {
beforeAdvice.before();
}
Object result = null;
if (targetObject != null) {
result = method.invoke(targetObject, args);
}
if (afterAdvice != null) {
afterAdvice.after();
}
return result;
}
};
public Object createProxy() {
return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), targetObject.getClass().getInterfaces(), handler);
}
}
/**
* Created by jifang on 15/12/20.
*/
public class Client {
@Test
public void client() {
Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
star.singSong();
}
/**
* BeforeAdvice实现可定制化
*/
private static class StarBeforeAdvice implements BeforeAdvice {
@Override
public void before() {
System.out.println("签合约");
}
}
/**
* AfterAdvice实现可定制化
*/
private static class StarAfterAdvice implements AfterAdvice {
@Override
public void after() {
System.out.println("收款");
}
}
}
现在, 我们的对明星要求比较高了, 他不光要会唱歌, 还要会跳舞.
public interface Star {
void singSong();
void dancing();
}
public class RealStar implements Star {
@Override
public void dancing() {
System.out.println("周杰伦在跳舞...");
}
}
此时, 我们的client
什么都不需要改, 只是添加一个调用就可:
public class Client {
@Test
public void client() {
Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
star.singSong();
star.dancing();
}
}
而且在实际开发中, 这些增强类还可以从配置文件中读取(像Spring).
这种代理在AOP(Aspect Orient Programming: 面向切面编程)
中被成为AOP代理
,AOP代理包含了目标对象的全部方法, 但AOP代理中的方法与目标对象的方法存在差异: 比如可以在执行目标方法之前/后插入一些通用的处理(增强).
代理场景
- 当Client需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,Client要只是一个能提供该功能的对象而已,因此我们就可返回该对象的代理(Proxy).
代理
就是在访问对象时引入一定程度的间接性
, 由于存在这种间接性, 我们就可以做很多工作:
- 远程代理: 为一个对象在不同的地址空间提供
局部代表
, 这样可以隐藏一个对象存在于不同地址空间的事实(Dubbo实现);
- 安全代理: 屏蔽对真实角色的访问, 用代理来控制对真实对象的访问权限;
- 延迟加载: 先加载轻量级代理对象,真正需要时再加载真实对象.
- 参考:
- 你应该知道的 RPC 原理
- 大话设计模式
- 高琪讲设计模式
- 崔希凡讲动态代理
- java代理机制
- 常用设计模式的应用场景