说明
代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
实现方式
- 静态代理:由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了
- JDK动态代理:是在程序运行时通过反射机制动态创建的
- CGLIB动态代理:CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理
模式特征
角色 | 说明 | 举栗 |
---|---|---|
Component | 统一接口,也是代理类和被代理类的基本类型 | BuyComputer |
ConcreteComponent | 具体实现类,也是被代理类 | BuyComputerImpl |
Proxy | 代理类 | BuyComputerProxy、BuyComputerHandler、BuyComputerInterceptor |
代码实现
公用类
- 统一接口:买电脑
public interface BuyComputer {
void buyComputer();
}
- 具体实现类
public class BuyComputerImpl implements BuyComputer {
@Override
public void buyComputer() {
System.out.println("我买了windows电脑");
}
}
静态代理
- 静态代理类
public class BuyComputerProxy implements BuyComputer {
private BuyComputer buyComputer;
public BuyComputerProxy() {
//由代理类主动new出的被代理对象
this.buyComputer = new BuyComputerImpl();
}
@Override
public void buyComputer() {
System.out.println("推荐一款华为电脑");
buyComputer.buyComputer();
System.out.println("华为提供售后服务");
}
}
JDK动态代理
- JDK动态代理处理类
public class BuyComputerHandler implements InvocationHandler {
private Object object;
public Object newProxyInstance(Object object) {
this.object = object;
//注意Proxy.newProxyInstance()方法接受三个参数:
//ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
//Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
//InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
/**
* @param proxy 指代我们所代理的那个真实对象
* @param method 指代的是我们所要调用真实对象的某个方法的Method对象
* @param args 指代的是调用真实对象某个方法时接受的参数
* @return 结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("推荐一款华为电脑");
Object result = method.invoke(object, args);
System.out.println("华为提供售后服务");
return result;
}
}
CGLIB动态代理
- CGLIB动态代理拦截器
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class BuyComputerInterceptor implements MethodInterceptor {
//代理的目标对象
private Object object;
public Object getInstance(Object object){
this.object = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("推荐一款华为电脑");
Object result = methodProxy.invoke(object, args);
System.out.println("华为提供售后服务");
return result;
}
}
测试
- 客户端测试
public class Client {
public static void main(String[] args) {
//静态代理
BuyComputerProxy proxy = new BuyComputerProxy();
proxy.buyComputer();
System.out.println("--------分割线---------");
//JDK动态代理
BuyComputerHandler handler = new BuyComputerHandler();
//这里是基于接口的代理
BuyComputer buyComputer = (BuyComputer) handler.newProxyInstance(new BuyComputerImpl());
buyComputer.buyComputer();
System.out.println("--------分割线---------");
//CGLIB动态代理
BuyComputerInterceptor interceptor = new BuyComputerInterceptor();
//这里代理的是BuyComputerImpl这个实现类,而非接口BuyComputer
BuyComputerImpl buyComputer1 = (BuyComputerImpl) interceptor.getInstance(new BuyComputerImpl());
buyComputer1.buyComputer();
}
}
- 结果
推荐一款华为电脑
我买了windows电脑
华为提供售后服务
--------分割线---------
推荐一款华为电脑
我买了windows电脑
华为提供售后服务
--------分割线---------
推荐一款华为电脑
我买了windows电脑
华为提供售后服务
优缺点
静态代理
- 优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
JDK动态代理
- 优点:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度,实现无侵入式的代码扩展
- 缺点:使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
CGLIB动态代理
- 优点:CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高
- 缺点:CGLIB创建代理对象时所花费的时间却比JDK多得多
对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理,JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
模式对比
很多刚刚接触设计模式的同学,很容易将装饰器模式和静态代理模式混淆,下面简单说说他们之间的区别,需要用心体会。
- 对装饰器模式来说,装饰者和被装饰者都实现一个接口。对代理模式来说,代理类和真实处理的类也都实现同一个接口。此外,不论我们使用哪一个模式,都可以很容易地在真实对象的方法前面或者后面加上自定义的方法.
- 装饰器模式关注于在一个对象上动态地添加方法,而代理模式关注于控制对对象的访问。换句话说,用代理模式,代理类可以对它的客户隐藏一个对象的具体信息。因此当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例;当使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰器的构造器,简单的说,就是调用者主动将一个对象传入装饰器的构造器,来主动装饰它,调用者知道原始对象的存在。