前言
在面向对象系统中,有些对象由于某些原因,比如对象创建开销很大,或者某些操作需要安全控制,直接访问会给使用者或者系统结构带来很多麻烦。为了解决这个场景的途径之一,便是增加一个中间层作为代理层。这边是代理模式。
代理模式 ,作为23种经典设计模式之一在很多地方有用到,其目的是为其他对象提供一种代理以控制对这个对象的访问。通过代理可以为原对象附加多种用途。
代理模式又分为动态代理和静态代理。而静态代理需要自己声明代理类对象。动态代理不需要声明代理类对象,通过程序自动生成。
代理模式更多的是通过增加一个中间层作为代理层对访问代理对象做额外的控制,真正操作还是代理对象进行操作。对于装饰器模式而已,而是在原对象增加新的功能
基于对代理模式应用场景的理解,我想到一个场景,找董事长签字的时候,一般是找秘书。先由秘书检查完文件,秘书再将文件拿给董事长签字。在这个场景下,秘书便是代理层,代理对象是董事长,对董事长签文件这个过程进行了控制(即检查文件)。
静态代理
秘书和董事长签字这个场景,用静态代理怎么写呢?其结构如下:
需要有个基础类(People),用来声明代理层和代理对象共同的方法,代理对象继承基础类,并真正实现具体方法。代理层同样继承基础类,来实现相应的方法,但与代理对象不同的是,代理层可以在实现方法时加额外的能力,如校验功能。然后通过调用代理对象来实现真正的操作。
在写代码的时候,根据基础类(People) --> 代理对象(Boss) --> 代理层(Secretary)顺序编写。
基础类
首先声明一个基础类--People,里面只有一个方法声明,签字sign。代理对象和代理层基层基础接口,从而对外面展示是一样的动作。
public interface People {
void sign(String file);
}
代理对象
然后写代理对象,也就是真正签字的人 - 董事长(Boss)。董事长继承基础类People
public class Boss implements People {
@Override
public void sign(String file) {
System.out.println("我是董事长,我来签字,文件为:"+ file);
}
}
代理层
然后写代理层 -- 秘书(Secretary)。秘书也继承基础类People,从而和董事长有一样的方法,但秘书要多一步检查文件(checkContract)的操作。
public class Secretary implements People {
private People boss;
public Secretary(People p){
this.boss = p;
}
@Override
public void sign(String file) {
System.out.println("我是秘书,签字的请找我,请您在外稍等一下");
if (checkContract(file)){
boss.sign(file);
}else {
System.out.println("我是秘书,文件不合格,请回");
}
}
public boolean checkContract(String file){
System.out.println("我是秘书,我检查文件");
if ("合同".equals(file)){
return true;
}
return false;
}
}
调用方法
public static void main(String[] args) {
Secretary secret = new Secretary(new Boss());
secret.sign("合同");
secret.sign("协议");
}
调用结果如下。可见虽然调用的是代理层 -- 秘书(Secretary)的签字方法,但真实签字的还是代理对象 - 董事长(Boss)。但代理层提供一种代理以控制对这个对象的访问。当然通过代理还可以为原对象附加其他功能。
动态代理
秘书除了可以代理董事长,也可以代理相关部门进行改签盖章,若要代理部门的时候,显然现在的写法是行不通的。
上面的例子中代理类代理层 -- 秘书(Secretary)实现了抽象角色的接口(People),并在属性中声明了被代理的对象(Boss,也是Peple的实现类),导致代理类受到限制无法通用。为了让代理类更具有通用性,只需要使用反射动态的获取抽象接口的类型,进而获取相关方法实现动态代理。
若想实现秘书除了可以代理董事长,也可以代理相关部门进行改签盖章这个功能,则需要用到动态代理,使用到了反射。
Jdk代理
第一个方案,Jdk代理,即使用Java原生反射机制进行动态代理。
首先除了上面写的代理基础类People和代理对象Boss。现在再加一种要代理的对象-- 部门
新的代理基础类--Organization(部门)
public interface Organization {
void sign(String file);
}
新的代理对象--LegalDepartment(法务部)
public class LegalDepartment implements Organization{
@Override
public void sign(String file) {
System.out.println("我是法务部,我来签字,文件为:"+ file);
}
}
这个时候代理层怎么写呢?答案:实现 InvocationHandler 接口,表明该类是一个动态代理执行类。
其重点有两个
1. InvocationHandler 接口内有一实现方法如下: public Object invoke(Object proxy, Method method, Object[] args) 。使用时需要重写这个方法
2.获取代理类,需要使用 Proxy.newProxyInstance(Clas loader, Class[] interfaces, InvocationHandler h) 这个方法去获取Proxy对象(Proxy 类类型的实例)。
代码如下:
public class Secretary implements InvocationHandler {
private Object proxyTarget;
public Object getProxyInstance(Object target) {
this.proxyTarget = target;
// 第一个参数,是类的加载器
// 第二个参数是代理对象的接口类型,
// 第三个参数就是代理对象类本身
return Proxy.newProxyInstance(proxyTarget.getClass().getClassLoader(), proxyTarget.getClass().getInterfaces(), this);
}
/**
*
* @param proxy 调用该方法的代理对象
*
* @param method 在代理对象上调用的接口方法
*
* @param args 在代理实例上的方法调用中传递的参数
*
* @return 代理对象某个方法的执行结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object methodObject = null;
System.out.println("我是秘书,签字的请找我,请您在外稍等一下");
if (checkContract((String) args[0])){
methodObject = method.invoke(proxyTarget, args);
}else {
System.out.println("我是秘书,文件不合格,请回");
}
return methodObject;
}
public boolean checkContract(String file){
System.out.println("我是秘书,我检查文件");
if ("合同".equals(file)){
return true;
}
return false;
}
}
调用方法
public static void main(String[] args) {
Secretary secret = new Secretary();
// 继承父类方法
Pepple boss = (Pepple) secret.getProxyInstance(new Boss());
boss.sign("合同");
Organization legalDepartment = (Organization) secret.getProxyInstance(new LegalDepartment());
legalDepartment.sign("协议");
}
调用结果,可见现在代理层可以实现了通用。
CgLib代理
第二个方案,CGLib代理,即要引入cglib.jar进行使用,从而实现动态代理。与Jdk代理不同的是,Jdk代理对象需要继承基础类,而CGLib代理可以直接使用子类,不需要基础类,但在使用的时候直接使用子类,在实现。此外,Jdk代理只用代理Public方法,CGLib可以代理protected方法。
代理层的修改
代理层继承MethodInterceptor,在getProxyInstance方法中实现代理对象的方法改为了Enhancer.create();重新方法改为了intercept();
public class Secretary implements MethodInterceptor {
private Object proxyTarget;
public Object getProxyInstance(Object target) {
this.proxyTarget = target;
return Enhancer.create(target.getClass(), target.getClass().getInterfaces(), this);
}
/**
*
* @param o 调用该方法的代理对象
*
* @param method 在代理对象上调用的接口方法
*
* @param objects 在代理实例上的方法调用中传递的参数
*
* @methodProxy 它的作用是用于执行目标(委托类)的方法,,
* 官方的解释是速度快且在intercept内调用委托类方法时不用保存委托对象引用。
*
* @return 代理对象某个方法的执行结果
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object methodObject = null;
System.out.println("我是秘书,签字的请找我,请您在外稍等一下");
if (checkContract((String) objects[0])){
methodObject = method.invoke(proxyTarget, objects);
}else {
System.out.println("我是秘书,文件不合格,请回");
}
return methodObject;
}
public boolean checkContract(String file){
System.out.println("我是秘书,我检查文件");
if ("合同".equals(file)){
return true;
}
return false;
}
}
调用方法
public static void main(String[] args) {
Secretary secret = new Secretary();
// 可以直接继承子类
Boss boss = (Boss)secret.getProxyInstance(new Boss());
boss.sign("合同");
LegalDepartment legalDepartment = (LegalDepartment) secret.getProxyInstance(new LegalDepartment());
legalDepartment.sign("合同");
}
调用结果
-
CGLib (基于子类的动态代理)使用的是方法拦截器 MethodInterceptor ,需要导入 cglib.jar 和 asm.jar 包
-
基于子类的动态代理,返回的是子类对象
-
方法拦截器对 protected 修饰的方法可以进行调用
总结
通过代理模式 实现为其他对象提供一种代理以控制对这个对象的访问,从而为原对象附加多种用途。
代理模式又分为动态代理和静态代理。而静态代理需要我们自己写代理类对象。动态代理不需要写代理类对象,通过程序自动生成。
代理模式更多的是通过增加一个中间层作为代理层对访问代理对象做一下控制,真正操作还是代理对象进行操作。对于装饰器模式而已,而是在原对象增加新的功能
静态代理 | 某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。 代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。 |
动态代理 | 代理角色不固定,更加灵活,代理对象由Java反射机制动态产生,无需程序员手动编写它的源代码。 |
其中动态代理又分为Jdk代理和CgLib代理,其区别在于
原理 | |
Jdk代理 |
|
CgLib代理 |
|
参考资料
装饰器:装饰器模式 | 菜鸟教程
适配器:适配器模式 | 菜鸟教程