反射
反射是框架设计的灵魂
一、类的加载时机
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载 :就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
- 连接:验证 是否有正确的内部结构,并和其他类协调一致。准备 负责为类的静态成员分配内存,并设置默认初始化值。
- 初始化:初始化成员变量等等。
加载时机
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 初始化某个类的子类
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
二、类加载器
什么是类加载器classLoader
负责将 .class文件加载到内存中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
类加载器分类
(1)根类加载器
- 也被称为引导类加载器,负责Java核心类的加载
- 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
(2)扩展类加载器
- 负责JRE的扩展目录中jar包的加载。
- 在JDK中JRE的lib目录下ext目录
(3)系统类加载器
- 负责在JVM启动时加载来自java命令的class文件
- 以及classpath环境变量所指定的jar包和类路径
三、什么是反射
创建一个对象的三个阶段
- 源文件阶段 .java的文件
- 字节码阶段 .class
- 创建对象阶段 new 对象名称
内省
在运行时能够获取JavaBean当中的属性名称和get与set方法
反射
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
- 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 想要使用反射,就必须得要获取字节码文件。
四、反射机制的相关类
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
(1)Class类
- 获得类相关的方法
方法 | 用途 |
---|---|
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
asSubclass(Class clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
- 获得类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
- 获得类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
- 获得类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
- 获得类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
- 类中其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回true |
isAnonymousClass() | 如果是匿名类则返回true |
isArray() | 如果是一个数组类则返回true |
isEnum() | 如果是枚举类则返回true |
isInstance(Object obj) | 如果obj是该类的实例则返回true |
isInterface() | 如果是接口类则返回true |
isLocalClass() | 如果是局部类则返回true |
isMemberClass() | 如果是内部类则返回true |
(2)Field类
方法 | 用途 |
---|---|
equals(Object obj) | 属性与obj相等则返回true |
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
(3)Method类
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
(4)Constructor类
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
五、获取字节码文件
1. Object类的getClass()方法
对象.getClass()
多用于对象的获取字节码的方式
2. 静态属性class
类名.class
多用于参数的传递
3. Class类中静态方法forName()
class.forName("全类名")
多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
注意:在运行期间,一个类,只有一个Class对象产生。
e.g.
class Person { String name; Integer age; Person() { } Person(String name, Integer age) { this.name = name; this.age = age; } } public class Test { public static void main(String[] args) throws ClassNotFoundException { Class clazz1 = Class.forName("Test.Person"); Class clazz2 = Person.class; Class clazz3 = new Person().getClass(); System.out.println(clazz1 == clazz2); // true System.out.println(clazz2 == clazz3); // true System.out.println(clazz1 == clazz3); // true } }
六、获取构造器
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
获取构造器后可执行操作
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
1. 获取公有的构造器
class Person { String name; Integer age; Person() { } private Person(String name) { super(); this.name = name; } public Person(String name, Integer age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Constructor<?>[] cons = clazz.getConstructors(); for (Constructor<?> con : cons) { System.out.println(con); } System.out.println("--------"); Constructor<?> con = clazz.getConstructor(String.class, Integer.class); System.out.println(con); Person p = (Person) con.newInstance("zsy", 20); System.out.println(p); } }
运行结果
public Test.Person(java.lang.String,java.lang.Integer) -------- public Test.Person(java.lang.String,java.lang.Integer) Person [name=zs, age=20]
2. 获取私有的构造器
class Person { String name; Integer age; Person() { } private Person(String name) { super(); this.name = name; } public Person(String name, Integer age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Constructor<?>[] cons = clazz.getDeclaredConstructors(); for (Constructor<?> con : cons) { System.out.println(con); } System.out.println("--------"); Constructor<?> con = clazz.getDeclaredConstructor(String.class); System.out.println(con); Person p = (Person) con.newInstance("zs"); System.out.println(p); } }
运行结果
public Test.Person(java.lang.String,java.lang.Integer) private Test.Person(java.lang.String) Test.Person() -------- private Test.Person(java.lang.String) 抛出java.lang.IllegalAccessException
会发现在使用私有构造器时会报异常
- 使用暴力反射,忽略访问权限修饰符的安全检查
class Person { String name; Integer age; Person() { } private Person(String name) { super(); this.name = name; } public Person(String name, Integer age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Constructor<?>[] cons = clazz.getDeclaredConstructors(); for (Constructor<?> con : cons) { System.out.println(con); } System.out.println("--------"); Constructor<?> con = clazz.getDeclaredConstructor(String.class); System.out.println(con); con.setAccessible(true); Person p = (Person) con.newInstance("zs"); System.out.println(p); } }
运行结果
public Test.Person(java.lang.String,java.lang.Integer) private Test.Person(java.lang.String) Test.Person() -------- private Test.Person(java.lang.String) Person [name=zs, age=null]
3. 获取无参构造器
class Person { String name; Integer age; public Person() { } public Person(String name, Integer age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Constructor<?> con = clazz.getConstructor(); System.out.println(con); // public Test.Person() Person p = (Person) con.newInstance(); System.out.println(p); // Person [name=null, age=null] } }
获取无参构造器时,可以简化为
public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Person p = (Person) clazz.newInstance(); System.out.println(p); // Person [name=null, age=null] } }
七、获取字段
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
获取字段后可执行操作
方法 | 用途 |
---|---|
get(Object obj) | 获得obj中对应的属性值 |
set(Object obj, Object value) | 设置obj中对应属性值 |
1. 获取公有的字段
直接调用 clazz.getField("字段名称")
class Person { public String name; public Integer age; protected String a; String b; private String c; Person() { } Person(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Field[] fields = clazz.getFields(); for (Field field : fields) { System.out.println(field); } System.out.println("--------"); Field name = clazz.getField("name"); Field age = clazz.getField("age"); System.out.println(name); System.out.println(age); System.out.println("--------"); Person p = (Person) clazz.newInstance(); name.set(p, "zs"); System.out.println(name.get(p)); } }
运行结果:
public java.lang.String Test.Person.name public java.lang.Integer Test.Person.age -------- public java.lang.String Test.Person.name public java.lang.Integer Test.Person.age -------- zs
2. 获取私有的字段
class Person { public String name; public Integer age; protected String a; String b; private String c; Person() { } Person(String name, Integer age) { this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { System.out.println(field); } System.out.println("--------"); Field name = clazz.getDeclaredField("name"); Field age = clazz.getDeclaredField("age"); Field a = clazz.getDeclaredField("a"); Field b = clazz.getDeclaredField("b"); Field c = clazz.getDeclaredField("c"); System.out.println(name); System.out.println(age); System.out.println(a); System.out.println(b); System.out.println(c); System.out.println("--------"); Person p = (Person) clazz.newInstance(); name.set(p, "zs"); System.out.println(name.get(p)); a.set(p, "a"); System.out.println(a.get(p)); b.set(p, "b"); System.out.println(b.get(p)); c.set(p, "c"); System.out.println(c.get(p)); } }
运行结果:
public java.lang.String Test.Person.name public java.lang.Integer Test.Person.age protected java.lang.String Test.Person.a java.lang.String Test.Person.b private java.lang.String Test.Person.c -------- public java.lang.String Test.Person.name public java.lang.Integer Test.Person.age protected java.lang.String Test.Person.a java.lang.String Test.Person.b private java.lang.String Test.Person.c -------- zs a b 抛出java.lang.IllegalAccessException
会发现在调用和设置私有字段时会报异常
- 使用暴力反射,忽略访问权限修饰符的安全检查
class Person { private String c; Person() { } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Field c = clazz.getDeclaredField("c"); System.out.println(c); // private java.lang.String Test.Person.c Person p = (Person) clazz.newInstance(); c.setAccessible(true); c.set(p, "c"); System.out.println(c.get(p)); // C } }
八、获取方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
获取方法后可执行操作
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递object对象及参数调用该对象对应的方法 |
1. 获取公有的方法
class Person { String name; public void eat(String str) { System.out.println(name + "在吃" + str); } public void work() { System.out.println(name + "在工作"); } private void walk() { System.out.println(name + "在行走"); } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Person p = (Person) clazz.newInstance(); p.name = "张三"; Method method = clazz.getMethod("work"); method.invoke(p); // 张三在工作 } }
2. 获取私有的方法
类似的,使用暴力反射,忽略访问权限修饰符的安全检查
class Person { String name; public void eat(String str) { System.out.println(name + "在吃" + str); } public void work() { System.out.println(name + "在工作"); } private void walk() { System.out.println(name + "在行走"); } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Person p = (Person) clazz.newInstance(); p.name = "张三"; Method method = clazz.getDeclaredMethod("walk"); method.setAccessible(true); method.invoke(p); // 张三在行走 } }
3. 获取有参的方法
class Person { String name; public void eat(String str) { System.out.println(name + "在吃" + str); } } public class Test { public static void main(String[] args) throws Exception { // 1. 获取字节码 Class<?> clazz = Class.forName("Test.Person"); Person p = (Person) clazz.newInstance(); p.name = "张三"; Method method = clazz.getMethod("eat", String.class); method.invoke(p, "苹果"); // 张三在吃苹果 } }
九、反射实例
设计一个可以创建任意类的对象,执行此对象中任意方法的框架。
public class Student { public String name = "zs"; public void study() { System.out.println(name + "在学习"); } public void eat(String str) { System.out.println(name + "在吃" + str); } public void parents(String father, String mother) { System.out.println(name + "的父亲是:" + father); System.out.println(name + "的母亲是:" + mother); } }
public class Test { public static void main(String[] args) throws Exception { Properties props = new Properties(); InputStream ins = Test.class.getClassLoader().getResourceAsStream("Test/pro.properties"); props.load(ins); ins.close(); String className = props.getProperty("className"); String methodName = props.getProperty("methodName"); String methodArgs = props.getProperty("methodArgs"); String[] argList = methodArgs.isEmpty() ? null : methodArgs.split(","); Class<?> clazz = Class.forName(className); Object object = clazz.newInstance(); Method[] methods = clazz.getMethods(); for (Method method : methods) { if(method.getName().equals(methodName)) { method.invoke(object, argList); } } } }
className=Test.Student methodName=parents methodArgs=ls,ww
加载配置文件
配置文件放置位置
一、通过文件路径加载
该方式必须知道文件的真实路径。
public class Test { public static void main(String[] args) throws Exception { String aName = getProperties("a.properties"); String bName = getProperties("src/b.properties"); String cName = getProperties("src/Test/file/c.properties"); System.out.print(aName + "\n" + bName + "\n" + cName); } public static String getProperties(String path) throws Exception { InputStream ins = new FileInputStream(path); Properties props = new Properties(); props.load(ins); ins.close(); String className = props.getProperty("className"); return className; } }
二、通过getResourceAsStream加载
(1)获取src下的指定资源
Class.getResourceAsStream(String path)
path 不以’/'开头时默认是从此类所在的包下取资源
path 以’/'开头则是从ClassPath根下(即'/'代表src)获取
其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
a.在同级目录下
有类me.class,同时在同级目录下有资源文件 myfile.properties,则应使用:me.class.getResource("myfile.properties");
b.在子目录下
com.x.y 下有类me.class,同时在子目录 com.x.y.file 下有资源文件 myfile.properties,则应使用:me.class.getResource("file/myfile.properties");
c.不在同级目录和子目录下
com.x.y 下有类me.class,同时在 com.x.file 目录下有资源文件 myfile.properties ,则应使用:me.class.getResource("/com/x/file/myfile.properties");
public class Test { public static void main(String[] args) throws Exception { //String aName = getProperties("a.properties"); // 这里a不在src下,不能获取 String bName = getProperties("../b.properties"); // 相对路径 String cName = getProperties("/Test/file/c.properties"); // 绝对路径 System.out.print(bName + "\n" + cName); } public static String getProperties(String path) throws Exception { // getResource 获取资源的绝对路径 URL url = Test.class.getResource(path); System.out.println(url); // getResourceAsStream 获取资源的字节流 InputStream ins = Test.class.getResourceAsStream(path); Properties props = new Properties(); props.load(ins); ins.close(); String className = props.getProperty("className"); return className; } }
(2)获取web项目下的指定资源
ServletContext.getResourceAsStream(String path)
默认从WebAPP根目录(即:要发布在服务器下的项目的根目录(与src同级的web文件夹下))下取资源
path是否以’/'开头无所谓。 例:
在web项目的根目录下有myfile.xml文件,则应该使用
getServleContext().getResourceAsStream("myfile.xml");
三、通过类加载的方式进行加载
Class.getClassLoader().getResourceAsStream(String path)
- 默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
a.不在同级目录和子目录下
com.x.y 下有类me.class,同时在 com.x.file 目录下有资源文件 myfile.properties ,则应使用:me.class.getClassLoader().getResourceAsStream("com/x/file/myfile.properties");
public class Test { public static void main(String[] args) throws Exception { //String aName = getProperties("a.properties"); // 这里a不在src下,不能获取 String bName = getProperties("b.properties"); String cName = getProperties("Test/file/c.properties"); System.out.print(bName + "\n" + cName); } public static String getProperties(String path) throws Exception { // getResource 获取资源的绝对路径 URL url = Test.class.getClassLoader().getResource(path); System.out.println(url); // getResourceAsStream 获取资源的字节流 InputStream ins = Test.class.getClassLoader().getResourceAsStream(path); Properties props = new Properties(); props.load(ins); ins.close(); String className = props.getProperty("className"); return className; } }
四、通过基名
文件必须是 key=value 的properties文件
public class Test { public static void main(String[] args) throws Exception { //String aName = getProperties("a"); // 这里a不在src下,不能获取 String bName = getProperties("b"); String cName = getProperties("Test/file/c"); System.out.print(bName + "\n" + cName); } public static String getProperties(String path) throws Exception { ResourceBundle bundle = ResourceBundle.getBundle(path); String className = bundle.getString("className"); return className; } }