最近用到了动态代理, 在Spring框架中也大量使用了反射来完成Ioc和AOP. 对于反射一直也都是使用, 也没怎么系统的学习. 这篇文章就系统的总结一下在Java中反射的相关机制!
Github源码: https://github.com/JasonkayZK/Java_Samples/tree/java-reflection
Java反射
1. 反射概述
在运行过程中:
- 对于任意一个类: 都能够知道这个类的所有属性和方法;
- 对于任意一个对象: 都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
实际上, 我们创建的每一个类也都是对象! 即类本身是java.lang.Class类
的实例对象, 被称为类对象!
2. Class对象特点
Class类的API如下图所示:
从图中可以得出以下几点:
- Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口; 也就是jvm中有很多的实例,每个类都有唯一的Class对象;
- Class 类没有公共构造方法, Class 对象是在加载类时由 Java 虚拟机自动构造的. 也就是说我们不需要创建,JVM已经帮我们创建了;
- Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法;
3. 反射的使用
假设有一个JavaBean: Hero类
package reflection.pojo;
public class Hero {
public String name;
public double hp;
protected double armor;
public int speed;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHp() {
return hp;
}
public void setHp(double hp) {
this.hp = hp;
}
public double getArmor() {
return armor;
}
public void setArmor(double armor) {
this.armor = armor;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
", hp=" + hp +
", armor=" + armor +
", speed=" + speed +
'}';
}
}
1): 获取类对象
获取类对象的方法有3种:
- Class.forName() [常用]
- Hero.class
- new Hero().getClass()
在一个JVM中, 同一个ClassLoader引导创建的类, 只会有一个类对象存在!
所以对于上述三个方法来说, 都是使用的AppClassLoader
引导创建的, 所以产生的类对象都相同:
package reflection.chapter3.getClass;
import reflection.pojo.Hero;
public class GetClassDemo {
public static void main(String[] args) {
String className = "reflection.pojo.Hero";
try {
Class clazz1 = Class.forName(className);
Class clazz2 = Hero.class;
Class clazz3 = new Hero().getClass();
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出为:
true
true
三种方法比较:
- 使用
Class.forName()
静态方法最常用! - 使用
Hero.class方法
需要导入对于的类的包! - 使用
new方法
已经直接new出了对象, 一般不再需要反射了!
一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法.
2): 利用反射创建对象
与传统的通过new来获取对象的方法不同: 反射会先拿到Hexo的”类对象”, 然后通过类对象获取”构造器对象”, 再通过构造器对象创建一个对象!
如, 使用默认的构造器方法构造对象:
/*
1.获取类对象 Class clazz = Class.forName("reflection.pojo.Hero");
2.获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
3 获取对象 Hero hero =con.newInstance(实参);
*/
package reflection.chapter4.constructObject;
import reflection.pojo.Hero;
import java.lang.reflect.Constructor;
public class DefaultConstructor {
public static void main(String[] args) {
try {
Class clazz = Class.forName("reflection.pojo.Hero");
Constructor constructor = clazz.getConstructor();
Hero hero = (Hero) constructor.newInstance();
System.out.println(hero);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出为:
Hero{name='null', hp=0.0, armor=0.0, speed=0}
当Hero的构造方法不是无参构造方法的时候: 需要先获取对应的构造器方法!
如: 获取构造函数构造对象
1.Hero类中添加构造方法
//---------------构造方法-------------------
//(默认的构造方法)
Hero(String str){
System.out.println("(默认)的构造方法 s = " + str);
}
//无参构造方法
public Hero(){
System.out.println("调用了公有、无参构造方法执行了。。。");
}
//有一个参数的构造方法
public Hero(char name){
System.out.println("姓名:" + name);
}
//有多个参数的构造方法
public Hero(String name ,float hp){
System.out.println("姓名:"+name+"血量:"+ hp);
}
//受保护的构造方法
protected Hero(boolean n){
System.out.println("受保护的构造方法 n = " + n);
}
//私有构造方法
private Hero(float hp){
System.out.println("私有的构造方法 血量:"+ hp);
}
2.通过反射机制获取对象
package reflection.chapter4.constructObject;
import reflection.pojo.Hero;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class SelectConstructor {
/**
*
* 通过Class对象可以获取某个类中的: 构造方法, 成员变量, 成员方法;
*
* 并访问成员.
*
* 1.获取构造方法:
* 1).批量的方法:
* public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
* 2).获取单个的方法,并调用:
* public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
* public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
*
* 2.创建对象
* Constructor对象调用newInstance(Object... initargs)
*
*/
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, ClassNotFoundException {
// 1. 获取Class对象
Class clazz = Class.forName("reflection.pojo.Hero");
// 2. 获取构造方法
System.out.println("----公有构造方法----");
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("----所有的构造方法(包括:私有、受保护、默认、公有)----");
constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("----获取公有、无参的构造方法----");
// 1> 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型!!!!!
// 2> 返回的是描述这个无参构造函数的类对象.
Constructor cons = clazz.getConstructor(null);
System.out.println("consturctor = " + cons);
// 调用方法
Object object = cons.newInstance();
System.out.println("Object: " + (Hero)object);
System.out.println("----获取私有构造方法,并调用----");
cons = clazz.getDeclaredConstructor(float.class);
System.out.println("consturctor = " + cons);
// 调用方法
cons.setAccessible(true);
object = cons.newInstance(100);
System.out.println("Object: " + (Hero)object);
}
}
输出为:
----公有构造方法----
public reflection.pojo.Hero()
public reflection.pojo.Hero(char)
public reflection.pojo.Hero(java.lang.String,float)
----所有的构造方法(包括:私有、受保护、默认、公有)----
reflection.pojo.Hero(java.lang.String)
public reflection.pojo.Hero()
public reflection.pojo.Hero(char)
public reflection.pojo.Hero(java.lang.String,float)
protected reflection.pojo.Hero(boolean)
private reflection.pojo.Hero(float)
----获取公有、无参的构造方法----
consturctor = public reflection.pojo.Hero()
调用了公有、无参构造方法执行了...
Object: Hero{name='null', hp=0.0, armor=0.0, speed=0}
----获取私有构造方法,并调用----
consturctor = private reflection.pojo.Hero(float)
私有的构造方法 血量:100.0
Object: Hero{name='null', hp=0.0, armor=0.0, speed=0}
总结:
获取构造器批量的方法:
public Constructor[] getConstructors():
所有”公有的”构造方法
public Constructor[] getDeclaredConstructors():
获取所有的构造方法(包括私有、受保护、默认、公有)
获取构造器单个的方法:
public Constructor getConstructor(Class… parameterTypes):
获取单个的”公有的”构造方法
public Constructor getDeclaredConstructor(Class…parameterTypes):
获取”某个构造方法”可以是私有的,或受保护、默认、公有;
3): 获取成员变量并使用
基本步骤:
- 获取对象: 通过new或者反射获得对象;
- 获取属性: Field f1 = hero.getDeclaredField(“属性名”)
- 修改属性: f1.set(hero, 实参) 此处为对象, 而不是类对象!!!
例1: 获取并修改属性
package reflection.chapter5.param;
import reflection.pojo.Hero;
import java.lang.reflect.Field;
public class GetAndModifyParamDemo {
public static void main(String[] args) {
Hero hero = new Hero();
try {
// 获取hero的叫做name字段的属性
Field field = hero.getClass().getDeclaredField("name");
// 修改属性
field.set(hero, "teemo");
System.out.println(hero);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
输出为:
Hero{name='teemo', hp=0.0, armor=0.0, speed=0}
补充: getField和getDeclaredField的区别
getField: 只能获取public的,包括从父类继承来的字段;
getDeclaredField: 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段.
(注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))
4): 获取成员方法并使用
基本步骤:
获取对象;
获取成员方法:
public Method getMethod(String name ,Class<?>… parameterTypes)
: 获取”公有方法”; (包含了父类的方法也包含Object类)public Method getDeclaredMethods(String name ,Class<?>… parameterTypes)
: 获取成员方法,包括私有的(不包括继承的)
参数解释:
- name : 方法名;
- Class … : 形参的Class类型对象
调用方法:
Method –> public Object invoke(Object obj,Object… args)
参数说明:- obj: 要调用方法的对象;
- args: 调用方式时所传递的实参;
*实例: *
package reflection.chapter6.method;
import reflection.pojo.Hero;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InvokeMethodDemo {
public static void main(String[] args) {
Hero hero = new Hero();
Hero heroSet = new Hero();
try {
// 获取方法
Method method = hero.getClass().getMethod("setName", String.class);
// 对heroSet调用反射方法!
method.invoke(heroSet, "Garon");
// 对hero调用常规方法
hero.setName("Teemo");
System.out.println("hero: " + hero);
System.out.println("heroSet: " + heroSet);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果为:
hero: Hero{name='Teemo', hp=0.0, armor=0.0, speed=0}
heroSet: Hero{name='Garon', hp=0.0, armor=0.0, speed=0}
关于Java方法反射的源码实现的分析: 深入分析Java方法反射的实现原理
5): 获取main方法并使用
*例: *
在Hero中添加main方法:
public static void main(String[] args) {
System.out.println("执行main方法");
for (String arg : args) {
System.out.println(arg);
}
}
通过反射获取main方法, 并执行:
package reflection.chapter7.main;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InvokeMainDemo {
public static void main(String[] args) {
try {
// 1. 获取Class对象
Class clazz = Class.forName("reflection.pojo.Hero");
// 2. 获取main方法
Method mainMethod = clazz.getMethod("main", String[].class);
// 3. 调用main方法
/*
1. 错误调用
mainMethod.invoke(null, new String[] {"a", "b", "c"});
首先,
第一个参数: 对象类型, 当方法是静态方法时, 可以为null!
第二个参数: String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
上述方法会报错:
这里拆的时候会将 new String[]{"a","b","c"} 拆成3个对象!!!
所以需要将它强转!!!
*/
mainMethod.invoke(null, (Object)new String[] {"a", "b", "c"}); // 方法一
mainMethod.invoke(null, new Object[] {new String[] {"a", "b", "c"}}); // 方法二
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
执行main方法
a
b
c
4. 反射的应用:
1): 通过反射读取并运行配置文件内容
首先准备两个业务类:
package reflection.chapter8.settings.service;
public class Service1 {
public void doService1() {
System.out.println("Service 1");
}
}
package reflection.chapter8.settings.service;
public class Service2 {
public void doService2() {
System.out.println("Service 2");
}
}
此时如果需要讲业务方法一切换为业务方法二时, 如果使用非反射方式: 必须修改源代码, 然后重新编译, 运行才可以!
如:
package reflection.chapter8.settings;
import reflection.chapter8.settings.service.Service1;
import reflection.chapter8.settings.service.Service2;
/**
* 不使用反射时, 需要修改源代码, 并重新编译!!!
*/
public class CommonDemo {
public static void main(String[] args) {
// new Service1().doService1();
// 想要使用service2, 必须修改源码!
new Service2().doService2();
}
}
如果使用反射, 将会方便的多!
首先准备一个配置文件, 如: reflection.properties;
文件存放的是类的名称,和要调用的方法名.
如:
class=reflection.chapter8.settings.service.Service2
method=doService2
- 测试类中,首先取出类名称和方法名,然后通过反射去调用这个方法;
- 当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件,再运行即可
例如: 测试类
package reflection.chapter8.settings;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectSettingsDemo {
public static void main(String[] args) {
Properties properties;
InputStream in = null;
try {
properties = new Properties();
in = ReflectSettingsDemo.class.getClassLoader().getResourceAsStream("reflection.properties");
properties.load(in);
String className = properties.getProperty("class");
String methodName = properties.getProperty("method");
// 根据配置类名寻找类对象
Class clazz = Class.forName(className);
// 根据方法名, 寻找方法对象
Method method = clazz.getMethod(methodName);
// 获取默认无参构造器
Constructor constructor = clazz.getConstructor();
// 根据构造器, 实例化对象, 并调用指定方法!
method.invoke(constructor.newInstance());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
最后读取配置文件后输出:
Service 2
将配置文件修改为:
class=reflection.chapter8.settings.service.Service1
method=doService1
输出为
Service 1
2): 通过反射越过泛型检查
泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型!
package reflection.chapter9.genericType;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class AviodGenericTypeCheckDemo {
@SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
list.add("this");
list.add("is");
// List.add(5) 编译报错!
/* 越过泛型检查! */
// 获取ArrayList的Class对象, 反射调用add()
Class listClazz = list.getClass();
// 获取add()方法
Method method = listClazz.getMethod("add", Object.class);
method.invoke(list, 5);
for (Object obj : list) {
System.out.println(obj);
}
}
}
正常情况下, 由于声明的泛型类型为String, 而向其中加入的是Integer类型, 所以编译器在检查期将报错!
而使用了反射之后, 通过反射调用add()方法
将越过编译器类型检查, 而成功加入
最终执行会输出结果:
this
is
5
附录
Github源码: https://github.com/JasonkayZK/Java_Samples/tree/java-reflection
*引用: *