目录
一、代理模式概述
含义:为其他对象提供一种代理,以控制对这个对象的访问。
特点:
- 代理对象可以在客户端和目标对象之前起到中介的作用;
- 当实现类(即客户端)需要扩展其他功能时,根据开闭原则是不能修改实现类的,因此,通过代理模式添加额外的功能可以拓展实现类的功能;
- 代理模式可分为:静态代理和动态代理,前者在程序运行前就已经存在的编译好的代理类,后者运行时由JVM来实现。
- 动态代理又分为:JDK动态代理 和 CGLIB动态代理。二者的区别为JDK动态代理产生的代理类和目标类实现了相同的接口,CGLIB动态代理产生的代理类是目标对象的子类。
二、场景举例
场景:
常见的买车为例,消费者不会直接从汽车厂商那里购买汽车,而是通过4S店,4S店作为一种代理角色,可以控制消费者买车的行为。这里的例子一种静态代理模式,后面再详细分析动态代理。
分析:
- 汽车厂商:作为抽象角色,包含消费者的购买汽车、个性化定制等功能;
- 消费者:作为真实角色,是具体功能的实现;
- 4S店:作为代理角色,控制着消费者对汽车厂商的访问限制,必须通过4S店才能买车或定制化服务。
Demo实现:
/** 抽象角色(定义功能接口) */
public interface CarProdures {
// 购买汽车
void buyCar();
// 个性化定制汽车
void pushDIYService();
}
/**代理角色(代理抽象角色的功能,也可以额外扩展添加其他的功能) */
public class Proxy4S implements CarProdures {
private CarProdures cp;
//通过构造器方式注入
public Proxy4S(CarProdures cp) {
this.cp = cp;
}
@Override
public void buyCar() {
cp.buyCar();
}
@Override
public void pushDIYService() {
cp.pushDIYService();
}
//代理角色可以附加功能,如汽车贴膜,洗车等服务
public void autoFilm(){
System.out.println("在4S店,你也可以进行汽车贴膜!");
}
//代理真实角色功能的方法
public void proxyService(){
buyCar();
pushDIYService();
autoFilm();
}
}
/**真实角色(消费者) */
public class Consumers implements CarProdures{
@Override
public void buyCar() {
System.out.println("欢迎通过XXX 4S店订购大奔一辆!");
}
@Override
public void pushDIYService() {
System.out.println("土豪,欢迎通过XXX 4S店个性化定制法拉利!");
}
}
测试:
Consumers conmuser = new Consumers();
// new Proxy4S(conmuser).proxyService();
new Proxy4S(conmuser).buyCar(); // 买车
new Proxy4S(conmuser).pushDIYService(); // 个性化定制
// 扩展功能时比如汽车贴膜,添加额外的汽车贴膜方法即可,符合开闭原则
new Proxy4S(conmuser).autoFilm();
小结:
静态代理只能为一个类服务,如果需要代理的类很多,那么就需要编写更多的代理类,比较繁琐,因此可以考虑动态代理模式。
三、动态代理
动态代理在Java中有着广泛的应用,比如Spring AOP,Hibernate数据查询、RPC,Java注解对象获取等,动态代理的代理关系是在运行时期确定的,可分为JDK动态代理和CGLIB动态代理。下面重点说明二者的原理和demo实现,这些是工作或面试经常被提及的,需要特别注意。
1、JDK动态代理Demo实现
JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy 和 InvocationHandler。其中,InvocationHandler是一个接口,可以在实现该接口并重写invoke()方法中横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。特别注意,JDK动态代理是面向接口的代理模式,要保证代理类和目标类实现相同的接口。
还是以上面买车的例子为例,抽象角色CarProdures、真实角色Consumers代码和静态代理模式一样,JDK动态代理Demo实现步骤如下:
-
目标类 DynamicProxy4S 要实现 InvocationHandler 接口并重写 invoke() 方法,其中在 invoke() 方法中可以横切逻辑;
-
为代理类 Proxy 指定 ClassLoader 对象和一组接口来创建动态代理;
-
通过反射机制获取动态代理类的构造函数,通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数等。
public class DynamicProxy4S implements InvocationHandler {
CarProdures cp;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// invoke()方法前切入代码
System.out.println("Before invoke: 向代理服务器发送了请求....");
// 第一次调用时,生成真实角色
if (null == cp)
cp = new Consumers();
// 调用invoke()方法
method.invoke(cp,null);
// invoke()方法后切入代码
System.out.println("After invoke: 代理服务器响应了请求....");
return null;
}
public CarProdures createDynamicProxy(){
// 动态代理CarProdures
CarProdures carProdures = (CarProdures) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{CarProdures.class}, new DynamicProxy4S());
return carProdures;
}
}
测试:
@Test
void testProxy() {
DynamicProxy4S dynamicProxy4S = new DynamicProxy4S();
CarProdures carProdures = dynamicProxy4S.createDynamicProxy();
carProdures.pushDIYService();
// carProdures.buyCar();
}
输出结果:
2、CGLIB动态代理Demo实现
原理:
当代理类和目标类无法实现相同的接口时(换句话说,目标类没有通过接口去实现相应的业务逻辑),则无法使用JDK动态代理了,此时可以考虑使用CGLIB动态代理。
CGLIB(即Code Generation Library)是一个基于ASM的字节码生成库,它允许在运行时对字节码进行修改和动态生成。CGLIB通过继承方式实现代理,会在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。特别注意,目标类的子类不能是final的,否则会失败。
基本流程:
- 根据指定的回调类生成 Class 字节码;
- 通过 defineClass() 将字节码定义为类;
- 使用反射机制生成该类的实例。
在Java中,动态代理类的生成主要涉及对 ClassLoader 的使用。以 CGLIB 为例,使用 CGLIB 生成动态代理,首先需要生成 Enhancer 类实例,并指定用于处理代理业务的回调类。在 Enhancer.create() 方法中,会使用DefaultGeneratorStrategy.Generate() 方法生成动态代理类的字节码,并保存在 byte数组中,接着使用 ReflectUtils.defineClass() 方法,通过反射,调用ClassLoader.defineClass()方法,将字节码装载到 ClassLoader 中,完成类的加载。最后使用 ReflectUtils.newInstance() 方法,通过反射,生成动态类的实例,并返回该实例。
代码实现:
public class CglibProxy4S implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// intercept()方法前切入代码
System.out.println("Before invoke: 向代理服务器发送了请求....");
methodProxy.invokeSuper(o,objects);
// intercept()方法后切入代码
System.out.println("After invoke: 向代理服务器发送了请求....");
return null;
}
public Object getInstance(Object obj){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
测试:
@Test
void testProxy() {
CglibProxy4S cglibProxy4S = new CglibProxy4S ();
Consumers consumers =new Consumers();
CarProdures carProdures = (CarProdures)cglibProxy4S.getInstance(consumers);
carProdures.buyCar();
// carProdures.buyCar();
}
输出结果:
3、小结
使用动态代理的优势:
- 减少编码工作量:假如需要实现多种代理处理的逻辑,只要写多个代理处理器就可以了,无需每种方式都写一个代理类;
- 扩展性强,易于维护:一般只要改代理处理器类就行。