技术文章第一时间送达!
反射是指计算机程序在运行时访问、检测和修改它本身状态或行为的一种能力,是一种元编程语言特性,有很多语言都提供了对反射机制的支持,它使程序能够编写程序。Java的反射机制使得Java能够动态的获取类的信息和调用对象的方法。
在Java中,Class(类类型)是反射编程的起点,代表运行时类型信息(RTTI,Run-Time Type Identification)。java.lang.reflect包含了Java支持反射的主要组件,如Constructor、Method和Field等,分别表示类的构造器、方法和域,它们的关系如下图所示。
Constructor和Method与Field的区别在于前者继承自抽象类Executable,是可以在运行时动态调用的,而Field仅仅具备可访问的特性,且默认为不可访问。下面了解下它们的基本用法:
// 1. 采用Class.forName获取类的Class对象
Class clazz0 = Class.forName("com.yhthu.java.ClassTest");
System.out.println("clazz0:" + clazz0);
// 2. 采用.class方法获取类的Class对象
Class clazz1 = ClassTest.class;
System.out.println("clazz1:" + clazz1);
// 3. 采用getClass方法获取类的Class对象
ClassTest classTest = new ClassTest();
Class clazz2 = classTest.getClass();
System.out.println("clazz2:" + clazz2);
// 4. 判断Class对象是否相同
System.out.println("Class对象是否相同:" + ((clazz0.equals(clazz1)) && (clazz1.equals(clazz2))));
注意:三种方式获取的Class对象相同的前提是使用了相同的类加载器,比如上述代码中默认采用应用程序类加载器(sun.misc.Launcher$AppClassLoader)。不同类加载器加载的同一个类,也会获取不同的Class对象:
// 自定义类加载器
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
// 采用自定义类加载器加载
Class clazz3 = Class.forName("com.yhthu.java.ClassTest", true, myLoader);
// clazz0与clazz3并不相同
System.out.println("Class对象是否相同:" + clazz0.equals(clazz3));
通过Class的getDeclaredXxxx和getXxx方法获取构造器、方法和域对象,两者的区别在于前者返回的是当前Class对象申明的构造器、方法和域,包含修饰符为private的;后者只返回修饰符为public的构造器、方法和域,但包含从基类中继承的。
// 返回申明为public的方法,包含从基类中继承的
for (Method method: String.class.getMethods()) {
System.out.println(method.getName());
}
// 返回当前类申明的所有方法,包含private的
for (Method method: String.class.getDeclaredMethods()) {
System.out.println(method.getName());
}
通过Class的newInstance方法和Constructor的newInstance方法方法均可新建类型为Class的对象,通过Method的invoke方法可以在运行时动态调用该方法,通过Field的set方法可以在运行时动态改变域的值,但需要首先设置其为可访问(setAccessible)。
注解(Annontation)是Java5引入的一种代码辅助工具,它的核心作用是对类、方法、变量、参数和包进行标注,通过反射来访问这些标注信息,以此在运行时改变所注解对象的行为。Java中的注解由内置注解和元注解组成。内置注解主要包括:
首先,注解中的方法并不支持所有的数据类型,仅支持 八种基本数据类型、String、Class、enum、Annotation和它们的数组 。比如以下代码会产生编译时错误:
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
// 1. 注解数据类型不能是Object;2. 默认值不能为null
Object value() default null;
// 支持的定义方式
String value() default "";
}
其次,上节中提到的反射相关类(Class、Constructor、Method和Field)和Package均实现了AnnotatedElement接口,该接口定义了访问反射信息的方法,主要如下:
// 获取指定注解类型
getAnnotation(Class<T>):T;
// 获取所有注解,包括从父类继承的
getAnnotations():Annotation[];
// 获取指定注解类型,不包括从父类继承的
getDeclaredAnnotation(Class<T>):T
// 获取所有注解,不包括从父类继承的
getDeclaredAnnotations():Annotation[];
// 判断是否存在指定注解
isAnnotationPresent(Class<? extends Annotation>:boolean
当使用上例中的AnnotationTest 标注某个类后,便可在运行时通过该类的反射方法访问注解信息了。
@AnnotationTest("yhthu")
public class AnnotationReflection {
public static void main(String[] args) {
AnnotationReflection ar = new AnnotationReflection();
Class clazz = ar.getClass();
// 判断是否存在指定注解
if (clazz.isAnnotationPresent(AnnotationTest.class)) {
// 获取指定注解类型
Annotation annotation = clazz.getAnnotation(AnnotationTest.class);
// 获取该注解的值
System.out.println(((AnnotationTest) annotation).value());
}
}
}
当自定义注解只有一个方法value()时,使用注解可只写值,例如:@AnnotationTest("yhthu")
代理是一种结构型设计模式,当无法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,可以通过一个代理对象来间接访问,代理对象向客户端提供和真实对象同样的接口功能。经典设计模式中,代理模式有四种角色:
在实现上,代理模式分为静态代理和动态代理,静态代理的代理类二进制文件是在编译时生成的,而动态代理的代理类二进制文件是在运行时生成并加载到虚拟机环境的。JDK提供了对动态代理接口的支持,开源的动态代理库(Cglib、Javassist和Byte Buddy)提供了对接口和类的代理支持,本节将简单比较JDK和Cglib实现动态代理的异同,后续章节会对Java字节码编程做详细分析。
JDK实现动态代理是通过Proxy类的newProxyInstance方法实现的,该方法的三个入参分别表示:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
private Object getProxy() {
return Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class<?>[]{Subject.class},
new MyInvocationHandler(new RealSubject()));
}
private static class MyInvocationHandler implements InvocationHandler {
private Object realSubject;
public MyInvocationHandler(Object realSubject) {
this.realSubject = realSubject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Some thing before method invoke");
Object result = method.invoke(realSubject, args);
System.out.println("Some thing after method invoke");
return result;
}
}
类加载器采用当前类的加载器,默认为应用程序类加载器(sun.misc.Launcher$AppClassLoader);接口数组以Subject.class为例,调用方法处理类MyInvocationHandler实现InvocationHandler接口,并在构造器中传入Subject的真正的业务功能服务类RealSubject,在执行invoke方法时,可以在实际方法调用前后织入自定义的处理逻辑,这也就是AOP(面向切面编程)的原理。
Proxy.newProxyInstance的代理类是如何生成的?Proxy.newProxyInstance生成代理类的核心分成两步:
// 1. 获取代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
// 2. 利用Class获取Constructor,通过反射生成对象
cons.newInstance(new Object[]{h});
与反射获取Class对象时搜索classpath路径的.class文件不同的是,这里的Class对象完全是“无中生有”的。getProxyClass0根据类加载器和接口集合返回了Class对象,这里采用了缓存的处理。
// 缓存(key, sub-key) -> value,其中key为类加载器,sub-key为代理的接口,value为Class对象
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
// 如果实现了代理接口的类已存在就返回缓存对象,否则就通过ProxyClassFactory生成
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}
如果实现了代理接口的类已存在就返回缓存对象,否则就通过ProxyClassFactory生成。ProxyClassFactory又是通过下面的代码生成Class对象的。
// 生成代理类字节码文件
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
// defineClass0为native方法,生成Class对象
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
generateProxyClass方法是用来生成字节码文件的,根据生成的字节码文件,再在native层生成Class对象。
回答这个问题得先看下上面生成的Class对象究竟是什么样的,将ProxyGenerator生成的字节码保存成文件,然后反编译打开(IDEA直接打开),可见生成的Proxy.class主要包含equals、toString、hashCode和代理接口的request方法实现。
public final class $Proxy extends Proxy implements Subject {
// m1 = Object的equals方法
private static Method m1;
// m2 = Object的toString方法
private static Method m2;
// Subject的request方法
private static Method m3;
// Object的hashCode方法
private static Method m0;
// 省略m1/m2/m0,此处只列出request方法实现
public final void request() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
由于生成的代理类继承自Proxy,super.h即是Prxoy的InvocationHandler,即代理类的request方法直接调用了InvocationHandler的实现,这就回答了InvocationHandler的invoke方法是如何被调用的了。
Cglib的动态代理是通过Enhancer类实现的,其create方法生成动态代理的对象,有五个重载方法:
create():Object
create(Class, Callback):Object
create(Class, Class[], Callback):Object
create(Class, Class[], CallbackFilter, Callback):Object
create(Class[], Object):Object
常用的是第二个和第三个方法,分别用于动态代理类和动态代理接口,其使用方法如下:
private Object getProxy() {
// 1. 动态代理类
return Enhancer.create(RealSubject.class, new MyMethodInterceptor());
// 2. 动态代理接口
return Enhancer.create(Object.class, new Class<?>[]{Subject.class}, new MyMethodInterceptor());
}
private static class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Some thing before method invoke");
Object result = proxy.invokeSuper(obj, args);
System.out.println("Some thing after method invoke");
return result;
}
}
从上小节可知,JDK只能代理接口,代理生成的类实现了接口的方法;而Cglib是通过继承被代理的类、重写其方法来实现的,如:create方法入参的第一个参数就是被代理类的类型。当然,Cglib也能代理接口,比如getProxy()方法中的第二种方式。