Java ”框架 = 注解 + 反射 + 设计模式“ 之 反射详解
每博一文案
无论幸福还是苦难,无论光荣还是屈辱,你都要自己遭遇与承受。
—————— 《平凡的世界》 孙少平
多少美好的东西消失和毁灭了,世界还像什么事也没有发生,是的,生活在继续着。
可是,生活中的每一个却在不断地失去自己最珍贵的东西。
生活永远是美好的;人的痛苦却时时在发生。
—————— 《平凡的世界》
我时常在想:第二天天一亮,一切都是新的。
—————— 《平凡的世界》
文章目录
1. 反射的概念
- Reflection (反射)是被视为 动态语言的关键 ,反射机制允许程序在执行期借助 Reflection API 取得任何类的内部信息,并能直接操作任意对象内部属性以及方法。
- 反射机制有什么用 ???
- 通过 Java语言中的反射机制可以操作字节码文件,优点类似于黑客。(可以读和修改字节码文件)
- 在Java中加载完类之后,会在堆内存的方法区中就产生一个 Class 类型的对象(一个类只有一个 Class 对象(因为类在内存当中只会生产一份,一个类对应的
.class
字节码文件,所有对象共用,这仅用的一份)),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为 : 反射。
- 反射机制的相关类在 **
java.lang.reflect.*
**包下- java.lang.Class : 代表整个字节码,代表一个类型,代表整个类。
- java.lang.reflect.Constructor : 代表字节码中的方法字节码,代表类中的方法。
- java.lang.reflect.Field : 代表字节码中的构造方法字节码,代表类中的构造方法。
- java.lang.reflect.Field : 代表字节码中的属性字节码,代表类中的成员变量(静态变量 + 实例变量)。
public class User {
// 这个是时属性 Field
int no;
public User(){
}
// 这个是构造器 Constructor
public User (int no) {
}
// 这个是方法 Method()
public void setNo(int no) {
}
public int getNo() {
return no;
}
}
2. 动态语言 vs 静态语言
- 动态语言:
动态语言: 是一类在运行时可以改变其结构的语言:例如:新的函数,对象,甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件自身结构 。
主要动态语言: Object-C,C#,JavaScript,PHP,Python,Erlang
- 静态语言:
与动态语言相应的,运行时结构不可变的语言就是静态语言。如:Java,C,C++
Java不是动态语言,但Java可以称之为 ”准动态语言“。即 Java有一定的动态性,我们可以利用反射机制,字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活!
3. Class 类
除了int
等基本类型外,Java的其他类型全部都是class
(包括interface
)。例如:
String
Object
Runnable
Exception
interface
接口enum
枚举annotaion
注解void
[]
数组class 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- …
- Class 本身也是一个类。注意是 大写的 C ,小写的表示的就成了 class 关键字了。
- Class 对象只能由系统建立对象。
- 一个加载的类 在 JVM 中只会有一个 Class 实例,因为 一个 Class 对象对应的是一个加载到 JVM 中的一个
.class
字节码文件,一个类只会加载一份对应的.class
字节码文件,所有这个类的对象共用。 - 每个类的的实例都会记得自己是由哪个 Class 实例所生成的。
- 通过 Class 可以完整地得到一个类中的所有被加载的结构。
- Class 类是 Reflection 的根源,针对任何你想要:动态加载,运行的类,唯有先获得相应的 Class 对象为前提。
4. Class 类中常用的方法
Class 这里类是没有公开的构造器的,所以是无法通过 new 的方式创建该 Class 对象的。
方法名 | 作用 |
---|---|
public static Class<?> forName(String className) throws ClassNotFoundException | 返回指定类名 name(全类名)的 Class 对象 |
public T newInstance() throws InstantiationException, IllegalAccessException | 调用无参构造器,返回该 Class 中对应的类的对象实例。等同于 new 对象 注意该方法并不会创建对应类中的实例对象,但是会调用该类中的静态代码块 |
public String getName() | 返回此 Class 对象所表示的实体(类,接口,数组类,基本数据类型或 void) 的名称 |
public Class<? super T> getSuperclass() | 返回当前Class 对象的父类的 Class 对象 |
public Class<?>[] getInterfaces() | 获取当前Class 对象的接口 |
public ClassLoader getClassLoader() | 返回该类的类加载器 |
public Constructor<?>[] getConstructors() throws SecurityException | 返回该类中的所有的构造器存储到 (Constructor[ ])对象的数组中 |
public Method[] getDeclaredMethods() throws SecurityException | 返回该类中的所有方法存储到Method[ ]对象数组 |
public Field[] getDeclaredFields() throws SecurityException | 返回该类中所有的属性存储到 Field[ ]对象数组中 |
5. 获取Class类的实例(三种方式)
5.1 方式一:
前提: 若已知具体的类,通过类的 class
属性获取,该方法最为安全可靠, 程序性能最高。任何类型(除了基本数据类型不行,但是可以用对应的包装类代替)都可以 .class
举例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
// <User> 泛型
Class<User> userClass = User.class;
System.out.println(userClass);
// <String> 泛型
Class<String> stringClass = String.class;
System.out.println(stringClass);
//这里自动装箱了
Class integerClass = int.class;
System.out.println(integerClass);
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
5.2 方式二:
前提: 已知某个类的实例,调用该实例的 getClass()
方法获取 Class
对象。
举例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
User user = new User();
// <? extends User >上界通配符
Class<? extends User> aClass = user.getClass();
System.out.println(aClass);
String s = new String();
Class<? extends String> aClass2 = s.getClass();
System.out.println(aClass2);
Integer integer = new Integer(123);
Class aClass3 = integer.getClass();
System.out.println(aClass3);
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
5.3 方式三:重点 ⭐⭐⭐
前提: 已知一个类的全类名,且该类在类路径下,可通过 Class类的静态方 法forName()
获取,可能抛出ClassNotFoundException
该类不存在异常
全类名 : 指的是将包含一个类的包名,比如:java.lang.String
,blogs.blog11.User
这样含有指定包下的类名。
这里使用 fonName(全类名) 的原因是:不同的包下,会存在同名的类名,这样Java程序无法确定你需要的是哪个包下的类了。所以需要指定包下的类。
可以使用 IDEA 工具,快速复制获取到一个类的:全类名,如下:
举例:
package blogs.blog11;
public class ReflectTest01 {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("blogs.blog11.User");
System.out.println(clazz);
Class<?> clazz2 = Class.forName("java.lang.String");
System.out.println(clazz2);
Class clazz3 = Class.forName("java.lang.Integer");
System.out.println(clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
这第三种方式还又一个好处: 会自动调用该类中的静态代码块,因为静态代码块,是和类一起加载到内存当中的,当类加载到内存时,该静态代码就会自动执行其中的语句,仅仅只会执行一次,因为类也仅仅只会加载到内存一次。而 Class.forName() 类/静态方法,就是加载对应类的。该方法并不会创建该类的实例化对象,而是将该类加载到内存当中。
如果你只想让一个类的 “静态代码块” 执行的话,你可以怎么做?
class.forName(“该类的类名”);
这样类就加载,类加载的时候,静态代码块执行!!!应用实例:JDBC 中加载对应数据库的驱动,执行其中的静态代码块。
举例: 如下,我们可以看到,其中的构造器并没有被调用(也就没有创建实例对象了,),但是静态代码块执行了。
package blogs.blog11;
/**
* 获取Class 对象的三种方式
*/
public class ReflectTest01 {
public static void main(String[] args) {
// 会执行blogs.blog11.User 类中的静态代码块,而不会创建器实例化对象(不会调用该类的构造器)
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(clazz);
}
}
class User {
String name;
public User() {
System.out.println("User 的构造器");
}
// 静态代码块
static {
System.out.println("User 类中的 静态代表块");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
注意:每个类对应的.class
字节码文件只有一份,所以对应的 每个类的 Class
对象也只有一个,所有的类对象共用
举例:
package blogs.blog11;
/**
* 获取Class 对象的三种方式
*/
public class ReflectTest01 {
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("java.lang.String");
Class<String> clazz2 = String.class;
System.out.println(clazz == clazz2); // true
// 因为每个类只有一个 Class 对象,类只会加载一次到内存当中,并只生成一份 .class 文件
// 所有对象共用。
Class clazz3 = Class.forName("blogs.blog11.User");
Class clazz4 = User.class;
System.out.println(clazz3 == clazz4); // 返回 true
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User {
String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
上述代码的内存图解析:
6. 类加载器的理解
6.1 类的加载过程:
当程序主动使用了某个类时,如果该类还未加载到内存中,则系统会通过,如下三个步骤对该类进行初始化:
- 加载:
将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的 Java.lang.Class 对象,作为方法区中类数据的访问入口 (即引用地址)。所有需要访问和使用类数据只能通过 这个 Class 对象。这个加载的过程需要类加载器参与。
- 链接:
将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。
- 验证:确保加载的类信息符合 JVM 规范,例如:以 cafe 开头,没有安全方面的问题。
- 准备:正式为类变量 (static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
执行 类构造器<clinit>() 方法的过程。类构造器<clinit>() 方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的 。(类构造器是构造类信息的,不是构造该类对象的构造器)。
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
虚拟机会保证一个类的<clinit> ()方法在多线程环境中被正确加锁和同步。
举例:
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
什么时候发生类初始化 ???
- 类的主动引用(一定会发生类的初始化)
当虚拟机启动,先运行 main 方法所在的类,new 一个类的对象,调用类的静态成员(除了 final 常量)和静态方法。使用 java.lang.reflect 包的方法对类进行反射调用。当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类。
- 类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化,当通过子类引用父类的静态变量,不会导致子类初始化,
通过数组定义类引用,不会触发此类的初始化。引用常量不会触发此类的初始化(常量在链接阶段存入调用类的常量池中了。)
6.2 类加载的作用
类加载器: 负责将对应类生成的.class
文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之在堆区中生成对应的 java.lang.Class
对象,作为方法区中类数据的访问入口。
类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的 类的加载器。
类加载机制:
类加载器负责加载所有的类,系统为所有被载入内存中的类生成
java.lang.Class
实例对象。一个类被载入 JVM 中,同一个类就不会再次载入了。正如一个对象有一个唯一的标识一样,一个载入 JVM 中的类也有一个唯一的标识。
在 Java 中,一个类用其全限定类名(包括包名和类名) 作为标识,但在JVM中,一个类用其全限定类名和其类加载器作为唯一标识。
例如:如果在 pg 的包中有一个名为 Person 的类,被类加载器 ClassLoader 实例 Loader 负责加载,则该 Person 类对应的 Class 对象在 JVM 中表示为(Person,pg,Loader)这意味着两个类加载器加载同名类:(Person,pg,Loader)和 (Person,pg,Loader)是不同的,它们所加载的类也是完全不同,互不兼容的。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:
- 根类加载器(Bootstrap ClassLoader) :其负责加载Java的核心类,比如String,System 这些类,一般是在
C:\Program Files\jdk1.8.0_101\jre\lib\rt.jar
路径下,rt.jar
中都是JDK最核心的类库 - 扩展类加载器(Extension ClassLoader) :其负责加载 JRE 的扩展类库。一般是在
:\Program Files\jdk1.8.0_101\jre\lib\*.jar
路径下的 - 系统类加载器(System ClassLoader) : 其负责加载 CLASSPATH 环境变量所指定的 JAR 包和类路径。
除了可以使用 Java 提供的类加载器之外, 开发者也可以实现自己的类加载器, 自定义的类加载器通过继承 ClassLoader 来实现。
- 用户类加载器:用户自定义的加载器,以类加载器为父类
6.2.1 双亲委派
java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为"父",“父"无法加载到,再从扩展类加载器中加载。这个称为"母”。双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。
如下是 :类加载器的层次结构图
双亲委派模型:如上图所示的类加载器之间的这种层次关系,就称为类加载器的双亲委派模型(Parent Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,子类加载器和父类加载器不是以继承 (Inheritance) 的关系实现,而是通过组合(Composition)关系来复用父加载器的代码。
双亲委派模型的工作过程为:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器 ,只有当父加载反馈自己无法完成该加载请求(该加载的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
JVM 的类加载机制主要有如下三种:
- 全盘负责: 所谓全盘负责,就是当一个类加载器负责加载某一个 Class 时,该 Class 所依赖的和引用的其他 Class 也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
- 父类委托: 所谓的父类委托,则是 先让 parent(父)类加载器试图加载该 Class,只有在父类加载器无法加载该类时,才尝试从自己的类路径中加载该类。
- 缓存机制: 缓存机制将保证所有加载过的 Class 都会被缓存,当程序中需要使用某个 Class 时,类加载器先从缓存区中搜寻该 Class,只有当缓存区不存在该 Class 对象时,系统才会读取该类对应的二进制数据,并将其转换成 Class 对象,存入缓存区中。这就是为什么修改了 类后,必须重新启动 JVM ,程序所做的修改才会生效的原因。
补充: 标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
6.3 ClassLoader 的使用
举例:
- 获取一个系统类加载器 格式如下:
ClassLoader classLoader = 类名.class.getClassLoader();
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 对于自定义类,使用的是系统类加载进行加载的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
System.out.println(classLoader);
}
}
- 获取系统类加载器的父类加载器,即扩展类加载器
格式:
// 首先通过: 类名.class 调用getClassLoader() 方法获取到该类的类加载器
// 再通过获取到的类加载器,调用 getParent()犯法,获取都器父类的类加载器。
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
ClassLoader classLoaderParent = classLoader.getParent();
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 对于自定义类,使用的是系统类加载进行加载的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
System.out.println(classLoader);
// 调用对应类中父类中的getParent(): 获取扩展类加载器
ClassLoader classLoaderParent = classLoader.getParent();
System.out.println(classLoaderParent);
}
}
- 获取扩展类加载器的父类加强器,即引导类加载器
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
// 对于自定义类,使用的是系统类加载进行加载的
ClassLoader classLoader = ReflectTest02.class.getClassLoader();
// 调用对应类中父类中的getParent(): 获取扩展类加载器
ClassLoader classLoaderParent = classLoader.getParent();
// 调用扩展类加强器到getParent(): 是无法获取引导类加载器
// 引导类加载器主要负责: 加载Java的核心类型,无法加载自定义类的
// 所以一般是返回 null 的。我们无法获取到的。
ClassLoader parent = classLoaderParent.getParent();
System.out.println(parent);
}
}
- 测试当前类由哪个类加载器进行加载的
package blogs.blog11;
public class ReflectTest02 {
public static void main(String[] args) {
try {
// Java内置的 String 类型
ClassLoader classLoader = Class.forName("java.lang.String").getClassLoader();
System.out.println(classLoader);
// 自定义的类引用类型
ClassLoader classLoader2 = Class.forName("blogs.blog11.Test").getClassLoader();
System.out.println(classLoader2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Test {
public int num = 0;
public Test() {
}
@Override
public String toString() {
return "Test{" +
"num=" + num +
'}';
}
}
7. 反射机制查看类
通过反射机制获取到对应类中的 包信息,父类名,接口,修饰符,类名,泛型,类中实现的接口,父类中实现的接口
举例
package blogs.blog11;
import java.lang.reflect.Modifier;
public class ReflectTest03 {
public static void main(String[] args) {
// 1. 获取到对应类中的 Class 对象
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 2. 通过该 Class 对象获取到对应类中的包名
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
// 3. 获取到该类中的修饰符
int modifiers = clazz.getModifiers(); // 注意这里返回的是对应 修饰符中序号
String s = Modifier.toString(modifiers);
System.out.println(s); // 如果默认是缺省的权限修饰符的话,返回的是 空
// 4. 获取到该类的类名
String name = clazz.getName();
System.out.println(name);
// 5. 获取该类中所继承的父类名
String superName = clazz.getSuperclass().getName();
System.out.println(superName);
// 6. 获取该类中的所实现的接口.因为一个类中可以继承多个接口所以,返回的是Class[]类型的数组
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
}
}
@MyAnnotation
class MyClass<String> extends MySuperClass implements Comparable {
@Override
public int compareTo(Object o) {
return 0;
}
}
@interface MyAnnotation {
String value() default "Hello";
}
class MySuperClass { // protected
}
8. 反射机制调用构造器
-
public Constructor[] getConstructors():返回此 Class 对象所表示的类的所有public构造方法。
-
public Constructor[] getDeclaredConstructors():返回此 Class 对象表示的类声明的所有构造方法,包括私有的构造器
-
Constructor类中:
-
public int getModifiers() 获取到修饰符,返回的是对应Modifier 修饰符类中的 数值常量,可以通过 Modifier .toString(对应修饰符的数值) 获取对应修饰符的名称;
-
public String getName() 获取到方法名称
-
public Class[] getParameterTypes() 获取到参数的类型
通过反射获取到类中所有的构造器中的信息
举例:
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* 反射处理构造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
StringBuilder s = new StringBuilder(); // 用于字符串的拼接
// 获取到对应的类中的 Class 对象.
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass3");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 获取该类中所有的构造方法
// 因为一个类中可能会存在多个构造器,所有返回的是一个Constructor 的数组
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors) {
// 获取到构造方法的修饰符
int modifiers = c.getModifiers();
String s2 = Modifier.toString(modifiers);
System.out.print(s2 + "\t");
// 获取到构造器的名字,其实就是类名
String simpleName = clazz.getSimpleName();
System.out.println(simpleName);
// 获取该构造方法中的实参列表
// 因为一个方法中会存在多个参数。所以返回的是一个 Class[]数组
Class[] parameterTypes = c.getParameterTypes();
// 当该返回的参数数组,含有数据,不是 0 ,不为 null
// 则说明该构造器是有参数的。
if(!(parameterTypes.length == 0 || parameterTypes == null)) {
// 则获取到该构造器中从参数
for(Class aClass : parameterTypes) {
String type = aClass.getSimpleName(); // 实参类型
System.out.println(type);
}
}
System.out.println();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
// 定义无参构造器,用于反射调用
}
public MyClass3(String name) {
this.name = name;
}
public MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
}
8.1 通过反射调用私有构造器 🌟🌟🌟
- 通过反射调用无参构造器
使用方法:newInstance()
该方法的实际上是调用了类中的无参构造器,如果类中没有定义无参构造器,就无法通过该反射机制调用类中的构造器
举例:
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* 反射处理构造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
// 1.获取对应的Class 对象
try {
Class clazz = Class.forName("blogs.blog11.MyClass3"); // 全类路径
// 2.调用 newInstance();这个构造器
// 该方法实际上是调用了类中的无参构造器
Object o = clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
System.out.println("无参构造器");
}
public MyClass3(String name) {
this.name = name;
}
public MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
}
注意: 如果该类中没有定义无参构造器,则无法反射,会异常:java.lang.InstantiationException
。所以一般定义的类都会创建一个无参构造器,用于反射机制的获取。
- 通过反射机制调用私有的构造器
如果想要调用类中 权限范围小的,比如 private 私有的,必须使用 setAccessible(true)
方法将其中的值设置为 true
- Method(方法) 和 Field(属性),Constructor(构造器) 都有 serAccessible() 方法
- setAccessible() 启动和禁用访问安全检查的开关。
- 参数值为
true
则指示反射的对象在使用时,应该取消 Java 语言访问检查。- 提高反射的效率。如果代码中必须用反射,而该代码需要频繁的被调用,那么请设置为
true
- 可以使得原本无法访问的私有成员/方法/构造器也可以访问。
- 提高反射的效率。如果代码中必须用反射,而该代码需要频繁的被调用,那么请设置为
- 参数值为
false
则指示反射的对象应该实施 Java语言访问检查。
举例: 这里访问私有的构造器,通过反射机制。
注意 需要将 setAccessible(true) 中设置为 true 是保证反射可以访问私有的构造器。
package blogs.blog11;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
/**
* 反射处理构造器
*/
public class ReflectTest05 {
public static void main(String[] args) {
// 1.获取对应的Class 对象
try {
Class clazz = Class.forName("blogs.blog11.MyClass3"); // 全类路径
// 第一步:先获取到这个类中有参数的构造器的Constructor对象
// 注意对应调用的构造器中的参数类型要保持一致,用于后面的赋值
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
// 第二步: 设置将该获取到的Constructor(构造器)对象中的setAccessible(true)保证私有的可以被访问
// 如果缺少这一步设置的话,是无法访问私有的构造器的
declaredConstructor.setAccessible(true);
// 第三步: 传构造器中对应的参数,调用构造器,创建对象
// 通用是调用 newInstance() 方法调用其中类对应的构造器。注意这里我们调用的是带了参数的构造器了。
Object tom = declaredConstructor.newInstance("Tom", 18);
System.out.println(tom);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
class MyClass3 {
String name;
int age;
public MyClass3() {
}
private MyClass3(String name) {
this.name = name;
}
private MyClass3(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "MyClass3{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
注意: 如果缺少了。setAccessible(true);
这一步设置的话,是无法调用到私有的构造器的,会报该异常:java.lang.IllegalAccessException:
非法访问异常。
9. 反射机制获取类中的属性
- public Field[] getFields() : 返回此Class对象所表示的类或接口的public的Field。
- public Field[] getDeclaredFields() : 返回此Class对象所表示的类或接口的全部Field。
- public int getModifiers() : 以整数形式返回此Field的修饰符.同理可以使用 Modifier .toString(对应修饰符的数值) 获取对应修饰符的名称;
- public Class getType() 得到Field的属性类型
- public String getName() 返回Field的名称。
举例:
package blogs.blog11;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) {
// 这里我们创建一个字符串,用于字符串的拼串
StringBuilder s = new StringBuilder();
Class clazz = null;
try {
// 1. 获取到该类的Class 字节码对象
clazz = Class.forName("blogs.blog11.MyClass6");
// 2. 实例化该对象:因为这里我们已经知道需要实例读的对象是什么了,所以可以直接强制转换
// newInstance()方法调用的是该类中的无参构造器,所以无参构造器必须要有
MyClass6 myClass = (MyClass6) clazz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 将该类中的 修饰符 类名 添加到该字符串中
s.append(Modifier.toString(clazz.getModifiers()) + "class \t" + clazz.getSimpleName() + "\t{ \n");
// 3. 获取到该类中的所有属性,包括private 私有的
// 因为一个类中的属性是多个,所以返回的是 Field 属性数组
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
s.append("\t");
// 获取到该属性中的修饰符
int modifiers = f.getModifiers();
String bedeck = Modifier.toString(modifiers);
s.append(bedeck); //添加到字符串中
s.append(" ");
// 获取到该属性的类型
Class<?> type = f.getType();
// 获取到该属性的类型名称
String nameType = type.getName();
s.append(nameType); // 添加
s.append(" ");
// 获取到属性名
String nameAttribute = f.getName();
s.append(nameAttribute); // 添加
s.append("\n");
}
s.append("}");
System.out.println(s);
}
}
class MyClass6 {
public String name;
protected int age;
private int id;
boolean sex;
public MyClass6() {
// 定义无参构造器,用于反射调用
}
@Override
public String toString() {
return "MyClass6{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", sex=" + sex +
'}';
}
}
9.1 通过反射机制调用私有的属性(set/get) 🌟🌟🌟
在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()
和get()
方法就可以完成设置和取得属性内容的操作。
-
public Field getField(String name) :返回此Class对象表示的类或接口的指定的 public 的Field 属性的字节码对象。只能获取到 public的属性
-
public Field getDeclaredField(String name) :返回此Class对象表示的类或接口的 指定的Field属性的字节码对象,所以的都可以,包括 private 私有的
-
public Object get(Object obj) :取得指定对象obj上此Field的属性内容,获取到该对应对象中指明的属性的值
-
public void set(Object obj,Object value) :设置指定对象obj上此Field的属性内容,修改该对应对象中指明的属性的值
核心代码:
//获取需要访问的属性的 Field 对象((根据属性的名称来获取Field)) : 通过属性名找到你想要set/get的属性的 Field (属性)字节码对象
Field fieldName = clazz.getDeclaredField("name");
// 通过将为setAccessible(true)反射可以访问私有的属性
fieldName.setAccessible(true);
// 使用 set()对属性赋值
// set(哪个对象中的属性,赋的值)
fieldName.set(myClass,"Tom"); // 注意所赋的值,需要符合该类中的这个属性的定义,不然报错
// 6.读取属性的值:
// 两个要素:MyClass对象的 name 属性的值,哪个对象中的属性值。获取到该对象中对应的属性值
Object o = fieldName.get(myClass);
System.out.println(o);
举例:
package blogs.blog11;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest06 {
public static void main(String[] args) {
try {
// 1. 创建该类的 Class 字节码对象
Class clazz = Class.forName("blogs.blog11.MyClass6");
// 2. 获取该类的实例化对象: 因为这里我们已经知道需要实例化的对象是什么了,所以可以直接强制转换
// newInstance()实际调用的是该类中的无参构造器,所以该类必须要有无参构造器
MyClass6 myClass = (MyClass6) clazz.newInstance();
// 3. 获取需要访问的属性的 Field 对象((根据属性的名称来获取Field))
Field fieldName = clazz.getDeclaredField("name");
// 4. 给MyClass6 对象中的 私有 name 属性赋值
/*
虽然使用了反射机制,但是三要素还是缺一不可:
要素1: Object 对象,修改赋值的对象上的属性值
要素2: no 属性
要素3: 100属性值
*/
// 通过将为setAccessible(true)反射可以访问私有的属性
fieldName.setAccessible(true);
// 5.使用 set()对属性赋值
// set(对应的赋值的对象,该对象中想要赋的属性值)
fieldName.set(myClass,"Tom"); // 注意所赋的值,需要符合该类中的这个属性的定义,不然报错
// 6.读取属性的值:
// 两个要素:MyClass 对象的 name 属性的值
Object o = fieldName.get(myClass);
System.out.println(o);
System.out.println(myClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
class MyClass6 {
private String name;
protected int age;
private int id;
boolean sex;
public MyClass6() {
// 定义无参构造器,用于反射调用
}
@Override
public String toString() {
return "MyClass6{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
", sex=" + sex +
'}';
}
}
10. 反射机制获取类中的方法
-
public Method[] getDeclaredMethods():返回此Class对象所表示的类或接口的全部方法,包括私有的方法
-
public Method[] getMethods() :返回此Class对象所表示的类或接口的public的方法
-
public Class getReturnType():获取到该方法中的返回值类型
-
public Class[] getParameterTypes():获取到该方法全部的参数类型,
-
public int getModifiers():获取到该方法的修饰符,返回的是对应Modifier 修饰符类中的 数值常量,可以通过 Modifier .toString(对应修饰符的数值) 获取对应修饰符的名称;
-
public Class[] getExceptionTypes(): 获取到该方法的异常信息
举例:
package blogs.blog11;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest07 {
public static void main(String[] args) {
// 定义一个字符串,用于字符串的拼接
StringBuilder stringBuilder = new StringBuilder();
// 1. 获取到该类的 Class 字节码对象
Class clazz = null;
try {
clazz = Class.forName("blogs.blog11.MyClass7");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 首先将该类名,信息添加到字符串中
stringBuilder.append(Modifier.toString(clazz.getModifiers()) + "\tclass\t" + clazz.getSimpleName() +
"\t{\n");
// 2. 获取到该类中的所有的方法,包括private 私有的
Method[] methods = clazz.getDeclaredMethods();
// 遍历该 Method 数组中的值
for (Method m : methods) {
stringBuilder.append("\t");
// 获取到该方法中的修饰符 public
int modifiers = m.getModifiers();
String s = Modifier.toString(modifiers);
stringBuilder.append(s); // 添加上
stringBuilder.append(" ");
// 获取到该方法的返回值类型
Class<?> returnType = m.getReturnType();
// 获取到该方法的返回值类型名
String nameType = returnType.getName();
stringBuilder.append(nameType); // 添加上
stringBuilder.append(" ");
// 获取到该方法名称
String name = m.getName();
stringBuilder.append(name);
stringBuilder.append(" ( ");
// 获取到该方法的参数列表:
// 因为方法中可能存在多个参数列表,也可能一个也没有
Class<?>[] parameterTypes = m.getParameterTypes();
// 如果该方法从实参数是 0 个,就不要获取该方法中的实参类型了
if (!(parameterTypes.length == 0 || parameterTypes == null)) {
// 有遍历该实参列表
for (Class<?> p : parameterTypes) {
// 获取到该实参的类型
String simpleName = p.getSimpleName();
stringBuilder.append(simpleName); // 添加上
stringBuilder.append(",");
}
// 删除指定下标位置上的字符,这里删除的是:最后一个参数多了一个 “,”逗号
stringBuilder.deleteCharAt(stringBuilder.length() - 1);
}
stringBuilder.append("){}\n");
}
stringBuilder.append("}");
System.out.println(stringBuilder);
}
}
final class MyClass7 {
public MyClass7() {
// 定义无参构造器,用于反射调用
}
public static void fun2() {
}
static final void fun4(String s, double d) {
}
private int fun1(int num) {
return num;
}
private String fun3() {
return "Hello World";
}
}
10.1 通过反射调用类中私有的方法
通过反射,调用类中的方法,通过Method类完成。步骤:
1.通过Class类的getMethod(String name,Class…parameterTypes)
方法取得 一个Method 方法字节码对象,并设置此方法操作时所需要的参数类型。之后使用Object invoke(Object obj, Object[] args)
进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object … args) 方法的说明:
-
invoke()方法的返回值,就是调用的原方法的返回值,若原方法无返回值,此时返回null
-
若原方法若为静态方法,此时形参Object obj 可为null
-
若原方法形参列表为空,则 Object[] args 可为null
-
若原方法声明为 private,则需要在调用此 **invoke()**方法前,显式调用 方法对象的
setAccessible(true)
方法,将可访问private的方法。
举例:
package blogs.blog11;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest07 {
public static void main(String[] args) {
try {
// 1. 获取到该类的 Class 字节码对象
Class clazz = Class.forName("blogs.blog11.MyClass7");
//2. 创建对应的类对象,通过反射调用其无参构造器
// 这里因为我们知道,实例化的对象是什么类型的可以直接强制转换
MyClass7 myClass = (MyClass7) clazz.newInstance();
// 3. 获取指定想要调用的方法中的 Method 方法的字节码对象
// 方法存在重载:所以想要确定一个方法的要素是:方法名,实参类型
// 获取到 fun1()方法的 Method 对象
Method method = clazz.getDeclaredMethod("fun1");
// 获取到 fun3(String s ,int num) 方法的 Method 对象
Method method2 = clazz.getDeclaredMethod("fun3", String.class, int.class); // 注意需要和实际调用的方法中的类型保持一致
// 4. 设置为 true 保证反射可以调用私有的方法
method.setAccessible(true);
method2.setAccessible(true);
// 5.
/*
调用方法
1. fun1() fun2() 方法名
2. myClass调用哪个对象中的方法
3. 方法中的参数列表的赋值:“Tom" ,999实参
4. o,o2 返回类型
该 invoke()方法的返回类型就是,对应反射机制调用的对象中的方法的返回类型
*/
// 调用 method 中的 fun1() 方法
// 因为我们的 fun1()方法是没有实参的所以传个 null 就可以了
Object o = method.invoke(myClass, null);
System.out.println(o); // 因为fun1()方法是没有返回值的所以返回的是 null
// 调用 method2 中的fun2()方法
Object o2 = method2.invoke(myClass, "Tom", 999);
System.out.println(o2); // invoke()方法的返回值,就是 fun2()方法的返回值
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
final class MyClass7 {
public MyClass7() {
// 定义无参构造器,用于反射调用
}
public static void fun2() {
}
static final void fun4(String s, double d) {
}
private void fun1() {
System.out.println("你好世界");
}
private String fun3(String s, int num) {
return "Hello World" + num;
}
}
11. 反射读取文件信息
// 使用反射进行获取
@Test
public void test3() {
// Map 集合
Properties properties = new Properties();
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// 注意: 该文件路径需要在 src 目录下才行,注意: 再包下时需要说明包路径下的位置
// 读取配置文件的方式二: 使用ClassLoader
// 配置文件默认识别为: 当前module的src 下
InputStream resourceAsStream = classLoader.getResourceAsStream("day28/jdbc.properties");
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
String user = properties.getProperty("user");
System.out.println(user);
String password = properties.getProperty("password");
System.out.println(password);
}
12. 反射机制获取到类中的注解信息
想要让反射可以读取到注解中的信息,则该反射中的元注解必须是: @Retention(RetentionPolicy.RUNTIME)
才行。
举例: 这里我们使用反射读取到 fun() 方法中的 注解中的 value 属性值:
注解
package blogs.blog10;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 生命周期在:运行期,才可以被反射读取到
public @interface MyAnnotation {
String value() default "Tom";
}
package blogs.blog10;
import java.lang.reflect.Method;
public class AnnotationTest {
public static void main(String[] args) {
Method method = null;
try {
// 获取类加载器,类对象
Class clazz = Class.forName("blogs.blog10.AnnotationTest"); // 全类路径名
// 获取 fun()方法
method = clazz.getDeclaredMethod("fun");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
// 判断该方法是否存在该注解,存在才读取该注解上的属性值
if(method.isAnnotationPresent(MyAnnotation.class)) {
// 获取该注解对象
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// // 获取该注解的属性值,就像对象.属性一样
String value = annotation.value();
System.out.println(value);
}
}
@MyAnnotation("lihua")
public void fun() {
int num = 0;
}
}
13. 23种设计模式:动态代理模式
想要了解:动态代理模式的大家可以移步至:🔜🔜🔜 https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
14. 总结:
- 反射的作用:通过 Java语言中的反射机制可以操作字节码文件,优点类似于黑客。(可以读和修改字节码文件),可以调用私有的构造器,set/get 属性,调用私有的方法。
- 获取 Class 类实例的三种方式,重点掌握方式三:该方式会自动调用该类中的静态代码块,因为静态代码块,而不会实例化类的,可以应用于 JDBC 加载数据库驱动,执行其中的静态代码块,而不实例化。
- 类加载的过程。
- 类加载器的作用: 负责将对应类生成的
.class
文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之在堆区中生成对应的java.lang.Class
对象,作为方法区中类数据的访问入口。 - 双亲委派的原理。类加载器的种类,层次
- 反射机制中的:调用私有的构造器,调用私有的属性,调用私有的方法,获取注解中的属性值。
15. 最后:
👍👍👍 ✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️ 感谢以下大佬提供的参考资料 ✏️✏️✏️✏️✏️✏️✏️✏️✏️✏️ 👍👍👍
【1】: https://fighter3.blog.csdn.net/article/details/103758938
【2】:https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善 。谢谢大家,江湖再见,后会有期 !!!