恶心的东西就更要花时间让它变得更恶心🧎♀️ ----xmonster
文章目录
开篇
可以去看《设计模式之禅》(第2版)里对代理模式的理解,他利用的是“游戏打怪”的例子,我觉得非常的生动形象,理解起来非常通透!如果你对代理模式不是很通透,那么动态代理你就自然比较难理解了,不做无用功,先去补基础 (别忘了补完回来);如果你已经特别了解,那么就继续往下吧!
来一个图吧,没有任何意义,只是为了提个醒,这是上面那本书的流程图
本篇博文较长,望耐心看完🐈
💜💜
🧡🧡🧡🧡🧡
💛💛💛💛💛
走一遍百度百科
动态代理类
Java动态代理类位于Java.lang.reflect包下,这里和Java的反射是息息相关的,如果不了解Java的反射,是比较难理解动态代理的,这里有一篇我对Java反射的基本理解总结,主要参考百度百科:大话Java反射机制
一般主要涉及到以下两个类:
一、Interface InvocationHandler
:该接口中仅定义了一个方法Object:invoke(Object obj,Method method,Object[] args)。在实际使用时,第一个参数obj一般是指代理类
,method是被代理的方法
,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
二、Proxy
:该类即为动态代理类其中主要包含以下内容:
Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。
Static Class getProxyClass (ClassLoader loader,Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。(为某个接口创建代理)
重点看以下方法:
Static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h):返回代理类的一个实例
,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)。
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它
,然后该class就宣称它实现了这些 interface
。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler
,由它接管实际的工作。
代理机制及特点(使用步骤)
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过为 Proxy 类指定 ClassLoader 对象
和一组 interface
来创建动态代理类;
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型
;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
上面没看懂,没关系,下面慢慢来🥂
以下我们的代码就按照这个顺序来完成对应的动态代理实现
动态代理的工具类代码
下面的代码注解是我个人的一些理解,有点乱,但很nice,也可直接跳过~
package com.xmonster.demo01;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等会用这个类自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
// Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
// new Class<?>[] { Foo.class },
// handler);
// 被代理的接口
// 1、代理一个接口 这个接口说白了是谁,就是真实对象;真实对象会实现那个接口,我们代理它就完了
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
// getClassLoader()类加载器:它负责将字节码文件加载到内存,创建Class对象
// target.getClass().getClassLoader() 通过target示例得到的Loader()信息
// target.getClass().getInterfaces() 得到对应的interfaces信息(反射)
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// target.getClass().getClassLoader()
/* target.getClass()得到它的class对象
* getClassLoader():每个Class对象都会有一个方法,可以获取到它的ClassLoader
* target.getClass().getClassLoader():获取类以及获取到这个类的类加载器
*
*
*
* */
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
// newProxyInstance:返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
// loader - 类加载器来定义代理类
//interfaces - 代理类实现的接口列表
//h - 调度方法调用的调用处理函数
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理的是一个接口
//动态代理的本质,就是使用反射机制来实现
setHost();
Object invoke = method.invoke(target, args);
return invoke;
}
}
这段代码相信有经验的老狗们都不陌生,这段代码是我看狂神的视频写下的,所以用的也是他的例子,房东和中介的例子,那么现在让我们一探究竟上述代码含义和使用
先来一段测试类吧
package com.xmonster.demo01;
public class Client {
public static void main(String[] args) {
// 真实对象
UserService userService = new UserServiceImpl();
// 代理对象
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 生成代理角色
pih.setTarget(userService); //设置要代理的对象
// 动态生成、获得代理类,获得代理类对象
// 注意这里的强制转换
UserService proxy =(UserService) pih.getProxy();
// Object proxy = pih.getProxy();
// 调用响应的代理的方法即可
proxy.query();
}
}
分析
回到上面我们讲的,代理类和被代理类都是实现了一个“接口”,只不过代理类传递了一个参数(传递被代理类),然后帮助被代理类干事情,所以重点就是“接口”
(代理和实际对象一般都是有相同的接口)
这也就是为什么
UserService proxy =(UserService) pih.getProxy();
这里要强转的原因
这里不像适配器和装饰器,因为代理一般不改变接口
实现方式
实现方式一般有2种,一种是Java SDK,一种是cglib
这里关于newProxyInstance、InvocationHandler 的例子和参数理解在这篇博客:
且玩一玩动态代理之小栗子
因为这里也是需要结合这个例子来玩,所以先看上面的例子,对照理解
将SimpleInvocationHandler 的主函数改成另一种方式实现:
Class<?> proxyClass = Proxy.getProxyClass(IService.class.getClassLoader(),
new Class<?>[] {IService.class});
Constructor<?> ctor = proxyClass.getConstructor(new Class<?>[] {InvocationHandler.class});
InvocationHandler handle = new SimpleInvocationHandler(realService);
IService proxyService = (IService) ctor.newInstance(handle);
proxyService.sayHello();
理解步骤:
- 通过Proxy.getProxyClass创建了代理类的“定义”,类定义会被缓存;
- 获取了类的构造方法,其中构造方法有一个InvocationHandler类型的参数
- 创建InvocationHandler对象,创建了代理对象
Proxy.getProxyClass
这个方法的源码:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
Class<?> caller = System.getSecurityManager() == null
? null
: Reflection.getCallerClass();
return getProxyConstructor(caller, loader, interfaces)
.getDeclaringClass();
}
可以看到这个方法需要两个参数,一个是ClassLoader,另一个是接口数组
给定类加载器和接口数组的代理类的java.lang.Class对象。 代理类将由指定的类加载器定义,并将实现所有提供的接口。 如果任何给定的接口是非公开的,则代理类将是非公开的。 如果类加载器已经定义了接口相同置换的代理类,那么将返回现有的代理类; 否则,这些接口的代理类将被动态生成并由类加载器定义。
这个方法会动态生成一个类,类名以$Proxy开头,然后后面会跟一些数字之类的
🔺参数
loader - 类加载器来定义代理类
interfaces - 要实现的代理类的接口列表
🔺结果
在指定的类加载器中定义并实现指定接口的代理类
该方法给定
类加载器
和接口数组的代理类的java.lang.Class对象
。 代理类将由指定的类加载器定义,并将实现所有提供的接口
。 如果任何给定的接口是非公开的,则代理类将是非公开的。 如果类加载器已经定义了接口相同置换的代理类,那么将返回现有的代理类; 否则,这些接口的代理类将被动态生成并由类加载器定义。
$Proxy0
这里分析$Proxy0的源码的话就会特别清楚(代码比较长,这里没有放出来了,大家可以百度,样子都长得差不多,主要看它的构造方法和结构)
$Proxy0的父类是Proxy,并且它有一个构造方法,接受一个InvocationHandler类型的参数,保存为了实例变量h,h定义在父类的Proxy种,
它实现了接口IService
,对于每个方法,都会调用InvocationHandler的invoke方法,对于Object中的许多方法,也同样转给了InvocationHandler
这个类定义本身与被代理的对象没有关系,与InvocationHandler的具体实现也没有关系,而主要和接口数组有关,给定这个接口数组,它就会动态创建每个接口的实现代码,实现就是转发给InvocationHandler,与被代理对象以及它的调用由InvocationHandler的实现管理
接下来就是获取构造方法,创建代理对象!
getConstructor
@CallerSensitive
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException
{
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
return getReflectionFactory().copyConstructor(
getConstructor0(parameterTypes, Member.PUBLIC));
}
- getConstructor方法返回一个Constructor对象,该对象反映Constructor对象表示的类的指定的公共类函数。 parameterTypes参数是以声明顺序标识构造函数的形式参数类型的类对象的数组。 如果此类对象表示在非静态上下文中声明的内部类,则形式参数类型将显式包围实例作为第一个参数。
newInstance
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
- 使用由此Constructor对象表示的构造函数,使用指定的初始化参数创建和初始化构造函数的声明类的新实例。 个别参数自动解包以匹配原始形式参数,原始参考参数和参考参数都需要进行方法调用转换。
- 如果构造函数正常完成,则返回新创建和初始化的实例。
- 参数initargs - 要作为构造函数调用的参数传递的对象的数组; 原始类型的值被包装在适当类型的包装器对象中(例如float中的float )
结果
通过调用此对象代表的构造函数创建的新对象
这样整个过程基本结束