版权声明:本文系博主原创,未经博主许可,禁止转载。保留所有权利。
引用网址:https://www.cnblogs.com/zhizaixingzou/p/10023612.html
目录
1. 反射
1.1. 什么是反射
我们知道,类被JVM加载进入内存之后,JVM就会为该类生成一个对应的java.lang.Class对象(Class为元类)。这个对象包含了该类的完整的结构信息,包括字段名、方法名及参数情况等。通过它,我们能获取到这些结构信息,就像通过一面镜子“反射”(相比于类实例的点号直接访问)过来的影像。通过该Class对象,程序可以获得检测、访问和修改该类的状态或行为的能力。
1.2. 怎么使用反射
1.2.1. 反射的核心类:java.lang.Class<T>
我们使用反射的入口是java.lang.Class<T>类。
java.lang.Class<T>是Java反射机制的核心,它在java_home\jre\lib\rt.jar的.\java\lang\reflect\目录下定义:
Class<T>的用途是充当元类,它好像是一面镜子,哪个类作为它声明变量的值,哪个类就可以通过该变量获得其自身结构信息。例如,Class<String> clazz = String.class,clazz就是String类型的模型了,通过它可以获得String类型的结构信息。
Class<T>可以表示一个类(包含枚举)、接口(包含注解)、各型数组、基本类型(包含void)。
1.2.2. 获取类对应的Class实例的3种方法
Class并无公共构造方法,JVM在加载一个类时,是通过调用类加载器ClassLoader的defineClass方法来获得该类的Class实例的,而该方法最终又调用如下本地方法:
private native Class<?> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
下面是3种获取该类对应Class实例的方法:
1)Class<?> clazz = Class.forName(“com.phewy.Employee”),参数须是全限定名形式的。
2)Class<?> clazz = Employee.class
3)Class<?> clazz = new Employee().getClass()
1.2.3. Class<T>的定义和使用
1.2.3.1. 获取类的组成
getFields(),返回类的所有公共字段,包括继承来的公共字段,Field[]。
getMethods(),返回类的所有公共方法,包括继承来的公共方法,Method[]。
getConstructors(),返回类的所有公共构造方法,构造方法谈不上继承,Constructor<?>[]。
getAnnotations(),返回类的所有注解,包括继承来的注解,Annotation[]。
getClasses(),返回类的所有公共内部类,如“com.cjw.studying.Person$Capacity”,Class<?>[]。
getField(String name),Field
getMethod(String name, Class<?>... parameterTypes),Method
getConstructor(Class<?>... parameterTypes),Constructor<T>
getAnnotation(Class<A> annotationClass),<A extends Annotation> A
返回类的指定参数的成员,公共的,包括继承来的。
getDeclaredFields(),Field[]
getDeclaredMethods(),Method[]
getDeclaredConstructors(),Constructor<?>[]
getDeclaredAnnotations(),Annotation[]
getDeclaredClasses(),Class<?>[]
返回类的所有成员,不只公共的,不包括继承来的。
getDeclaredField(String name) ,Field
getDeclaredMethod(String name, Class<?>... parameterTypes) ,Method
getDeclaredConstructor(Class<?>... parameterTypes) ,Constructor<T>
getDeclaredAnnotation(Class<A> annotationClass),<A extends Annotation> A
返回类的指定参数的成员,不只公共的,不包括继承来的。
getEnumConstants(),T[]
如果是枚举类型,则返回枚举类型的各个枚举值。
getModifiers(),int
返回类的修饰符集整型表示,Modifier.toString(modifiers)可以转换该整型为字符串表示。
getTypeParameters(),TypeVariable<Class<T>>[]
如果类是泛型,返回它的类型参数数组,每个元素代表一个类型参数。
getSuperclass(),Class<? super T>
返回类父类。
getPackage(),Package
返回类所在包。
getInterfaces(),Class<?>[]
返回类实现或扩展的接口集合,数组内顺序与声明顺序一致。
getComponentType(),Class<?>
如果类是数组,则返回类的组件类型,否则返回null。
new Thread() {}.getClass().getEnclosingMethod(),Method
如果类是匿名类,则返回定义该匿名类的方法(匿名类在该方法中)。
getEnclosingConstructor(),Constructor<?>
如果类是匿名类,则返回该匿名类所在的构造方法(匿名类在该构造方法中)。
getDeclaringClass(),Class<?>
返回直接定义(而非在方法内定义)类的外部类,方法内定义的匿名类的该方法返回null。
getEnclosingClass(),Class<?>
返回类的外部类,无论本类定义在外部类的字段处还是方法内。
getEnclosingClass()和getDeclaringClass()的区别见:http://lee-govern.iteye.com/blog/1776046
getName()
返回类的全限定名,对于数组则用描述符的形式,如“learning.demo.Entry”、“[I”、“[Ljava.lang.Integer;”。
getSimpleName()
返回简单的类名,如“Employee”、“Vector[][]”。
getCanonicalName()
返回Java语言规范汇中规定的规范化名称,如“learning.demo.Entry”、“java.util.Vector[][]”,也就是getSimpleName()返回的前面加上包名路径。
1.2.3.2. 类特征的判断
isInterface()
isArray()
isPrimitive()
isAnnotation()
isSynthetic()
isEnum()
判断类是否是接口、数组、基本类型(包括void)、注解、复合类、枚举。
isAnonymousClass()
判断类是否匿名类。
isLocalClass()
判断类是否本地类,即定义于方法内的非匿名的类。
1 public class Demo { 2 public static void main(String[] args) { 3 class Entry { 4 private int value; 5 6 public int getValue() { 7 return value; 8 } 9 10 public void setValue(int value) { 11 this.value = value; 12 } 13 } 14 } 15 }
isMemberClass()
判断类是否是内部成员类(非方法内定义,也非匿名类)。
isAnnotationPresent(Class<? extends Annotation> annotationClass)
如果指定注解有在类上,则返回true。
1.2.3.3. 类其他基本语法
toString()
1 public String toString() { 2 return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) 3 + getName(); 4 }
返回如下各种形式的字符串,枚举是一种类,注解是一种接口:
Class.forName("com.cjw.studying.Person"):class com.cjw.studying.Person
Class.forName("com.cjw.studying.IRun"):interface com.cjw.studying.IRun
Vector[][].class:class [[Ljava.util.Vector;
int.class:int
void.class:void
Class.forName("com.cjw.studying.Person")
返回指定全限定名对应的类,它等效于:
Class.forName("com.cjw.studying.Person", true, this.getClass().getClassLoader())
true表示如果之前没有初始化过该类,则初始化它,第三个参数如果是null,则使用引导类加载器加载该类,它会引发目标类的定位、加载和链接。
newInstance()
调用类的无参构造函数创建一个实例。如果没有无参构造函数,则会报异常,而且该构造函数必须是非private的,可以是protected或public的,Spring框架就是用这种方式创建实例的。
isInstance(object)
如果object不为null,且可以转换为类,则返回true,它是instanceOf的动态形式。
cast(object)
将object强制转换为该类。
isAssignableFrom(Employee.class)
判断参数是否是类的子类。
<U> Class<? extends U> asSubclass(Class<U> clazz)
父类为参数,转换为子类。
结果为:java.util.ArrayList
getClassLoader()
返回类的类加载器。
getResource(String name)
返回的类型为URL。
1 package com.cjw.learning.reflect; 2 3 public class Demo03 { 4 public static void main(String[] args) { 5 System.out.println(Person.class.getResource("")); 6 System.out.println(Person.class.getResource("/")); 7 System.out.println(Person.class.getClassLoader().getResource("")); 8 System.out.println(Person.class.getClassLoader().getResource("/")); 9 } 10 }
getResourceAsStream(String name)
返回的是InputStream类型。
name指定资源的路径。如果不以“/”开头,则是相对路径(可以使用.或者..两个符号),即与本.class在同一个包目录下,否则是从classPath下取,即本类所在包的顶级分项父目录下,这个父目录包含了许多的Jar包。
org.ethereum.solidity.compiler.Solc
com.x.y 下有类me.class,同时有资源文件myfile.xml:me.class.getResourceAsStream("myfile.xml")
com.x.y 下有类me.class ,同时在 com.x.y.file 目录下有资源文件myfile.xml:me.class.getResourceAsStream("file/myfile.xml")
com.x.y 下有类me.class,同时在 com.x.file 目录下有资源文件myfile.xml:me.class.getResourceAsStream("/com/x/file/myfile.xml")
Class.getClassLoader.getResourceAsStream(String path) :默认则是从classPath根下获取,path不能以/开头,因为最终是由ClassLoader获取资源,而ClassLoader又是逐层向上委托的,最后到根类加载器,是C++实现的,它不许以/开头。
ServletContext.getResourceAsStream(String path):默认从WebAPP根目录下取资源,Tomcat下path是否以/开头无所谓。
1.2.3.4. 使用举例
根据构造方法类创建实例:
Constructor<?> con = String.class.getConstructor(char[].class)
(String) con.newInstance({'h','e','l','l','o'})
可以修改字段的可访问性,private也可以访问:
field.setAccessible(true)
field.set(object, "110")
field.get(object)
java.lang.reflect.Modifier.toString(field.getModifiers())
field.getType().getSimpleName()
field.getName()
获取方法的返回类型:
method.getReturnType()
获取方法的形参类型:
method.getParameterTypes()
获取方法的抛出异常列表:
method.getExceptionTypes()
调用方法(如果是静态方法,object就用null代替):
method.invoke(object, 参数, ……)
利用反射创建数组:
1 package com.cjw.learning.reflect; 2 3 import java.lang.reflect.Array; 4 5 public class Demo02 { 6 public static void main(String[] args) { 7 Class<?> clazz = int.class; 8 Object array = Array.newInstance(clazz, 4); 9 Array.set(array, 0, 7); 10 Array.set(array, 1, 9); 11 System.out.println(Array.get(array, 2)); 12 } 13 }
Array类为java.lang.reflect.Array类。
int.class == Integer.class返回false,int.class == Integer.TYPE返回true。
1.3. 解决什么问题
1.3.1. 编写通用DAO类
DAO,即Data Access Object,数据访问对象,乃是用来隔离业务逻辑层和持久层的一个接口、对象,或者说服务。业务逻辑层使用DAO,可以只关注数据应该怎么处理(即怎么组合使用CRUD几个操作),不用关注数据怎么持久化(持久化的过程都由DAO代劳了)。
传统上,对于每一个数据库表,我们会在应用软件端准备一个对应的DAO类访问它,实现的组成结构如下:
一个管理数据库连接的类,可以是连接工厂,可以是连接池。
一个与数据库表对应的POJO类。
一个DAO类,封装了对表的增删改查等数据库访问。
但这有个问题是,数据库若有多张不同的表,我们就需要为每一张表建立对应的DAO。为此,我们可以使用反射只建立一个通用的DAO,用它一个可以代替许多个。但要做到这些,需要遵循一定的规则:
1)数据库的每一个表对应一个POJO类,表中的每一个字段对应POJO类的中的一个属性。并且POJO类的名字和表的名字相同,属性名和字段名相同,大小写没有关系,因为MySQL数据库不区分大小写 。
2)为POJO类中的每一个属性添加标准的set和get方法。
......
编写通用DAO的基本原理是:保存数据时,把需要保存的对象的属性值全部取出来再拼凑sql语句,查询时,将查询到的数据全部包装成一个java对象,......,而能这样也必须定制规则,这是框架的必要代价。 实际上,目前已有许多框架正是这样做的,如ibatis、Spring的模板方法等。
1.3.2. 各种框架如JUnit
好多框架都使用了反射机制,如代码自动生成器、JUnit等。JUnit的工作原理是,在测试运行器中,使用反射获取测试类,并获取注解,根据注解安排测试的执行和顺序控制等。
反射一般会与属性配置文件.properies一起使用,用来增强程序的灵活性。
1.3.3. 反射使用的优缺点
反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性。在J2EE中优势明显,因为他可以不卸载的情况下增加功能,如OSGi规范就应用了这点。
它的缺点是对性能有影响。但仍然可以考虑性能提升的可能:
1)善用API
比如,尽量不要getMethods()后再遍历筛选,而直接用getMethod(methodName)来根据方法名获取方法。
2)使用缓存
需要多次动态创建一个类的实例的时候,有缓存的写法会比没有缓存要快很多
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.2</version>
</dependency>
1 package com.cjw.learning.reflect; 2 3 import com.github.benmanes.caffeine.cache.Cache; 4 import com.github.benmanes.caffeine.cache.Caffeine; 5 6 import java.util.concurrent.TimeUnit; 7 8 public class ClassCache { 9 private Cache<String, Class<?>> cache = 10 Caffeine.newBuilder().maximumSize(100).expireAfterAccess(1, TimeUnit.DAYS).build(); 11 12 public Class<?> getClass(String fullClassName) { 13 return cache.get(fullClassName, clazzName -> { 14 Class clazz = null; 15 try { 16 clazz = Class.forName(clazzName); 17 cache.put(clazzName, clazz); 18 } catch (ClassNotFoundException e) { 19 e.printStackTrace(); 20 } 21 return clazz; 22 }); 23 } 24 }
1.4. 参考资料
http://www.importnew.com/17616.html
http://blog.csdn.net/qq_20745827/article/details/50891606
http://www.cnblogs.com/jqyp/archive/2012/03/29/2423112.html
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.2</version>
</dependency>
package com.cjw.learning.reflect;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class ClassCache {
private Cache<String, Class<?>> cache =
Caffeine.newBuilder().maximumSize(100).expireAfterAccess(1, TimeUnit.DAYS).build();
public Class<?> getClass(String fullClassName) {
return cache.get(fullClassName, clazzName -> {
Class clazz = null;
try {
clazz = Class.forName(clazzName);
cache.put(clazzName, clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clazz;
});
}
}