Java在运行时可以实时获取对象的类型信息:RTTI(RealTime Type Identification)。比如多态,父类引用子类对象,调用方法时可以准确调用子类的override方法。本文从RTTI讲反射,然后讲一个与反射-动态代理相关的主要应用。
类对象
getClass()
Class.forName(String)
反射:在运行时获取类信息
动态代理:反射的一大应用
调用处理程序
代理人
静态代理
动态代理
动态代理内部
动态代理应用
JDK 与 CGLib
Java 想知道一个对象的类型信息,并且必须将类型信息存储在某个地方:
.java该文件是类的源代码,运行时肯定不会用到;
.class该文件是源代码编译后的字节码,也是静态存储的,运行时不使用;
但是,当 jvm 加载一个 java 类(类文件)时,它会生成一个特殊的对象:Class 对象 。 该对象记录了类的相关信息,具体信息类型可以在java的Class.java中查看。 比如有一个类叫Father,加载后会生成一个Father的Class对象 Class。
与来自Father new的其他对象相比,Class对象是一个特殊的对象,对于每个类,Class对象都是一个单例。类new的每个对象都有一个指向该类的Class对象的指针,这样对于每个对象,通过找到它的Class对象,就可以得到该对象在运行时的结构:属性、方法、类信息等。
每个对象都有一个指向该类的Class对象的指针,这体现在代码层面,即在Object对象中,有一个获取Class对象的方法:
public final native Class<?> getClass();
Java中获取Class对象的另一种方法是使用Class类提供的静态方法:
public static Class<?> forName(String className) 抛出 ClassNotFoundException
只要给出完整的类名(类名加包名),就可以获得该类的Class对象。
类对象记录类的信息。对于每一个对象,通过它的类对象可以得到什么信息?看Class类的内容就明白了。
getFields:获取属性;
getConstructors:获取构造方法;
getDeclaredMethods:获取常用方法;
getAnnotations:获取注解;
getClasses:获取类的所有类信息(如父类、接口等);
还有其他辅助方法。总之,通过类对象,可以知道这个类的类名、继承关系、定义的属性、方法、构造函数、注解等。基本上一个类的所有信息都被反汇编了。
Field、Method、Annotation、Constructor等类都是上述数据结构的实现。它们都是 java.lang.reflect.*反射包( )中的核心类。
代理是一个很常见的概念。比如房产中介,可以简单理解为房东的代理人。租房者有租金要求,不是直接和房东打交道,而是和代理人沟通。至于代理人,是否直接解决租客的问题或与房东沟通。解决租客的问题后,即代表自己的内部事务。
假设有一个接口Coder:
public interface Coder {
/**
* 完成一个需求。
* @param demand demand
*/
void implementDemands(String demand);
/**
* 估计一个需求的时间。
* @param demand demand
*/
void estimateTime(String demand);
/**
* 说点什么
* @return
*/
String comment();}
这个接口是程序员要做的。现在有一个Java程序员:
import lombok.AllArgsConstructor;import org.apache.commons.lang3.RandomUtils;/**
* @author liuhaibo on 2018/04/18
*/@AllArgsConstructorpublic class JavaCoder implements Coder {
String name;
@Override
public void implementDemands(String demand) {
System.out.println(name + ": 我用Java完成" + demand);
}
@Override
public void estimateTime(String demand) {
System.out.println(name + ": 我将使用 " + (demand.length() + RandomUtils.nextInt(3, 9)) + " hours.");
}
@Override
public String comment() {
return "Java 是完美的!";
}
公共无效 showAngry() {
System.out.println(name + ": 我很生气!!!");
}}
它实现了程序员接口指定的任务。
静态代理:
/**
* @author liuhaibo on 2018/04/18
*/@ NoArgsConstructor@AllArgsConstructorpublic class StaticProxy implements Coder {
@Setter
private Coder coder;
@Override
public void implementDemands(String demand) {
// 代理要阻塞一些不合理的需求,给程序员最纯粹的任务
if ("illegal".equals(demand)) {
System.out.println("No! ILLEGAL demand! ");
返回;
}
System.out.println("好的,我会找个程序员来做的。");
coder.implementDemands(需求);
}
@Override
公共无效估计时间(字符串需求){
coder.estimateTime(需求);
}
@Override
public String comment() {
return coder.comment();
}}
这个静态代理和程序员的接口是一样的。当有外部需求提供时,它会处理,但实际上它只是调用Java程序员做事。这是一个假程序员,只是一个代理。当然,这个代理还做了一些额外的事情。比如当外部需求“非法”时,代理直接拒绝该需求,这样可以避免程序员的一些琐碎事情:
编码器 coder = new JavaCoder("Tom");
编码器 staticProxy = new StaticProxy(coder);
staticProxy.estimateTime("你好,世界");
staticProxy.implementDemands("发送广告");
staticProxy.implementDemands("非法");
输出:
汤姆:我会用 19 个小时。好的,我会找个程序员来做的。汤姆:我用 Java 来完成 发送广告不!非法需求!
静态代理不错,就是太麻烦了。如果要构建代理,则必须编写代理类来实现每个方法。如果有一个统一的要求:在程序执行每个方法之前和之后输出当前时间,那么写一个代理就太烦人了,看起来是重复的,毫无意义。
动态代理是jvm自动生成的代理,而不是类似上面开发者写的静态代理。此时,开发人员只需要指定代理的行为,剩下的交给 Java 来处理。
Java中创建动态代理的过程:
开发者指定接口;
JVM根据指定的接口自行动态生成一个类来实现接口;
接口中的每个方法都是这样实现的:对方法的每次调用都由开发人员指定的处理程序完成;
该处理程序的返回值将作为调用动态代理对象方法后的返回值。
所以理解 Java 动态代理的关键是区分 Java 会做什么和开发人员会做什么 。 开发者自己做就好了,剩下的就按照约定由Java来做。
所以开发者其实只需要担心两件事:
为哪个接口创建动态代理;
动态代理调用处理程序时要做什么;
第一步简单,第二步是核心。
我们先来看第二步。
InvocationHandler 是 Java 和开发者之间的核心契约:
对于java动态生成的接口的实现类,对它的每一个方法调用都会转换为对指定InvocationHandler的调用 。
而handler的返回值就是动态代理方法被调用后的返回值 。
例如,下面的 InvocationHandler 实现会输出方法调用前后的当前时间:
import lombok.AllArgsConstructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.time.LocalDateTime;/**
* 负责代理对象方法调用前后的bibi。
*
* @author liuhaibo on 2018/04/19
*/@AllArgsConstructorpublic class BibiAroundInvocationHandler implements InvocationHandler {
/**
* 被代理的对象。之所以使用它,是因为毕竟调用了原始方法。
* 如果不需要代理对象,则不需要放入
*/
private Coder coder;
/**
* 方法调用时将代理对象交给handler(回调handle的invoke方法)
*
* @param proxy jvm 生成的代理对象,如果与生成的代理对象无关,该参数可以省略
* @param method 要调用的代理对象的
方法 * @param args 代理对象方法的参数被调用.method + args 可以判断调用哪个方法
* @return 如果被调用的方法会返回一个值,一般返回值会原封不动的返回,当然也可以更改返回。
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(">>> Before: "+ LocalDateTime.now());
// 通过反射通过函数名、对象和函数参数调用这个函数。
对象结果 = method.invoke(coder, args);
System.out.println("<<< 之后:"+ LocalDateTime.now());
返回“(代理)”+结果;
}}
实现一个InvocationHandler其实就是实现他的invoke接口。当调用动态类的方法时,Java会调用接口的invoke方法并传入三个参数:
proxy:Java生成的动态代理类生成的对象;
method,args:方法名和方法参数,这两部分可以结合起来确定类中的一个方法;
具体到上面的实现:
首先,我们不关心实际的动态代理对象,所以不使用proxy参数;
由于我们要在被代理对象的方法调用前后进行bibi,所以必须分为三个步骤:
在调用方法之前,强制它;
调用代理对象的方法。因此,在创建 BibiAroundInvocationHandler 时,会传入一个代理对象 Coder 来调用其方法。当调用被反射时使用: Method#invoke(object, args);
方法调用后,强制执行;
设置调用函数的返回值。首先要知道BibiAroundInvocationHandler#invoke的返回值会被Java作为动态代理类的方法调用后的返回值。这里我们只是想在方法调用前后bibi,无意中改变了代理对象方法的返回值,所以对于代理对象,我们应该把它的返回值作为BibiAroundInvocationHandler#invoke的返回值。但是在返回之前,我给返回值加了一个“(Proxy)”前缀。作为demo,我们可以更清楚的看到动态代理对象的返回值被我们故意改变了,而且比代理好对象的返回值。返回值有一个额外的前缀。
解决了InvocationHandler实现的核心问题,还有一个简单的问题没有解决:为哪个接口生成动态代理类。
Java的Proxy类提供了创建动态代理类的方法。有两个方法比较重要:
public static Class<?> getProxyClass(ClassLoader loader, Class<?>...interfaces):
用于生成给定接口的动态代理类。需要传入接口和接口的ClassLoader,ClassLoader一般可以直接传入接口的ClassLoader;
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException:
用于为给定借口生成动态代理对象。其实这个方法包括前面的方法。毕竟要生成对象,必须先生成一个代理类,然后再生成一个新的对象。所以该方法大致分为三个步骤:
生成动态代理类 Class<?> cl = getProxyClass0(loader, intfs):;
获取动态代理类构造函数:
private static final Class<?>[] constructorParams = { InvocationHandler.class }; final Constructor<?> cons = cl.getConstructor(constructorParams);
生成动态代理对象 cons.newInstance(new Object[]{h}):;
值得关注的是生成对象的步骤。 首先,使用反射获取生成的动态代理类的构造函数。构造函数的参数是InvocationHandler。 然后将开发者定义的 InvocationHandler 实例传入构造函数,生成动态代理对象。 所以我们可以推测, Java在生成动态代理类时,除了要实现接口中给出的方法外,还必须添加一个参数,即 InvocationHandler 的构造方法 。
从动态代理方法只能传入接口参数可知,Java的动态代理 只能为接口生成代理,不能为具体的类生成代理 。
生成动态代理类并执行方法代码:
InvocationHandler handler = new BibiAroundInvocationHandler(coder);
// 直接生成代理对象实例(其实分两步:先在jvm中生成代理类Class对象,然后使用Class对象生成代理对象)
编码器代理 = (编码器) Proxy.newProxyInstance(coder.getClass().getClassLoader(), coder.getClass().getInterfaces(), handler);
proxy.estimateTime("你好,世界");
proxy.implementDemands("发送广告");
System.out.println(proxy.comment());
输出结果:
>>> 之前:2020-08-02T22:44:18.720Tom:我将使用 20 小时。<<< 之后:2020-08-02T22:44:18.721>>> 之前:2020-08-02T22:44: 18.721Tom:我使用 Java 完成发送广告<<< 之后:2020-08-02T22:44:18.721>>> 之前:2020-08-02T22:44:18.721<<< 之后:2020-08-02T22: 44:18.721(代理)Java 是完美的!
前两个方法输出调用前后的时间。第三种方法也是输出时间,然后输出方法的返回值:一个以“(Proxy)”为前缀的原始返回值。
以上分析了Java动态代理架构设计,Java与开发者之间的契约。现在我们来看看Java生成的代理类长什么样。
首先,使用java提供的ProxyGenerator类生成一个带有Coder接口的动态代理类,叫做DynamicCoder。然后将这个类的字节码写入一个文件:
/**
* @author liuhaibo on 2018/04/19
*/public class ProxyUtil {
public static void main(String[] args) throws IOException {
byte[] classFile = ProxyGenerator.generateProxyClass("DynamicCoder", JavaCoder.class.getInterfaces ());
文件 file = new File("/tmp/DynamicCoder.class");
FileOutputStream fos = new FileOutputStream(file);
fos.write(classFile);
fos.flush();
fos.close();
}}
然后使用IDE反编译字节码,大致看看生成的动态代理类的代码是什么样子的:
import example.proxy.Coder;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class DynamicCoder extends Proxy implements编码器{
私有静态方法m1;
私有静态方法 m5;
私有静态方法 m4;
私有静态方法 m3;
私有静态方法 m2;
私有静态方法 m0;
公共 DynamicCoder(InvocationHandler var1) {
super(var1);
}
public final boolean equals(Object var1) {
try {
return (Boolean) super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void estimateTime(String var1) {
try {
super.h.invoke(this, m5, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final void implementDemands(String var1) {
try {
super.h.invoke(this, m4, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String comment() {
try {
return (String) super.h.invoke(this, m3, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() {
try {
return (String) super.h.invoke(this, m2, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() {
try {
return (Integer) super.h.invoke(this, m0, (Object[]) null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
静态 {
尝试 {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m5 = Class.forName("example.proxy.Coder").getMethod("estimateTime", Class.forName("java.lang.String"));
m4 = Class.forName("example.proxy.Coder").getMethod("implementDemands", Class.forName("java.lang.String"));
m3 = Class.forName("example.proxy.Coder").getMethod("comment");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
首先,动态生成的代理类实现了Coder接口中的方法;
动态代理类确实有一个以InvocationHandler为参数的构造方法;
对于每个方法,动态代理都由 InvocationHandler 处理。在调用InvocationHandler的invoke时,按照合约约定,传入三个参数:动态代理对象本身、方法名、方法参数。
最后,每个方法的返回值就是调用invoke后的返回值。
由此,我们亲眼看到,当jvm按照约定运行时,Java确实为我们生成了一个动态代理类。
至此,我们可以得出一个结论:Java的动态代理并不神秘。其实它就像一个静态代理。创建一个类,将所有方法一一实现。这个实现可以像简单的静态代理类StaticProxy那样直接由代理对象来完成,也可以像Java生成的DynamicCoder那样由InvocationHandler来完成。
它们的本质是一样的:反正必须有这样一个代理类来生成代理对象。
它们的创建时间与创建者不同:静态代理类是开发者在编译前编写的;动态代理类由 jvm 在运行时生成。
动态代理应用最广泛的应该是Spring中实现的面向切面编程(AOP,Aspect Oriented Programming)。
在上面的例子中,通过动态代理,我们简单的输出了所有方法调用前后的时间。这是一个“横切逻辑”的场景。
Java中的继承是一个“垂直”的系统,方法前后的输出时间像狗皮膏药一样贴在每个方法的前后,是继承无法解决的。 如果把狗皮膏药和业务逻辑写在一起(就像一个静态代理一样),显然是杂乱无章的,重复性很高(每条业务逻辑前后都要贴两块相同的膏药)。 如果将方法一分为二,狗皮贴一分为二,提供一种机制,在调用业务逻辑代码前后自动调用狗皮贴, 不仅实现了想要的功能,而且还将业务代码和“狗皮膏药代码”分开 (性能监控、访问控制)、事务管理、日志记录等)。 这是一种“水平提取”的思想 ,即AOP。 动态代理显然可以很好地做到这一点。
当然,Spring的AOP不仅有Java动态代理模式的实现,还有使用CGLib实现动态代理的。
Java 的动态代理 只能为接口生成代理,不能为特定的类生成代理 。 而CGLib可以为具体的类生成动态代理,其思路大致如下:
动态生成一个类的子类;
这个子类可以拦截所有的父方法调用,并且在intercept方法中,完成父方法前后贴狗皮膏药的逻辑,就像JDK的InvocationHandler的invoke方法一样。
CGLib是底层字节码技术创建的子类,这个子类是代理类的代理。jdk就是为接口生成一个具体的实现类,用这个实现类代理另一个实现类。
另外CGLib创建动态代理比jdk慢,但是创建代理的效率比jdk创建的动态代理要高,所以如果要充当单例,最好使用CGLib。不过jdk动态代理的另一个好处是不需要引入额外的依赖,jdk就够了。CGLib技术显然需要引入第三方CGLib依赖。
你适合学Java吗?4大专业测评方法
代码逻辑 吸收能力 技术学习能力 综合素质
先测评确定适合在学习