1. 什么是代理模式
简单的来说,代理模式可以用这样一句话总结:
为其他对象(暂且称之为A)提供一种代理,通过代理对象,就可以控制对A的访问
那么,怎么理解这句话呢?代理的作用就好比是我们日常生活中的中介。举个栗子,假设现在有一个房东,他有一间房子想要出租,那么对于这个房东来说,他的“核心业务”就是出租房子,而其它的诸如看房、签合同等业务(暂且称之为关注点吧),他其实并不想管。在房屋中介出现之前,房客和房东之间是直接关联的:
而当房屋中介出现之后,房东想要出租房子,只需要将其交由中介“代理”即可,这样他就可以只关注与出租房子这一“核心业务”,其它的关注点(看房、合同等)都归中介负责。现在,想要租房的房客,就可以通过中介,“访问”到房东的房子了:
至此,我们再来理解一下文章一开始对代理模式的简单定义:为其他对象(暂且称之为A)提供一种代理,通过代理对象,就可以控制对A的访问。
在上述的栗子中,房东就相当于被代理的对象(对象A),房屋中介就相当于代理,他控制着被代理对象(房东)的引用。原本房客租房是直接找的房东(没有代理的时候,直接引用原实现类)租房,而有了中介之后,房客就可以通过中介完成租房了(有了代理,原本直接引用实现类的地方,变为引用代理类)。
2. 代理模式的优点
在了解了什么是代理模式之后,一个新的问题又出现了:我为什么我们要引入代理模式呢?
在程序中引入代理模式之后,会有一系列的优点:
- 使用代理模式,可以将安全、事务和日志等关注点与核心业务逻辑相分离,让业务代码可以专注于核心业务的实现;
- 代理模式进一步降低了各模块之间的耦合;
- 同时,代理模式可以把遍布应用各处的关注点(如日志)分离出来形成可重用的组件,并对其进行更好的管理。
在我们的程序中,系统一般由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心
业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。
如果这些关注点被分散到多个组件中,就会引入额外的复杂性:
- 如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。
- 组件会因为那些与自身核心业务无关的代码而变得混乱。
通过代理模式,就可以很好的解决这些问题。代理模式将这些横切关注点从各个组件中分离出来,放到代理类中统一进行管理。 这样的话,各个组件就可以专注于自己的核心业务,其代码也会变得更简洁;如果需要改变这些关注点的逻辑,也只需要在代理类中稍作修改即可,无需修改各个模块的代码。
3. 代理模式的实现
了解了代理模式的优点之后,我们继续聚焦到其具体代码实现上。继续上面的房东与房客的栗子,其具体代码实现应该是怎么样的呢?
公共代码(租房接口):
/**
* 接口Rent,定义了一个出租房子的方法:rent()
*/
public interface Rent {
void rent();
}
3.1 不使用代理
/**
* 房东类Host,出租房子的具体方法
*/
public class Host implements Rent {
/*
* 模拟没有中介的情况,租房的整体流程为:先看房、然后确认租房
* 假设核心业务为出租房子,此处关注点(看房)被耦合到了业务代码中
*/
@Override
public void rent() {
System.out.println("房客来看房。");
System.out.println("房东出租房子。");
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
Rent host = new Host();
host.rent();
}
}
3.2 静态代理
静态代理通过一个静态代理类实现,它已经具备了前面提到的代理的优点。但美中不足的是,我们需要为每一个服务创建一个代理类,工作量比较大。同时,如果接口发展改变,代理类也得做出相应的修改。
public class Host implements Rent {
// 静态代理中,房东已经可以专注于其核心业务(出租房子)
@Override
public void rent() {
System.out.println("房东出租房子。");
}
}
/**
* 使用静态代理类,控制对原对象的引用(房东)
*/
public class StaticProxy {
private Rent rent; // 原对象(房东)的引用
public StaticProxy(Rent rent) {
this.rent = rent;
}
// 由代理控制对原对象的引用,实现租房
public void rent() {
seeHouse();
rent.rent();
}
// 关注点(看房)交由代理类管理
public void seeHouse() {
System.out.println("中介带房客看房子。");
}
}
public class Test {
public static void main(String[] args) {
Rent host = new Host();
StaticProxy proxy = new StaticProxy(host);
proxy.rent();
}
}
3.3 动态代理
虽然使用静态代理已经可以实现代理的功能,但正如前面所提到的,静态代理还有许多的局限性。使用动态代理,可以较好的解决这些问题。
Java的动态代理,需要借助反射实现。
public class Host implements Rent {
// 与静态代理类似,房东只需要专注于出租房子这一核心业务
@Override
public void rent() {
System.out.println("房东出租房子。");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理实例的调用处理程序,需要实现java.lang.reflect.InvocationHandler接口
*/
public class ProxyInvocationHandler implements InvocationHandler {
private Object target; // 被代理的对象
public void setTarget(Object target) {
this.target = target;
}
/**
* getProxy方法返回一个动态代理实例,每个代理实例都有一个关联的调用处理程序。
* 在这里就是指ProxyInvocationHandler的invoke方法。
* @return 返回动态代理实例,与前面的静态代理类StaticProxy的实例类似,只不过这里是动态生成的。
*/
public Object getProxy() {
/*
* 通过Proxy.newProxyInstance方法可以得到动态代理实例,此方法需要三个参数:
* 第一个参数:ClassLoader 类型,需要传入一个类加载器
* 第二个参数:Class<?>[] 类型,被代理的接口类型,Java的动态代理是基于接口的
* 第三个参数:InvocationHandler 类型,与代理类关联的调用处理程序
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* 与代理实例相关联的调用处理程序,即调用了代理实例需要代理的业务。
* 官方文档解释为:
* Processes a method invocation on a proxy instance and returns the result.
* @param proxy method的代理实例
* @param method 在代理实例上调用被代理的接口方法对应的实例
* @param args 参数
* @return 代理实例上方法调用的返回值
* @throws Throwable 代理实例上的方法调用引发的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse(); // 关注点(看房)交由代理类管理
// 调用代理的业务
Object result = method.invoke(target, args);
return result;
}
public void seeHouse() {
System.out.println("中介带房客看房子。");
}
}
public class Test {
public static void main(String[] args) {
Rent host = new Host();
// 代理实例的调用处理程序的实例
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(host); // 传入被代理的对象
// Java的动态代理是基于接口实现的,因此需要强转为接口(如果强转为Host类型会报错)
Rent proxy = (Rent) pih.getProxy(); // 获得代理实例
proxy.rent(); // 通过代理实例租房
}
}
动态代理的实现显然已经比较完美,但它其实也还存在着一些问题:
- Java的继承机制注定了其无法实现对class的动态代理,即它仅支持interface代理。
3.4 CGLIB代理
由于JDK提供的动态代理仅支持对接口的代理,那对于没有接口的类,改如何实现动态代理呢?这时候我们可以使用CGLib。
CGLib的原理是通过字节码技术为一个类创建子类,并在子类中利用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。 但因为采用的是继承,所以不能对final修饰的类进行代理。
JDK动态代理与CGLib动态代理均是实现Spring AOP(面向切面编程)的基础。
// 此时,Host类已经可以不实现Rent接口了(即不是必须实现接口)
public class Host {
@Override
public void rent() {
System.out.println("房东出租房子。");
}
}
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB代理
*/
public class ProxyMethodInterceptor implements MethodInterceptor {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
seeHouse();
Object result = methodProxy.invokeSuper(object, args);
return result;
}
public void seeHouse() {
System.out.println("中介带房客看房子。");
}
}
public class Test {
public static void main(String[] args) {
Host host = new Host();
ProxyMethodInterceptor pmi = new ProxyMethodInterceptor();
pmi.setTarget(host);
Host proxy = (Host) pmi.getProxy();
proxy.rent();
}
}