我们通过前面几篇文章的学习已经了解了类加载机制和类加载器,当一个类被类加载器加载到内存之后,在内存中就会存在一份类的Class实例,这个Class里面包含了类的所有信息。
Class里面的信息即是我们这篇文章要学习的反射。
什么是反射
当类加载器将一个类加载完成后,方法区中就产生了一个Class类型的对象,一个类只有一个Class对象,则个Class对象中包含了完整的类信息,我们可以通过Class对象获取类中的所有信息。这种功能我们就称为”反射“。反射是动态语言的关键。
Class类和 java . lang .reflect类库一起对反射的概念进行了支持,java.lang.reflect类库包含了Field、 method 和 constructor 类,这些类的对象是JVM在运行时创建的,用于表示未知类里对应的成员。
反射的作用
反射的作用主要体现在运行时对象的灵活处理:
- 判断一个对象所属的类
- 获取类的成员变量、方法和 构造器
- 获取类的注解信息
- 动态代理
反射好处多多,当然也是有缺陷的,反射的主要缺陷就是在运行时的性能损耗,但是大部分框架(比如spring)都是加了缓存的,我们无需太过担心啦!
反射API用法
我们首先创建一个Person类:里面包含了成员变量,构造器,方法和注解,并且实现了Serializable接口。
//类注解 | |
@Component | |
public class Person implements Serializable { | |
//成员变量 | |
public String name; | |
protected Integer age; | |
private String sex; | |
//有参构造 | |
public Person(String name, Integer age, String sex) { | |
this.name = name; | |
this.age = age; | |
this.sex = sex; | |
} | |
public Person(String name, Integer age) { | |
this.name = name; | |
this.age = age; | |
} | |
//无参构造 | |
public Person() { | |
System.out.println("这是一个无参构造器"); | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public Integer getAge() { | |
return age; | |
} | |
public void setAge(Integer age) { | |
this.age = age; | |
} | |
public String getSex() { | |
return sex; | |
} | |
public void setSex(String sex) { | |
this.sex = sex; | |
} | |
//方法注解 | |
@Override | |
public String toString () { | |
return "Person{" + | |
"name='" + name + ''' + | |
", age=" + age + | |
", sex='" + sex + ''' + | |
'}'; | |
} | |
//私有方法 | |
@Deprecated | |
private void showAge(Integer age) throws RuntimeException { | |
System.out.println("My age is " + age); | |
} | |
//开放方法 | |
public void showName(String name) { | |
System.out.println("My name is " + name); | |
} | |
} |
Person类被应用类加载器加载之后,就会在方法区中存在唯一的Class对象,那么接下来我们就通过类库的API了解如果获取类结构信息。
Class类是源数据,我们一切的处理都是在获取Class之后。
获取Class的方式有4种 :
1)调用运行时类的属性.class。
2)通过运行时类的对象调用getClass方法。
3)调用Class的静态方法forName,指定类的全限定名。
4)获取类的类加载器,调用方法loadClass,指定类的全限定名。
Class clazz = Person.class; | |
Class clazz = new Person().getClass(); | |
Class clazz = Class.forName("com.study.test.code.girl.base.reflect.Person"); | |
Class clazz = ReflectionTest1.class.getClassLoader().loadClass("com.study.test.code.girl.base.reflect.Person"); | |
System.out.println(clazz); | |
System.out.println(clazz); | |
System.out.println(clazz); | |
System.out.println(clazz); |
上述获取Class的方法得到的都是Person对象。但是除了class之外,还有其他的类型存在Class信息:
//类 | |
Class<Thread> threadClass = Thread.class; | |
//接口 | |
Class<Iterable> iterableClass = Iterable.class; | |
//注解 | |
Class<Override> overrideClass = Override.class; | |
//枚举 | |
Class<EnumTest> enumTestClass = EnumTest.class; | |
//数组 | |
Class<int[]> aClass = int[].class; | |
//基本数据类型 | |
Class<Integer> integerClass = int.class; | |
//void | |
Class<Void> voidClass = void.class; |
获取到Class信息之后,我们就可以使用API操作Class对象啦。
newInstance():创建对应的运行时类对象,内部调用运行时类的无参构造器。
如果找不到无参构造器将抛出异常NoSuchMethodException,如果无参构造器无法访问(比如权限为私有)则抛出异常IllegalAccessException。
所以如果使用newInstance()创建类对象,类必须包含无参构造器,且满足调用权限(一般为public)。
Class<Person> clazz = Person.class; | |
Person person = clazz.newInstance(); | |
System.out.println(person); |
运行结果:
这是一个无参构造器 | |
Person{name='null', age=null, sex='null'} |
getPackage():获取类所在的全限定包路径。
getAnnotations():获取类的注解信息,在切面开发中使用比较多。
Class<Person> clazz = Person.class; | |
/** | |
* 获取所在包 | |
*/System.out.println(clazz.getPackage()); | |
/** | |
* 类注解 | |
*/Annotation[] annotations = clazz.getAnnotations(); | |
Arrays.stream(annotations).forEach(System.out::println); |
运行结果:
package com.study.test.code.girl.base.reflect | |
@org.springframework.stereotype.Component(value=) |
getFields():获取当前运行时类及其所有父类中声明为public权限的属性信息。
getDeclaredFields():获取当前运行类当中声明的所有属性(不限制权限),不包含父类。
getDeclaredField(fileldName):获取指定的属性信息,通过set()或者get()获取或者设置属性值。如果属性权限为private时需要setAccessible(true)再对属性赋值。
我们还可以根据拿到的Field获取属性的名称、权限描述符、数据类型等信息。
Class<Person> clazz = Person.class; | |
/** | |
* 获取运行类属性 | |
*/System.out.println("********************限制权限得到的结果******************************"); | |
Field[] fields = clazz.getFields(); | |
Arrays.stream(fields).forEach(field ->System.out.println(field)); | |
System.out.println("********************不限制权限得到的结果******************************"); | |
Field[] declaredFields = clazz.getDeclaredFields(); | |
Arrays.stream(declaredFields).forEach(field -> | |
{ | |
System.out.print(field); | |
System.out.print(" 权限修饰符为:" + Modifier.toString(field.getModifiers())); | |
System.out.print(" 数据类型为:" + field.getType()); | |
System.out.println(" 属性名称为:" + field.getName()); | |
} | |
); | |
//设置属性信息 | |
Field age = clazz.getDeclaredField("age"); | |
age.setAccessible(true); | |
age.set(person,); | |
age.setAccessible(false); | |
System.out.println(person.toString()); | |
运行结果:
********************限制权限得到的结果****************************** | |
public java.lang.String com.study.test.code.girl.base.reflect.Person.name | |
********************不限制权限得到的结果****************************** | |
public java.lang.String com.study.test.code.girl.base.reflect.Person.name 权限修饰符为:public 数据类型为:class java.lang.String 属性名称为:name | |
private java.lang.Integer com.study.test.code.girl.base.reflect.Person.age 权限修饰符为:private 数据类型为:class java.lang.Integer 属性名称为:age | |
private java.lang.String com.study.test.code.girl.base.reflect.Person.sex 权限修饰符为:private 数据类型为:class java.lang.String 属性名称为:sex | |
Person{name='null', age=, sex='null'} |
getMethods():获取当前运行时类及其所有父类中声明为public权限的方法。
getDeclaredMethods():获取当前运行类当中声明的所有方法(不限制权限),不包含父类。
getDeclaredMethod(methodName,args):获取指定的方法,并指定参数列表,因为你可能重写了方法。invoke()即调用这个方法。
我们可以根据获取的Method获取方法的名称、权限修饰符、方法参数数组、方法注解数组、返回值类型以及方法抛出的异常信息等。
Class<Person> clazz = Person.class; | |
/** | |
* 获取运行类方法 | |
*/System.out.println("********************权限为public的方法******************************"); | |
Method[] methods = clazz.getMethods(); | |
Arrays.stream(methods).forEach(method -> System.out.println(method)); | |
System.out.println("********************所有方法******************************"); | |
Method[] declaredMethods = clazz.getDeclaredMethods(); | |
Arrays.stream(declaredMethods).forEach(method -> { | |
System.out.print(method); | |
System.out.print(" 方法名:" + method.getName()); | |
System.out.print(" 权限修饰符:" + Modifier.toString(method.getModifiers())); | |
System.out.print(" 方法参数:" + Arrays.stream(method.getParameters()).findFirst()); | |
System.out.print(" 方法注解:" + Arrays.stream(method.getAnnotations()).findFirst()); | |
System.out.print(" 返回值类型:" + method.getReturnType()); | |
System.out.println(" 异常信息:" + Arrays.stream(method.getExceptionTypes()).findFirst()); | |
}); | |
//调用方法 | |
Method showAge = clazz.getDeclaredMethod("showAge", Integer.class); | |
showAge.setAccessible(true); | |
showAge.invoke(person, 18); | |
运行结果:(方法结果比较多,抽样展示)
*******************权限为public的方法****************************** | |
public java.lang.String com.study.test.code.girl.base.reflect.Person.toString() | |
public java.lang.String com.study.test.code.girl.base.reflect.Person.getName() | |
public void com.study.test.code.girl.base.reflect.Person.setName(java.lang.String) | |
public java.lang.Integer com.study.test.code.girl.base.reflect.Person.getAge() | |
public void com.study.test.code.girl.base.reflect.Person.setAge(java.lang.Integer) | |
public java.lang.String com.study.test.code.girl.base.reflect.Person.getSex() | |
public void com.study.test.code.girl.base.reflect.Person.showName(java.lang.String) | |
public void com.study.test.code.girl.base.reflect.Person.setSex(java.lang.String) | |
public final void java.lang. Object .wait() throws java.lang.InterruptedException | |
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException | |
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException | |
public boolean java.lang.Object.equals(java.lang.Object) | |
public native int java.lang.Object.hashCode() | |
public final native java.lang.Class java.lang.Object.getClass() | |
public final native void java.lang.Object.notify() | |
public final native void java.lang.Object.notifyAll() | |
********************所有方法****************************** | |
private void com.study.test.code.girl.base.reflect.Person.showAge(java.lang.Integer) throws java.lang.RuntimeException 方法名:showAge 权限修饰符:private 方法参数:Optional[java.lang.Integer age] 方法注解:Optional[ .lang.Deprecated()] 返回值类型:void 异常信息:Optional[class java.lang.RuntimeException] | |
My age is |
getConstructors():获取当前运行时类及其所有父类中声明为public权限的构造器。
getDeclaredConstructors():获取当前运行类当中声明的所有构造器,不包含父类。
我们可以根据Constructor获取构造器的名称、权限修饰符、参数数组、注解、异常信息等。
Class<ArrayList> clazz = ArrayList.class; | |
/** | |
* 构造器 | |
*/Constructor[] constructors = clazz.getConstructors(); | |
Arrays.stream(constructors).forEach(constructor -> System.out.println(constructor)); | |
Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); | |
Arrays.stream(declaredConstructors).forEach(constructor -> { | |
System.out.print(constructor); | |
System.out.print(" 名称:" + constructor.getName()); | |
System.out.print(" 权限修饰符:" + Modifier.toString(constructor.getModifiers())); | |
System.out.print(" 参数:" + Arrays.stream(constructor.getTypeParameters()).findFirst()); | |
System.out.print(" 注解:" + Arrays.stream(constructor.getAnnotations()).findFirst()); | |
System.out.println(" 异常信息:" + Arrays.stream(constructor.getExceptionTypes()).findFirst()); | |
}); | |
//获取指定构造器 | |
Constructor constructor = clazz.getConstructor(String.class, Integer.class); | |
Person person = (Person) constructor.newInstance("JavaCodeGirl", 18); | |
System.out.println(person.toString()); |
运行结果:
********************权限为public的构造器****************************** | |
public com.study.test.code.girl.base.reflect.Person() | |
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer) | |
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer,java.lang.String) | |
********************所有构造器****************************** | |
public com.study.test.code.girl.base.reflect.Person() 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional.empty 注解:Optional.empty 异常信息:Optional.empty | |
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer) 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional[java.lang.String name] 注解:Optional.empty 异常信息:Optional.empty | |
public com.study.test.code.girl.base.reflect.Person(java.lang.String,java.lang.Integer,java.lang.String) 名称:com.study.test.code.girl.base.reflect.Person 权限修饰符:public 参数:Optional[java.lang.String name] 注解:Optional.empty 异常信息:Optional.empty | |
Person{name='JavaCodeGirl', age=, sex='null'} |
以ArrayList为例我们可以获取运行时类的父类信息。
getSuperclass():获取当前运行时类的父类。
getGenericSuperclass():获取当前运行时类的泛型父类。
Class<ArrayList> clazz = ArrayList.class; | |
/** | |
* 获取父类信息 | |
*/ System.out.println("*******************获取所有父类信息******************************"); | |
Class<? super ArrayList> superclass = clazz.getSuperclass(); | |
System.out.println(superclass); | |
System.out.println("*******************获取泛型的父类信息******************************"); | |
Type genericSuperclass = clazz.getGenericSuperclass(); | |
System.out.println(genericSuperclass); | |
运行结果:
*******************获取所有父类信息****************************** | |
class java.util.AbstractList | |
*******************获取泛型的父类信息****************************** | |
java.util.AbstractList<E> |
getInterfaces():取当前运行时类实现的接口。
getGenericInterfaces():当前运行时类实现的泛型接口。
Class<ArrayList> clazz = ArrayList.class; | |
/** | |
* 获取接口信息 | |
*/System.out.println("*******************获取所有接口信息******************************"); | |
Class<?>[] interfaces = clazz.getInterfaces(); | |
Arrays.stream(interfaces).forEach(System.out::println); | |
System.out.println("*******************获泛型接口信息******************************"); | |
Type[] genericInterfaces = clazz.getGenericInterfaces(); | |
Arrays.stream(genericInterfaces).forEach(System.out::println); | |
运行结果:
*******************获取所有接口信息****************************** | |
interface java.util.List | |
interface java.util.RandomAccess | |
interface java.lang.Cloneable | |
interface java.io.Serializable | |
*******************获泛型接口信息****************************** | |
java.util.List<E> | |
interface java.util.RandomAccess | |
interface java.lang.Cloneable | |
interface java.io.Serializable | |
学完这些API,我们平时工作中是怎么使用的呢?勾勾给大家描述一个场景,看看你会怎么做。
反射实战
在一个请求中,我们需要针对不同的类型(type)做不同的业务处理,如果你做开发会怎么设计呢?
策略+模板方法,我猜你肯定会这么说。
没错,勾勾也是这么用的,那么此时就有一个问题:你是如何根据type找到对应的策略呢?千万不要用if…else。
勾勾是这么做的:
1)自定义注解;
@Target(ElementType.TYPE) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface BizDealAnnotation { | |
String value(); | |
} |
2)策略类添加注解,定义唯一的value值。
@Slfj | |
@BizDealAnnotation("stock_sale") | |
@Component | |
public class StockSaleHandler extends AbstractHandler { | |
} |
3)项目启动时,根据注解扫描得到value值和类信息,将这两个值分别作为key和value存储到map中。
Map<String, Object> dealBeans = applicationContext.getBeansWithAnnotation(BizDealAnnotation.class); | |
// 被事务代理,获取目标类上的注解信息 | |
for (Object serviceObject : dealBeans.values()) { | |
Class<?> targetClass = AopUtils.getTargetClass(serviceObject); | |
//做一些规范性的判断 | |
if (StandardDealService.class.isAssignableFrom(targetClass) && !Modifier.isAbstract(targetClass.getModifiers())) { | |
StandardDealService dealService = (StandardDealService) serviceObject; | |
//通过反射获取指定的注解 | |
BizDealAnnotation annotation = targetClass.getAnnotation(BizDealAnnotation.class); | |
if (annotation != null) { | |
String value = annotation.value(); | |
//map存值 | |
StandardDealServiceRegister.addDealBean(value, dealService); | |
} | |
} | |
} |
4)使用时通过type找到map中对应的策略类。
总结
反射即是根据Class获取类的结构信息,我们可以使用Constructor创建新的对象,使用get()和set()方法读取和修改和Field对象关联的字段信息,用invoke方法调用与Method对象关联的方法。
当通过反射与未知对象通信时,JVM必须已经加载了这个类的Class对象。
反射的API比较简单,大家学完可以发现它的规律,在开发中使用最多是注解,大家重点掌握即可。
我是勾勾,一直在努力的程序媛,感谢您的点赞、转发和关注。
我们下篇文章见!