反射机制
1.反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。
优点类似于黑客。(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class文件。)
2.反射机制的相关类在哪个包下?
java . lang . reflect.* ;
3.反射机制相关的重要的类有哪些?
-
java.lang.Class:代表整个字节码,代表一个类型。代表整个类
-
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法
-
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法。
-
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
//Class
public class User {
int no;//Field
//Constructor
public User() {}
public User(int no) {
this.no = no;
} //Method
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}
4.要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java. lang. Class实例?
三种方式
第一种:Class c = Class .forName(“完整类名带包名”);
第二种:Class c = 对象. getClass();
第三种:Class c = 任何类型.class;
①、Class.forName()
- 静态方法
- 方法的参数是一个字符串
- 字符串需要的是一个完整类名
- 完整类名必须带有包名。java.lang包也不能省略。
②、java中任何一个对象都有一个方法:getClass()
③、java语言中任何一种类型,包括基本数据类型,它都有.class属性。
获取到Class 能干什么?
通过Class的newInstance()方法来实例化对象。
注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
5.验证反射机制的灵活性
java代码写一遍,在不改变java源代码的基础之上,可以做到不同对象的实例化。
非常之灵活。( 符合OCP开闭原则:对扩展开放,对修改关闭。)
后期要学习的是高级框架,而工作过程中,也都是使用高级框架,
包括: Spring、 SpringMVC、 MyBatis 、Struts 、Hibernate、、、、、、
这些高级框架底层实现原理:都采用了反射机制。所以反射机制还是重要的。
学会了反射机制有利于你理解剖析框架底层的源代码。
类加载的过程主要分为三个部分:加载、链接、初始化,这三个阶段
class. forName()发生了什么?
重点: 如果你只是希望一个类的静态代码块执行,其它代码一律不执行,你可以使用:
Class . forName(“完整类名”);
这个方法的执行会导致类加载,类加载时,静态代码块执行。
//获取一个文件的绝对路径
String path = Thread.currentThread().getContextClassLoader().getResource("classinfo2.properties").getPath();
System.out.println(path);
//通过IO流读取classinfo2.properties文件
FileReader reader = new FileReader(path);*/
InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo2.properties");
//创建属性类对象Map
Properties pro = new Properties();
//加载
pro.load(reader);
//关闭流
reader.close();
//通过key获取value;
String className = pro.getProperty("className");
Class c = Class.forName(className);
6.接下来说一种比较通用的一种路径。即使代码换位置了,这样编写仍然是通用的。
注意:使用以下通用方式的前提是:这个文件必须在类路径下。
什么类路径下?凡是在src下的都是类路径下。【记住它】
src是类的根路径。
-
Thread.currentThread()当前线程对象
-
getContextClassloader()是线程对象的方法,可以获取到当前线程的类加载器对象。
-
getResource() [获取资源] 这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源。
String path = Thread.currentThread().getContextClassLoader().getResource("从src的根路径下作为起点开始").getPath();
7.java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。
使用以下这种方式的时候,属性配置文件xxx.properties必须放到类路径下,并且在写路径的时候,路径后面的扩展名不能写。
ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
String className = bundle.getString("className"); System.out.println(className);
8.关于JDK中自带的类加载器
8.1、什么是类加载器?
专门负责加载类的命令/工具。ClassLoader
8.2、JDK中自带了3个类加载器
-
启动类加载器
-
扩展类加载器
-
应用类加载器
8.3、假设有这祥一段代码
String s =" abc" ;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?
首先通过"启动类加载器"加载。
注意:启动类加载器专门加载:C:\ProgramFiles\Java\jdk1.8.0_jre\lib\rt.jar
rt.jar中都是JDK最核心的类库
如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载。
注意:扩展类加载器专门加载: C:\ProgramFiles\Java\jdk1.8.0\jre\lib\ext*.jar
如果"扩展类加载器"没有加载到,那么会通过应用类加载器"加载。
注意:应用类加载器专门加载: classpath中的类。
8.4、java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父",
“父"无法加载到,再从扩展类加载器中加载,这个称为"母”。
双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
9.验证反射
Student类
//反射属性Field
public class Student {
public int no;
private String name;
protected int age;
boolean sex;
public static final double PI = 3.1415926;
}
反射Field
//获取整个类
Class aClass = Class.forName("com.se.reflect.Student");
//获取完整类名
String name = aClass.getName();
System.out.println("完整类名:" + name);
//获取简类名
String simpleName = aClass.getSimpleName();
System.out.println("简类名:" + simpleName);
//获取类中的Field(只有public修饰的才可以获取到)
Field[] fields = aClass.getFields();
System.out.println("数组长度:" + fields.length);//测试数组的长度 1
//取出这个Field
Field f = fields[0];
//取出Field的名字
String fieldName = f.getName();
System.out.println(fieldName);
System.out.println("=======================================================");
//获取所有的Field
Field[] fields1 = aClass.getDeclaredFields();
for (Field field : fields1) {
//获取属性的修饰符列表
int a = field.getModifiers();
//返回的修饰符是一个数字,每个数字是修饰符的代号!!!
//将这个“代号”转换成字符串
String modifierName = Modifier.toString(a);
System.out.println(modifierName);
//获取属性的类型
Class fieldType = field.getType();
System.out.println(fieldType);
String fName = field.getName();
System.out.println(fName);
System.out.println("===========================");
}
结果截图
通过反射机制反编译一个类的属性Field
//创建这个是为了拼接字符串
StringBuilder stringBuilder = new StringBuilder();
Class studentClass = Class.forName("com.se.reflect.Student");
stringBuilder.append(Modifier.toString(studentClass.getModifiers())+" class " + studentClass.getSimpleName() + "{\n");
Field[] fields = studentClass.getDeclaredFields();
for (Field field: fields) {
stringBuilder.append("\t");
stringBuilder.append(Modifier.toString(studentClass.getModifiers()));
stringBuilder.append(" ");
stringBuilder.append(field.getType().getSimpleName());
stringBuilder.append(" ");
stringBuilder.append(field.getName());
stringBuilder.append(";\n");
}
stringBuilder.append("}");
System.out.println(stringBuilder);
结果截图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/02020a7d63792246ad7d8c0fe2e8849c.jpeg#pic_center)
怎么通过反射机制访问一个java对象的属性?
-
给属性赋值set
-
获取属性的值get
//给对象的属性赋值
Student student = new Student();
student.no = 1111;
//通过反射机制,获取对象
Class studentClass = Class.forName("com.se.reflect.Student");
Object obj = studentClass.newInstance();
//获取no属性
Field noField = studentClass.getDeclaredField("no");
//给Object对象(Student)no赋值
noField.set(obj,2222);
System.out.println("noField:"+noField);
//获取no属性的值
System.out.println("获取no属性的值:"+noField.get(obj));
//获取name属性
Field nameField = studentClass.getDeclaredField("name");
System.out.println("nameField:"+nameField);
//打破封装,name属性为private修饰
nameField.setAccessible(true);
nameField.set(obj,"Mark");
System.out.println("获取name属性的值:"+nameField.get(obj));
注意:name属性是private修饰的,需要打破封装setAccessible(true);
结果截图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/6c32fd2d66bb6dfe1aa9a6d3fde2db2b.jpeg#pic_center)
可变长度参数
int… args这就是可变长度参数
语法是:类型…(注意:一定是3个点。)
1、可变长度参数要求的参数个数是:0~N个。
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。
3、可变长度参数可以当做一个数组来看待
反射Method
UserService类
public class UserService {
public boolean login(String name, String password) {
if ("admin".equals(name) && "123".equals(password)) {
return true;
}
return false;
}
public void loginOut() {
System.out.println("系统已安全退出");
}
}
//获取类
Class aClass = Class.forName("com.se.reflect.UserService");
//获取所有的Method(包括私有的)
Method[] methods = aClass.getDeclaredMethods();
//遍历Method
for (Method method : methods) {
//获取修饰符列表
System.out.println("修饰符:"+Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
String returnType = method.getReturnType().getSimpleName();
System.out.println("获取方法返回值类型:"+returnType);
//获取方法名
System.out.println("获取方法名:"+method.getName());
//获取方法的参数列表
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType :
parameterTypes) {
System.out.println("参数列表:"+parameterType.getSimpleName());
}
System.out.println("==================================");
}
结果截图
重点:必须掌握,通过反射机制怎么调用一个对象的方法?
反射机制,让代码很具有通用性,可变化的内容都是写到配置文件当中,将来修改配置文件之后,创建的对象不一样了,调用的方法也不同了,但是java代码不需要做任何改动。这就是反射机制的魅力。
Class aClass = Class.forName("com.se.reflect.UserService");
//创建对象
Object obj = aClass.newInstance();
//获取Method
Method loginMethod = aClass.getDeclaredMethod("login", String.class, String.class);
//四要素:对象,方法名,实参列表,返回值
//反射机制最重要的一个方法
/* loginMethod是方法
* obj是对象
* "admin","123"是实参列表
* retValue是返回值*/
Object retValue = loginMethod.invoke(obj,"admin","123");
System.out.println("调用方法返回值:"+retValue);
结果截图
通过反射机制反编译Method
Class aClass = Class.forName("com.se.reflect.UserService");
StringBuilder s = new StringBuilder();
s.append(Modifier.toString(aClass.getModifiers()) + " class " + aClass.getSimpleName() + "{");
s.append("\n");
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
s.append("\t");
s.append(Modifier.toString(aClass.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
//参数列表
Class[] parameterTypes = method.getParameterTypes();
for (Class parameterType : parameterTypes) {
s.append(parameterType.getSimpleName());
s.append(",");
}
if (parameterTypes.length > 0) {
s.deleteCharAt(s.length() - 1);
}
s.append("){\n\t}\n");
}
s.append("}");
System.out.println(s);
结果截图
反编译Constructor
Vip类
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {
}
public Vip(int no, String name, String birth, boolean sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
@Override
public String toString() {
return "Vip{" +
"no=" + no +
", name='" + name + '\'' +
", birth='" + birth + '\'' +
", sex=" + sex +
'}';
}
}
StringBuilder s = new StringBuilder();
Class aClass = Class.forName("com.se.reflect.Vip");
s.append(Modifier.toString(aClass.getModifiers()));
s.append(" class ");
s.append(aClass.getSimpleName());
s.append("{\n");
//拼接构造方法
Constructor[] constructors = aClass.getDeclaredConstructors();
for (Constructor c : constructors) {
s.append("\t");
s.append(Modifier.toString(aClass.getModifiers()));
s.append(" ");
s.append(aClass.getSimpleName());
s.append("(");
//public Vip(int no, String name, String birth, boolean sex) {
//拼接参数
Class[] parameterTypes = c.getParameterTypes();
for (Class parameterType : parameterTypes) {
s.append(parameterType.getSimpleName());
s.append(" ,");
}
if (parameterTypes.length > 0) {
s.deleteCharAt(s.length() - 1);
}
s.append("){\n\t}\n");
}
s.append("}");
System.out.println(s);
结果截图
通过反射机制调用构造方法实例化对象
//使用反射机制创建对象
Class aClass = Class.forName("com.se.reflect.Vip");
//调用无参构造方法
System.out.println("调用无参构造方法");
Object obj = aClass.newInstance();
System.out.println(obj);
System.out.println("=======================================");
System.out.println("先获取有参数构造方法,再调用构造方法new对象");
//先获取有参数构造方法
Constructor con = aClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
//调用构造方法new对象
Object obj2 = con.newInstance(12, "zhangsan", "20-3-7", true);
System.out.println(obj2);
结果截图
给你一个类,怎么获取这个类的父类,已经实现了哪些接口?
//获取父类
Class superClass = aClass.getSuperclass();
System.out.println(superClass.getName());
//获取类所实现的所有接口(一个类可以实现多个接口)
Class[] interfaces = aClass.getInterfaces();
for(Class c :interfaces){
System.out.println(c.getName());
}