目录
什么是动态代理
我们熟知的Javaee中大量框架使用到动态代理这个概念,那么动态代理到底是个什么玩意呢?
我们知道接口他是无法生成对象的,他只能被实现,而动态代理就是程序在运行中,通过一种方式使得接口生成一个代理的class文件,然后再通过class文件再生成代理类的对象。并且通过构造方法来传入InvocationHandler对象,而InvocationHandler对象通过用户自己重写并传入,具体的代理业务逻辑就可以在其中实现。InvocationHandler顾名思义就是用来处理接口代理对象的方法的回调。
静态代理和动态代理区别
静态代理一般是手动去创建一个代理类,代理类中维护一个接口的实现类,然后创建一个代理方法,将原有的实现类的方法在代理方法中执行,此时就可以在原有方法的前后做一些处理了。通俗易懂的话来说就是套一层就可以为所欲为了(手动滑稽),入下图所示。
这样就可以不改变原有代码的情况下增强或者改变。那要动态代理干嘛?
虽然这样确实可以实现,但是你去思考随着业务越来越复杂,当你接口的实现类越来越多你的代理类会越来越多,并且当接口的方法做出改变的时候,其他所有的代理类都需要做改变,极其难维护。而动态代理就不需要开发者再去手动创建代理类,JDK底层帮你通过接口的class文件,克隆出一个class文件,再通过底层维护的字节码生成器,生成一个代理的class文件,再通过class文件反射创建一个代理的对象。代理对象中需要传一个InvocationHandler。如下图所示。
区别就在于JDK底层帮你根据接口生成了一个字节码文件,内部还维护了InvocationHandler对象,所以你接口有改变也没关系,JDK底层去帮你生成新的字节码文件,所以是一个可变动态的。
动态代理案例
概念都讲了一大堆了,这时候需要来个案例来证实一下概念。
/**
* @author liha
* @version 1.0
* @date 2022/3/17 14:36
*/
public class JDKProxy {
public static void main(String[] args) {
UserDao dao = new UserDaoImpl();
Class[] interfaces = {UserDao.class};
/*
* 也就是newProxyInstance这个静态方法返回的对象就是一个代理对象
* */
dao= (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new MyInvocationHandler(dao));
/*
* 他会回调代理对象的方法
* */
dao.update("1");
}
}
class MyInvocationHandler implements InvocationHandler{
Object object;
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 被增强方法之前
System.out.println("这里是之前");
//原来代码的执行
Object res = method.invoke(object, args);
System.out.println(res);
//增强方法之后
System.out.println("这里是之后");
return res;
}
}
interface UserDao{
String update(String newData) ;
}
class UserDaoImpl implements UserDao{
@Override
public String update(String newData) {
System.out.println("update被执行");
return newData;
}
}
理论和实操介绍过后,就是源码证明了。
源码解读
我们直接看到newProxyInstance()方法创建代理对象的具体实现。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
// 克隆一份
// 目的是不影响原有的Class
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 这里就是去获取缓存,如果缓存中没有就去创建字节码
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// 获取到生成的代理类的构造方法,这里传入了InvocationHandler,所以是一个有参构造
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
// 一些修饰符的判断,如果为private,就通过一个标识符来控制代理
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
// 通过字节码然后反射生成代理对象
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
具体步骤如下:
- 为了不影响原本接口的class文件,克隆出一个
- 一些必要的验证
- 得到代理的Class——查询缓存,缓存中没有就去创建,具体逻辑下面会详细讲解
- 从代理的Class中获取到有参构造方法(InvocationHandler)
- 反射(执行有参构造)得到代理对象
接着看到创建代理Class对象的过程。
里面大量使用到函数式接口(也就是一个接口回调),这里大家可以去学习一下,不然想自己追具体的逻辑不一定看得懂。
因为是查缓存,没有就创建,所以直接看到创建的流程,在Proxy中维护了一个WeakCache对象。
我们暂时不关心如何查缓存,更关心如何创建,所以跳过一些缓存的判断,直接看到创建的代码。
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 代理类的名字的前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 原子类,用来计数
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 回调方法
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
// 遍历所有的接口
// 并且这个for循环其实也就是在检查一些内容,并没有来参与创建
// 其实你会发现,这些源码啊,他们为了性能,判断过程都是分几批来判断,一层一层的筛选
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
try {
// 获取到接口的Class
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {
}
// 如果接口不一致
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// 如果接口不是接口
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 判断接口是否重复
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}
// 用来保存代理类的名字
String proxyPkg = null; // package to define proxy class in
// 生成的代理类的修饰符
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 再次遍历所有接口
for (Class<?> intf : interfaces) {
// 获取到接口的修饰符
int flags = intf.getModifiers();
// 如果修饰符不为public的话生成的代理类和接口同包
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
// 如果是public修饰的接口就拼接生成的代理接口的路径com.sun.proxy.
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 生成名字的最后后缀就是全局计数器,从0开始
// 所以最后代理名字为com.sun.proxy.$Proxy+计数器值
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// ProxyGenerator做一个代理类的字节码生成
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 这是一个native方法,底层调JNI来对字节码做处理,返回一个代理的Class对象
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
具体为什么会走到这里来创建,大家可以自己追一下,并不难,就是有点点绕。
创建的过程:
- 做一些必要的判断逻辑
- 根据接口的修饰符创建出代理类的修饰符
- 根据接口的修饰符创建代理类的名字ReflectUtil.PROXY_PACKAGE + "."+"$Proxy"+索引
- 根据ProxyGenerator生成代理类的字节码,因为创建类,是需要字节码文件的。
- 根据defineClass0()JNI方法对字节码做操作并且返回代理的Class文件为后面的操作做铺垫
创建完代理的Class类后就是根据Class反射得到代理的实例对象
我们知道一个Class中包含了一个类的构造器、方法、字段。InvocationHandler需要我们来手动赋值,所以这里获取到代理类的有参构造方法,因为要通过有参构造方法将我们自定义的InvocationHandler 类给传到代理对象中,然后有参构造方法反射获取到代理类的实例对象并且返回实例对象。
后面通过代理对象调用原有的接口方法,就会走InvocationHandler接口中的invoke()方法执行。
有读者可能就会问你怎么知道代理类运行方法会走InvocationHandler接口中的invoke()方法?
这里逻辑推理一下,我们猜测代理类干了一些什么:
- 实现了UserDao接口
- 重写接口中的方法
- 实现了有参构造方法,要传一个InvocationHandler进去。
话不多说直接上代理类的字节码的反编译
public final class $proxy0 extends Proxy implements UserDao {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 需要传入的InvocationHandler对象,也就是我们重写的InvocationHandler对象
public $proxy0(InvocationHandler var1) throws {
// 回调给Proxy类,如上图所示
super(var1);
}
// 省略了hashcode、equals、toString方法
// UserDao里面的方法进行重写。
public final String update(String var1) throws {
try {
// 调用InvocationHandler的invoke,这里就已经证明了代理类是走invoke方法了
// 这里的m3也是static{}静态代码块中初始化完毕了
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 类被创建的时候就进行初始化工作
// 这里也就是提前通过反射获取到class,再通过class获取到具体的Method
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.liha.UserDao").getMethod("update", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
这里也就证实了代理类运行方法是回调的InvocationHandler的invoke()方法。
Java查看动态代理生成的字节码文件https://blog.csdn.net/qq_43799161/article/details/123559050
总结
可能概念讲的没那么通俗易懂,更希望大家不是为了面试来背概念的,更希望大家是写个案例,然后追源码来证实天上飞的这些概念。下面是笔者Cglib的源码解读,有需要的可以跳转链接阅读!
从源码角度理解Cglib动态代理https://blog.csdn.net/qq_43799161/article/details/123604338?spm=1001.2014.3001.5502
最后,有不懂的地方请评论区留言。如果本帖对读者有帮助,希望点赞+关注+收藏,您的支持是给我最大的动力,一直在努力的更新各种框架的使用和源码解读!