背景
小明是一个客户,它让小红帮忙购买香水,小红就成了一个代理对象,而香水提供商是一个真实的对象,可以售卖香水,小明通过代理商小红,购买到法国的香水,这就是一个代购的例子
代理可以分为静态代理和动态代理两大类:
静态代理
- 优点:代码结构简单,较容易实现
- 缺点:无法适配所有代理场景,如果有新的需求,需要修改代理类,不符合软件工程的开闭原则
动态代理
- 优点:能够动态适配特定的代理场景,扩展性较好,符合软件工程的开闭原则
- 缺点:动态代理需要利用到反射机制和动态生成字节码,导致其性能会比静态代理稍差一些,但是相比于优点,这些劣势几乎可以忽略不计
代理模式
(1)我们定义好一个售卖香水的接口
,定义好售卖香水的方法并传入该香水的价格。
public interface SellPerfume {
void sellPerfume(double price);
}
(2)定义香奈儿(Chanel)香水提供商,实现接口
。
public class ChanelFactory implements SellPerfume {
@Override
public void sellPerfume(double price) {
System.out.println("成功购买香奈儿品牌的香水,价格是:" + price + "元");
}
}
(3)定义小红代理类,她需要代购去售卖香奈儿香水,所以她是香奈儿香水提供商的代理对象,同样实现接口,并在内部保存对目标对象(香奈儿提供商)的引用,控制其它对象对目标对象的访问
。
public class XiaoHongSellProxy implements SellPerfume {
private SellPerfume sellPerfumeFactory;
public XiaoHongSellProxy(SellPerfume sellPerfumeFactory) {
this.sellPerfumeFactory = sellPerfumeFactory;
}
@Override
public void sellPerfume(double price) {
doSomethingBeforeSell(); // 前置增强
sellPerfumeFactory.sellPerfume(price);
doSomethingAfterSell(); // 后置增强
}
private void doSomethingBeforeSell() {
System.out.println("小红代理购买香水前的额外操作...");
}
private void doSomethingAfterSell() {
System.out.println("小红代理购买香水后的额外操作...");
}
}
(4)小明是一个需求者,他需要去购买香水,只能通过小红去购买
public class XiaoMing {
public static void main(String[] args) {
ChanelFactory factory = new ChanelFactory();
XiaoHongSellProxy proxy = new XiaoHongSellProxy(factory);
proxy.sellPerfume(1999.99);
}
}
实现代理模式,需要走以下几个步骤:
- 定义真实对象和代理对象的公共接口(售卖香水接口)
- 代理对象内部保存对真实目标对象的引用(小红引用提供商)
- 访问者仅能通过代理对象访问真实目标对象,不可直接访问目标对象(小明只能通过小红去购买香水,不能直接到香奈儿提供商购买)
代理模式的目的:
- 通过代理对象的隔离,可以在对目标对象访问前后增加额外的业务逻辑,实现功能增强。
- 通过代理对象访问目标对象,可以防止系统大量地直接对目标对象进行不正确地访问,出现不可预测的后果
动态代理在静态代理的基础上做了改进,极大地提高了程序的可维护性和可扩展性
动态代理
产生代理对象的时机是运行时动态生成,它没有 Java 源文件,直接生成字节码文件实例化代理对象
;而静态代理的代理对象,在程序编译时已经写好 Java 文件了,直接 new 一个代理对象即可。- 动态代理比静态代理更加稳健,对程序的可维护性和可扩展性更加友好
动态代理解决的问题是面对新的需求时,不需要修改代理对象的代码,只需要新增接口和真实对象,在客户端调用即可完成新的代理。
JDK Proxy
JDK Proxy 是 JDK 提供的一个动态代理机制,它涉及到两个核心类,分别是Proxy和InvocationHandler
以小红代理卖香水的故事为例,香奈儿香水提供商依旧是真实对象,实现了SellPerfume接口,这里不再重新写了,重点是小红代理,这里的代理对象不再是小红一个人,而是一个代理工厂
,里面会有许多的代理对象
代理对象是在程序运行过程中,由代理工厂动态生成,代理对象本身不存在 Java 源文件。
首先,代理工厂需要实现InvocationHanlder接口并实现其invoke()方法。
public class SellProxyFactory implements InvocationHandler {
/**
* 代理的真实对象
*/
private Object realObject;
public SellProxyFactory(Object realObject) {
this.realObject = realObject;
}
/**
* invoke() 方法是一个代理方法,
* 也就是说最后客户端请求代理时,执行的就是该方法
*
* @param proxy 代理对象
* @param method 真正执行的方法
* @param args 调用第二个参数 method 时传入的参数列表值
*
* @return
*
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
doSomethingBefore();
Object obj = method.invoke(realObject, args);
doSomethingAfter();
return obj;
}
private void doSomethingAfter() {
System.out.println("执行代理后的额外操作...");
}
private void doSomethingBefore() {
System.out.println("执行代理前的额外操作...");
}
如何通过代理工厂动态生成代理对象
生成代理对象需要用到Proxy类,它可以帮助我们生成任意一个代理对象,里面提供一个静态方法newProxyInstance。
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
客户端请求代理时
public static void main(String[] args) {
//设置系统属性,生成$Proxy0的class文件,在项目根目录下增加com/sun/proxy目录
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
ChanelFactory chanelFactory = new ChanelFactory();
SellProxyFactory sellProxyFactory = new SellProxyFactory(chanelFactory);
SellPerfume sellPerfume =
//生成代理对象需要用到Proxy类,它可以帮助我们生成任意一个代理对象
(SellPerfume) Proxy.newProxyInstance(
//加载动态代理类的类加载器
chanelFactory.getClass().getClassLoader(),
//代理类实现的接口,可以传入多个接口
chanelFactory.getClass().getInterfaces(),
//指定代理类的调用处理程序,即调用接口中的方法时,会找到该代理工厂h,执行invoke()方法
sellProxyFactory
);
sellPerfume.sellPerfume(1999.99);
}
新增红酒代理功能时,需要2个步骤:
- 创建新的红酒提供商SellWineFactory和售卖红酒接口SellWine
- 在客户端实例化一个代理对象,然后向该代理对象购买红酒
开闭原则:面向扩展开放,面向修改关闭。动态代理正是满足了这一重要原则,在面对功能需求扩展时,只需要关注扩展的部分,不需要修改系统中原有的代码。
Proxy.newProxyInstance() 是生成动态代理对象的关键
private static final Class<?>[] constructorParams ={ InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 获取代理类的 Class 对象
Class<?> cl = getProxyClass0(loader, intfs);
// 获取代理对象的显示构造器,参数类型是 InvocationHandler
final Constructor<?> cons = cl.getConstructor(constructorParams);
// 反射,通过构造器实例化动态代理对象
return cons.newInstance(new Object[]{h});
}
动态代理对象,那么是如何生成的呢
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
// 去代理类对象缓存中获取代理类的 Class 对象
return proxyClassCache.get(loader, interfaces);
}
发现里面用到一个缓存 proxyClassCache,从结构来看类似于是一个 map 结构,根据类加载器loader和真实对象实现的接口interfaces查找是否有对应的 Class 对象
public V get(K key, P parameter) {
// 先从缓存中查询是否能根据 key 和 parameter 查询到 Class 对象
// ...
// 生成一个代理类
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
}
在 get() 方法中,如果没有从缓存中获取到 Class 对象,则需要利用 subKeyFactory 去实例化一个动态代理对象,而在 Proxy 类中包含一个 ProxyClassFactory 内部类,由它来创建一个动态代理类,所以我们接着去看 ProxyClassFactory 中的 apply() 方法
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
// 非常重要,这就是我们看到的动态代理的对象名前缀!
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 一些状态校验
// 计数器,该计数器记录了当前已经实例化多少个代理对象
long num = nextUniqueNumber.getAndIncrement();
// 动态代理对象名拼接!包名 + "$Proxy" + 数字
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// 生成字节码文件,返回一个字节数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 利用字节码文件创建该字节码的 Class 类对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
apply() 方法中注意有两个非常重要的方法:
- ProxyGenerator.generateProxyClass():它是生成字节码文件的方法,它返回了一个字节数组,字节码文件本质上就是一个字节数组,所以 proxyClassFile数组就是一个字节码文件
- defineClass0():生成字节码文件的 Class 对象,它是一个 native 本地方法,调用操作系统底层的方法创建类对象
CGLIB
CGLIB(Code generation Library) 不是 JDK 自带的动态代理,它需要导入第三方依赖,它是一个字节码生成类库,能够在运行时动态生成代理类对 Java类 和 Java接口 扩展
。
-
依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib-nodep</artifactId> <version>3.3.0</version> <scope>test</scope> </dependency>
-
CGLIB 代理中有两个核心的类:MethodInterceptor接口 和 Enhancer类,前者是实现一个代理工厂的根接口,后者是创建动态代理对象的类
/** * 定义代理工厂SellProxyFactory */ public class SellProxyFactory implements MethodInterceptor { // 关联真实对象,控制对真实对象的访问 private Object realObject; /** * 从代理工厂中获取一个代理对象实例,等价于创建小红代理 */ public Object getProxyInstance(Object realObject) { this.realObject = realObject; Enhancer enhancer = new Enhancer(); // 设置需要增强类的类加载器 enhancer.setClassLoader(realObject.getClass().getClassLoader()); // 设置被代理类,真实对象 enhancer.setSuperclass(realObject.getClass()); // 设置方法拦截器,代理工厂 enhancer.setCallback(this); // 创建代理类 return enhancer.create(); } /** * @param o 被代理对象 * @param method 被拦截的方法 * @param objects 被拦截方法的所有入参值 * @param methodProxy 方法代理,用于调用原始的方法 * * @return * * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { doSomethingBefore(); // 前置增强 Object object = methodProxy.invokeSuper(o, objects); doSomethingAfter(); // 后置增强 return object; } private void doSomethingBefore() { System.out.println("执行方法前额外的操作..."); } private void doSomethingAfter() { System.out.println("执行方法后额外的操作..."); } }
-
调用
public static void main(String[] args) { //生动态代理 class 文件位置 String path = XiaoMing.class.getResource("").getPath(); System.out.println("生动态代理 class 文件位置" + path); System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, path); SellProxyFactory sellProxyFactory = new SellProxyFactory(); // 获取一个代理实例 SellPerfumeFactory proxyInstance = (SellPerfumeFactory) sellProxyFactory.getProxyInstance(new SellPerfumeFactory()); // 创建代理类 proxyInstance.sellPerfume(1999.99); }
CGLIB 的使用方法:
- 代理工厂需要实现 MethodInterceptor 接口,并重写方法,内部关联真实对象,控制第三者对真实对象的访问;代理工厂内部暴露 getInstance(Object realObject) 方法,用于从代理工厂中获取一个代理对象实例
- Enhancer 类用于从代理工厂中实例化一个代理对象,给调用者提供代理服务。
JDK Proxy 和 CGLIB 的对比
参考
Cglib源码分析 invoke和invokeSuper的差别
cglib动态代理中invokeSuper和invoke的区别