代理模式
1. 代理模式介绍
代理模式也称为委托模式,它是一项基本技巧。许多其他的模式(状态模式、策略模式、访问者模式)本质实在更特殊的场合采用了代理模式。代理模式可以提供非常好的访问控制
1.1 代理模式结构
- [x]
Subject(抽象主题角色)
:抽象主题类可以是抽象类也可以是接口,定义业务; - [x]
RealSubject(具体主题角色)
:被委托角色、被代理角色、业务逻辑的具体执行; - [x]
Proxy(代理主题角色)
:委托类、代理类、负责对真是角色的应用,把所有抽象主题类定义的业务执行限制委托给真实主题角色来实现,并在真实主题角色执行业务前后做预处理和善后处理工作;
Subject
抽象主题类
public interface Subject{
public void request();
}
RealSubject
具体主题角色,正常的业务实现类
public class RealSubject implement Subject{
//实现方法
public void request(){
}
}
Proxy
代理类,代理模式的核心
public class Proxy implements Subject{
//要代理哪个实现类
private Subject subject = null;
//默认被代理者
pubilc Proxy(){
this.subject = new Proxy();
}
//使用构造方法传递代理者
pulbic Proxy(Object... object){
}
//实现
public void request(){
this.before();
this.subject.request();
this.after();
}
//预处理
private void before(){
//do something;
}
//善后处理
private void after(){
//do something;
}
一个代理类可以代理多个被委托者(被代理者),这是由具体场景来决定的。最简单的就是一个主题类一个代理类。通常情况下一个主题类一个代理类也足够了,具体代理哪个实现类由搞成模块来决定,也就是在构造方法中指派传递;我们上述代理类
Proxy
中可以添加如下构造函数:
public Proxy(Subject subject){
this.subject = subject;
}
1.2 静态代理&动态代理
1.2.1 静态代理
由程序员创建或者工具生成代理类的源码,在编译代理类。亦就是在程序运行前已经存在了代理累的字节码文件了,代理类和委托类的关系在运行前就确定了,如下:
- 抽象主题角色
/**
* 描述:代理接口,处理给定名字的任务
*
* @author biguodong
* Create time 2018-12-16 下午1:08
**/
public interface Subject {
/**
* 执行给定任务的名字
* @param taskName
*/
void dealTask(String taskName);
}
- 具体处理业务的委托类
/**
* 描述:真正执行任务的类,实现了代理接口
* @author biguodong
* Create time 2018-12-16 下午4:34
**/
public class RealSubject implements Subject{
/**
* 执行给定任务的名字
* @param taskName
*/
@Override
public void dealTask(String taskName) {
System.out.println("正在执行任务:" + taskName);
try{
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
- 代理类
/**
* 描述:静态代理类的演示代码
* @author biguodong
* Create time 2018-12-16 下午4:38
**/
public class ProxySubject implements Subject{
/**
* 代理类持有一个委托类的对象引用
*/
private Subject delegate;
public ProxySubject(Subject delegate) {
this.delegate = delegate;
}
/**
* 将请求分派给委托类执行,记录任务执行时间
* @param taskName
*/
@Override
public void dealTask(String taskName) {
long beginTime = System.currentTimeMillis();
delegate.dealTask(taskName);
long finishTime = System.currentTimeMillis();
System.out.println("执行任务耗时:" + (finishTime - beginTime) + "毫秒");
}
}
- 代理类工厂
/**
* 描述:静态代理工厂
* @author biguodong
* Create time 2018-12-16 下午4:42
**/
public class SubjectStaticFactory {
/**
* 客户端调用此方法获取代理对象
* 客户端并不知道获取到的是代理类对象还是为拖类对象
* @return
*/
public static Subject getInstance(){
return new ProxySubject(new RealSubject());
}
}
- 调用客户端
/**
* 描述:调用客户端
* @author biguodong
* Create time 2018-12-16 下午4:45
**/
public class Client {
public static void main(String[] args) {
Subject subject = SubjectStaticFactory.getInstance();
subject.dealTask("some task");
}
}
- 执行结果
正在执行任务:some task
执行任务耗时:5008毫秒
静态代理优缺点
优点:
- [x] 业务类只需要关心业务逻辑本身,保证了业务类的重用性,这是代理的共同优点;
缺点:
- [x] 代理对象的一个接口只服务于一种类型的对象,如果代理对象很多,势必要为每一种方法都进行代理,在规模大时就无法胜任了;
- [x] 代理接口方法的变更,不仅所有实现类需要重写实现,所有的代理类业务也需要变更,增加了代码维护的复杂度;
1.2.2 动态代理
动态代理类的源码在JVM
runtime
时期根据反射等机制动态地生成,代理类和委托类的关系也是在程序运行时确定的;
1.java.lang.relect.Proxy
:java生成所有动态代理类的父类,提供一组静态方法来为一组接口动态的生成代理类及其对象。
//获取指定代理对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException
//指定接口列表和类装载器的代理类
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
//是否为动态代理类
public static boolean isProxyClass(Class<?> cl)
//指定类加载器、一组接口以及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
2. java.lang.reflect.InvocationHandler
: 调用处理器接口,自定义了invoke方法,用来集中处理动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
/**
* proxy 代理类实例
* method 被调用的方法对象
* args 调用方法参数
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
3. java.lang.ClassLoader
:类加载器,负责把类的字节码装在到JVM 并为其定义类对象,然后才能被使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的,而不是预存在任何一个.class
文件中
4. 实现动态代理的步骤:
- [x] 实现InvocationHandler接口,创建自己的调用处理器;
- [x] 给Proxy类提供ClassLoader、代理接口数组,创建动态代理类;
- [x] 利用反射机制使用调用处理器对象得到代理类的构造函数;
- [x] 利用代理类的构造函数创建动态代理类对象
Proxy类的静态方法
newInstance
对上述步骤作了封装,简化了动态代理对象的获取过程
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
/*
* 缓存中查找或生成代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 使用特定的调用处理器invoke构造方法
*/
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
//生成代理类实例
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
5. 创建自己的调用处理器实现动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 描述:自定义调用处理器
* @author biguodong
* Create time 2018-12-16 下午6:05
**/
public class SubjectInvocationHandler implements InvocationHandler {
private Object delegate;
public SubjectInvocationHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long beginTime = System.currentTimeMillis();
//利用反射将请求分派给为拖类对象
method.invoke(delegate, args);
long finishTime = System.currentTimeMillis();
System.out.println("动态代理-执行任务耗时:" + (finishTime - beginTime) + "毫秒");
return null;
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 描述:动态代理对象的工厂
*
* @author biguodong
* Create time 2018-12-16 下午6:08
**/
public class DynProxyFactory {
public static Subject getInstance(){
Subject delegate = new RealSubject();
InvocationHandler handler = new SubjectInvocationHandler(delegate);
Subject proxy;
proxy = (Subject) Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
delegate.getClass().getInterfaces(),
handler);
return proxy;
}
}
/**
* 描述:动态代理调用客户端
* @author biguodong
* Create time 2018-12-16 下午6:10
**/
public class Client {
public static void main(String[] args) {
Subject proxy = DynProxyFactory.getInstance();
proxy.dealTask("动态代理任务");
}
}
结果:
正在执行任务:动态代理任务
动态代理-执行任务耗时:5008毫秒
6.动态代理特点:
6.1动态生成的代理类本身特点:
- [x] 包: 如果所代理接口都是
public
的,那么它将被定义在顶层包(包路径为空),如果所代理接口中有非public
的接口(因为接口不能被定义为protect
或private
,只能是public
或package
访问级别),那么将被定义在接口所在包,这样最大程度的确保动态代理类不会因为包管理问题导致不能成功被访问; - [x] 类修饰符: 代理类有
final
和public
,可以被所有类访问,但是不能被继承; - [x] 类名: 格式为
"$ProxyN"
,如果对同一组接口(包括接口的排列顺序相通)试图重复创建动态代理类,会返回之前已经创建好的代理类的类对象; - [x] 类继承关系:
Proxy
是所有代理类的父类,这个规则适用于所有由Proxy创建的动态代理类,而且代理类还实现了其所代理的一组接口,这也是为什么它能够被安全地类型转化为其所代理的某一接口的原因;
6.2动态代理类实例的特点:
[x] 因为每个实例都会关联一个调用处理器对象,可以通过
Proxy
的静态方法getInvocationHandler
来获取。在代理类实例上调用其代理的接口中声明的方法时,这些方法最终都会由调用处理器的invoke
方法来执行。此外值得注意的是,代理类的根类java.lang.Object
中有三个方法也同样会被分派到调用处理器的invoke
方法中执行,它们分别是hashCode
、equals
和toString
可能原因有:[x] 1. 这些方法是
public
且非final
的,能够被代理覆盖;[x] 2. 这些方法呈现出一个类的某些特殊属性,具有一定区分度,为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到为拖类执行。
6.3被代理的接口特点:
- [x] 不能有重复的接口,否则会编译错误;
- [x] 这些接口对类装载器必须可见,否则无法链接它们,导致类定义失败;
- [x] 被代理的所有非
public
的接口必须在同一个包中,否则代理类生成会失败; - [x] 接口数不能超过
65536
jvm 规定;
6.4异常处理方面的特点:
- [x] 异常抛出,调用处理器接口上理论上可以抛出任何类型的异常。但是必须要知道的是,当子类覆盖父类或实现父接口的方法时,抛出的异常必须在源方法支持的异常列表内,除非父接口中的方法支持抛出
Throwable
异常。如果在invoke
方法中的确产生了接口方法声明中不支持的异常,会抛出
UndeclaredThrowableExeption
异常,是RuntimeException
;
7. 动态代理的有点和缺点
优点:
- [x] 接口中声明的方法都会被转移到调用处理器(
InvocationHandler.invoke
)方法执行,在方法较多时,可以灵活处理配置;不需要像静态代理那样对每一个方法进行中转;
缺点:
- [x] 无法摆脱仅支持
interface
桎梏;
2. 代理模式总结
优点:
- [x] 职责清晰,真实的角色就是实现实际的业务逻辑,无需关心其他非本职责的事务,通过后期的代理完成一件事务,编程更加简洁清晰;
- [x] 高扩展性,具体主题角色是随时都会发生变化的,只要实现了接口,崩管它如何变化,都逃脱不了接口的控制。代理类可以完全再不做任何修改的情况下使用;
- [x] 智能化,
Struts
是把表单元素映射到对象上;