前言
很多优秀的框架中,巧妙的用到了各种设计模式,在解读源码的过程中,肯定非常需要再熟悉下设计模式。今天咱们来说下代理模式。
正文
代理模式
为另一个对象提供一个替身或占位符以控制这个对象的访问。
代理就是找个人帮你做事情,好比你要买房子,代理就相当于房地产中介,帮你去寻找合适的房源,我们需要把自己对房源的需求告诉房地产中介,中介在找房源的过程中还会和我们不断的沟通。
为什么要有代理模式?
为了解决直接操作目标对象给系统带来的不必要的复杂性。
代理模式的种类
根据代理创建时期的不同分为静态代理和动态代理,静态代理由开发人员或特定工具自动生成源代码,再对其编译,在程序运行之前 ,代理类已经生成了;动态代理在程序运行时,利用java的反射机制动态创建而成。
静态代理
UML 类图
demo
//接口:声明目标对象需要让代理对象帮忙做的事情
public interface Subject {
public void buyHouse();
}
/*********************************************************/
//创建目标对象(真实对象)
public class RealSubject implements Subject {
@Override
public void buyHouse() {
System.out.println("我想买一个房子");
}
}
/*********************************************************/
//创建代理对象类,并通过代理类创建真实对象实例并访问其方法
public class Proxy implements Subject
{
@Override
public void buyHouse() {
//真实对象买房之前的操作
this.preBuyHouse();
// 引用并创建真实对象实例
RealSubject realSubject = new RealSubject();
//调用真实对象的购买方法
realSubject.buyHouse();
//真实对象买房之后的操作
this.postBuyHouse();
}
public void preBuyHouse(){
System.out.println("买房之前需要交中介费");
}
public void postBuyHouse(){
System.out.println("买房之后的一些操作");
}
}
/*********************************************************/
//客户端测试
public class Proxytest {
public static void main(String[] args){
Subject proxy = new Proxy();
proxy.buyHouse();
}
}
输出结果:
静态代理在使用时必须事先知道真实对象的存在,并将其作为代理对象的内部属性;一个真实对象对应一个代理对象,如果真实对象很多,那么代理对象的大量使用将是个灾难;此外,如果事先不知道真实对象是谁, 该怎么使用代理呢? 下面的动态代理就解决了这个问题。
动态代理
UML 类图
demo
package dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 通过实现InvocationHandler 接口创建自己的调用处理器
* @author zhang
*
*/
public class DynamicSubject implements InvocationHandler{
//真实对象的引用
private Object sub;
public DynamicSubject(Object sub){
this.sub = sub;
}
@Override
//该方法集中处理动态代理类上的所有方法调用,第一个参数既是代理类实例,第二个参数是被调用的方法对象,第三个参数是调用参数,调用处理器根据这三个参数进行预处理或分派到委托类示例上反射执行
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法调用之前
System.out.println("方法调用之前:买房之前需要交中介费" + method);
//执行方法
method.invoke(sub, args);
//方法调用之后
System.out.println("方法调用之后:买房之后的一些操作" + method);
return null;
}
}
/*********************************************************/
//接口:声明目标对象需要让代理对象帮忙做的事情
public interface Subject {
public void buyHouse();
}
/*********************************************************/
//创建目标对象(真实对象)
public class RealSubject implements Subject {
@Override
public void buyHouse() {
System.out.println("我想买一个房子");
}
}
/*********************************************************/
//客户端测试
public class Proxytest {
public static void main(String[] args) throws Throwable{
RealSubject realSubject = new RealSubject();
InvocationHandler handler = new DynamicSubject(realSubject);
//Proxy提供了静态方法为指定类装载器、一组接口以及调用处理器生成动态代理实例
Subject subject = (Subject)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), handler);
/* newProxyInstance 封装的步骤
//通过为Proxy 类指定Classloader对象和一组Interface来创建动态代理类
Class c = Proxy.getProxyClass(clazz.getClassLoader(),clazz.getInterfaces());
//通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理类接口类型
Constructor ct = c.getConstructor(new Class[]{InvocationHandler.class});
//通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入
Subject subject = (Subject)ct.newInstance(new Object[]{handler});
*/
subject.buyHouse();
}
}
输出结果:
可以看出,JDK 动态代理使用反射实现的,反射中Proxy可以根据传入参数的不同(handler)在运行时产生不同的代理类,该代理类必须是实现了被代理类的接口,并且有个参数为InvocationHandler的构造函数,所以jdk动态代理的缺点是:只能代理接口定义的方法。
为了解决这个问题,出现了动态代理的第二种实现方式—CGlib
Cglib 是针对类来实现代理的,原理是对指定的业务类生成一个子类,并覆盖其中业务方法实现代理,因为采用的是继承,所以不能对final修饰的类进行代理。
demo
package cglibproxy;
//定义业务类,不是必须要实现接口
//创建目标对象(真实对象)
public class RealSubject {
public void buyHouse() {
System.out.println("我想买一个房子");
}
}
/*********************************************************/
package cglibproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*
* 通过实现MethodInterceptor方法代理接口,创建代理类
*/
public class CglibProxy implements MethodInterceptor{
// 创建加强器,用来创建动态代理类
private Enhancer enhancer = new Enhancer();
//传进去的clazz 为业务类对象,供代理方法中进行真正的业务方法调用
public Object getProxy(Class clazz){
//为加强器指定要代理的业务类(为此方法生成的代理类指定父类)
enhancer.setSuperclass(clazz);
//设置回调:对于代理类上所有方法的调用,都会调用callback,而callback则需要实现intercept()方法进行拦截
enhancer.setCallback(this);
//创建动态代理类对象并返回
return enhancer.create();
}
//实现回调方法
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//方法调用之前
System.out.println("方法调用之前:买房之前需要交中介费" + method);
//执行方法,调用业务类(父类)中的方法
Object res = proxy.invokeSuper(arg0, args);
//方法调用之后
System.out.println("方法调用之后:买房之后的一些操作" + method);
return res;
}
}
/*********************************************************/
package cglibproxy;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
//客户端测试
public class Proxytest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
RealSubject rs = (RealSubject) proxy.getProxy(RealSubject.class);
rs.buyHouse();
}
}
输出结果:
总结
- JDK动态代理需要代理类和InvocationHandler接口
- JDK 动态代理的目标类必须实现某个接口,如果目标类没有实现某个接口就不能生成代理对象。
- CGlib 需要指定父类和回调方法
- CGlib 原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为private final类型。
适用场景
- 远程代理
为一个对象在不同的地址空间提供局部代表。 - 虚拟代理
当需要创建开销非常大的对象时,使用一个小对象代理一个大对象。 - 保护代理
当需要控制对原始对象的访问时,给不同的用户提供不同的访问权限。 - 智能引用代理
当需要在访问对象时附加额外操作时,在不影响对象类的情况下,在访问对象时进行更多的操作。
总结
将代理模式复习了一遍,有些地方有疑问,对于CGlib动态代理方式接触的不多,在项目中慢慢体会吧。