本文目录
Java基础——反射可以这么学 (框架的灵魂)
写在前面
昨天啊,小付在二刷JDBC源码的时候,源码中的反射又偷偷跑出来给俺当头一棒子,为了加深反射在框架中的熟悉程度堪比abandon
那般熟悉,为此今天就对基础的反射技术再次加以学习,毕竟只要功夫深,铁杵磨成针
,慢慢来 不慌~室友打了把英雄联盟的时间就够你学完本篇内容啦,话不多说 ,肘着~
反射入门知识
什么是反射?
什么是反射?
Java的反射机制是在运行状态中
,对于任意
的一个类,都能知道这个类的所有属性和方法,对于任意一个对象。都能随时调用
它的任意一个方法和属性,这种动态获取类的相关信息
,以及调用对象的方法
称之为Java语言的反射机制。
要想剖析一个类,就必须获得该类的字节码文件对象,也就是咱们javac编译java程序粗来的xxx.class文件,所以我们需要先获得每个字节码对应的Class类型的对象
。那么这里肯定要调用底层
,native方法肯定少不了。
通俗易懂的说法
反射说白了就是把Java类中的各种成分映射成一个个动态的Java对象
图示讲解
当我们创建一个对象(类)的实例时,我们往往会用到new这个关键字,这就是被
称之为静态创建,而静态创建时的步骤就大致如上图分为三个部分:
1)当我们在静态创建这个实例的时候,JVM会第一时间去本地磁盘调用底层方法找到对应的xxx.class文件
。
2)找到相应的xxx.class文件后会将其加载到JVM当中,在程序运行时,会将xxx.class文件读入运行时内存。
3)此时如果当前内存中没有与之对应的Class对象,也就是唯一的模板时,JVM就会自动帮我们创建一个Class对象,用来对静态创建的实例进行初始化
。
如图是类的正常加载过程:反射的原理在与class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象
。
基本的反射技术
JDK8的开发文档:
重点理解
Class类的实例表示正在运行的Java应用程序中的类和接口
,也就是JVM中有很多个实例,每个类都有与之对应的Class对象(以及八大基本数据类型
)
4个整型: byte、short、int、long
2个浮点型:float、double
1个字符型:char
1个布尔型:boolean
Class没有公共的构造方法,如下进行反射技术的基本使用.
1、三种获取类的Class对象的方法
三种获取Class对象的方法
- 1、Object ——> getClass();
- 2、任何数据类型都有一个静态的’class’属性
- 3、
通过Class类的静态方法:forName(String className)来创建(最常用)
/**
* 功能描述
* 获取Class对象的三种方法
* @author Alascanfu
* @date 2021/12/21
*/
public class Test {
public static void main(String[] args) {
//1、获取当前的类的Class对象 并且 输出Class对象的名称
User user = new User();
Class<? extends User> userClass1 = user.getClass();
System.out.println(userClass1.getName());
//2、通过对应的类.class也可以获得到当前的Class对象
Class<? extends User> userClass2 = User.class;
System.out.println(userClass1 == userClass2);//true
/*3、通过Class类中的forName方法调用两个native底层方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
与 反射类中的 public static native Class<?> getCallerClass();两个底层方法进行获取Class对象
*/
try {
Class<?> userClass3 = Class.forName("com.alascanfu.ReflectTest.User");
System.out.println(userClass3 == userClass1);//true
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
注意
当程序运行时,在运行时内存中,一个类只会自动产生一个Class对象。
2、所有的基本数据类型都有Type属性
Class<?> classByte = Byte.TYPE;
Class<?> classShort = Short.TYPE;
Class<?> classInteger = Integer.TYPE;
Class<?> classLong = Long.TYPE;
3、通过反射获取类的构造函数
User类:
package com.alascanfu.ReflectTest;
public class User {
private long id ;
private String name ;
private String username;
private String password;
private String address;
private String avatar;
private String permission;
User() {
}
protected User(String username){
this.username = "admin";
}
private User(long id) {
this.id = -1L;
}
public User(long id, String name, String username, String password, String address, String avatar, String permission) {
this.id = id;
this.name = name;
this.username = username;
this.password = password;
this.address = address;
this.avatar = avatar;
this.permission = permission;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", address='" + address + '\'' +
", avatar='" + avatar + '\'' +
", permission='" + permission + '\'' +
'}';
}
}
四种方式调用:
public class ConstructorTest {
public static void main(String[] args) throws Exception{
//1、加载Class对象
Class<?> userClass = Class.forName("com.alascanfu.ReflectTest.User");
//2、获取所有公有构造方法
System.out.println("**********************所有公有构造方法*********************************");
Constructor[] conArrayPublicCon = userClass.getConstructors();
for(Constructor c : conArrayPublicCon){
System.out.println(c);
}
//3、获取所有的构造方法
System.out.println("**********************所有构造方法*********************************");
Constructor[] conArrayAllCon = userClass.getDeclaredConstructors();
for(Constructor c : conArrayAllCon){
System.out.println(c);
}
//4、获取公有的指定参数的构造方法
System.out.println("*************从公有的构造方法中获得指定参数类型的公有构造方法******************");
Constructor<?> constructorPublicDirectParams = userClass.getConstructor(Long.TYPE,String.class,String.class,String.class,String.class,String.class,String.class);
System.out.println(constructorPublicDirectParams);
//5、获取所有的且指定参数的构造方法
System.out.println("************从所有构造函数中获取指定参数类型的构造方法***********************");
Constructor<?> constructorAllDirectParams = userClass.getDeclaredConstructor(null);
System.out.println(constructorAllDirectParams);
}
}
4、通过反射调用构造方法
通过Class对象获得到构造函数再来创建实例的过程,也就是我们常说的通过反射调用构造方法
代码实现:
//6、通过上述获得的构造方法创建实例
System.out.println("********************通过上述获得的构造方法创建实例***********************");
Object obj1 = constructorPublicDirectParams.newInstance(1L,"Alascanfu","admin","123456","CQTGU", "XXX.IMG", "Administrator");
System.out.println(obj1);
Object obj2 = constructorAllDirectParams.newInstance();
System.out.println(obj2);
以上是接着3续写,记得查看上下文,这里调用的是指定的构造函数,包括了指定的包含全部形参的公共构造函数
,以及指定的不包含任何参数的构造函数
。
输出结果:
********************通过上述获得的构造方法创建实例***********************
User{id=1, name='Alascanfu', username='admin', password='123456', address='CQTGU', avatar='XXX.IMG', permission='Administrator'}
User{id=0, name='null', username='null', password='null', address='null', avatar='null', permission='null'}
5、通过反射调用类中的方法
通过Class对象利用反射获取类中的方法,可以获取指定参数名的方法,获得到方法之后,还可以通过利用反射创建的实例来执行对应方法。
public Object invoke(Object obj, Object... args)
函数是用来执行方法的 其中第一个参数 是实例对象,第二个参数是参数{},
通过method.invoke(instance,args[])
来执行方法,但是方法是包含权限修饰符的,不是所有方法都可以直接执行的,这里可能会用到破除访问修饰符的利器setAccessible(true)
方法,后面会说,这里先进行理解一下就好。
代码实现:
//7、通过反射调用类中的方法
System.out.println("********************通过反射调用类中所有公有的方法***********************");
Method[] publicMethods = userClass.getMethods();
for (Method method :publicMethods){
System.out.println(method);
}
System.out.println("********************通过反射调用类中指定的公有方法***********************");
Method setId = userClass.getMethod("setId", long.class);
Method getId = userClass.getMethod("getId");
System.out.println(setId.invoke(obj1, 201901094106L));
System.out.println(getId.invoke(obj1));
System.out.println("********************通过反射调用类中所有的方法***********************");
Method[] declaredMethods = userClass.getDeclaredMethods();
for (Method method :declaredMethods){
System.out.println(method);
}
System.out.println("********************通过反射调用类中指定的方法***********************");
Method setPassword = userClass.getDeclaredMethod("setPassword", String.class);
Method getPassword = userClass.getDeclaredMethod("getPassword");
setPassword.setAccessible(true);
getPassword.setAccessible(true);
System.out.println(setPassword.invoke(obj1, "2021-12-22"));
System.out.println(getPassword.invoke(obj1));
6、通过反射调用类中的属性
利用Class对象通过反射技术调用类中的属性,也可为属性进行对应的修改
这里也有用到破除权限修饰符
的神奇能力
代码如下:
/**
* 8、通过反射调用属性
* public int age = 18;
* public String sex = "unknown";
*/
System.out.println("********************通过反射调用类中的所有公有属性***********************");
Field[] publicFields = userClass.getFields();
for (Field field:publicFields){
System.out.println(field);
}
System.out.println("********************通过反射调用类中的指定公有属性***********************");
Field age = userClass.getField("age");
age.set(obj1,21);
System.out.println(obj1);
System.out.println("********************通过反射调用类中的所有属性***********************");
Field[] allFields = userClass.getDeclaredFields();
for (Field field:allFields){
System.out.println(field);
}
System.out.println("********************通过反射调用类中所有属性中的指定属性***********************");
Field password = userClass.getDeclaredField("password");
password.setAccessible(true);
password.set(obj1,"123456");
System.out.println(obj1);
7、打破权限修饰符,这就是反射的可怕之处。setAccessible(true)
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限
。
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
而我们如果通过反射获得到非公有的方法之后,想要进行执行该方法或者说获得到非公有的构造函数,如单例模式下的万恶之源——反射创建的问题
引出都和setAccessible(true)
有关。
利用类对象通过反射技术获取得到的 构造函数,方法,属性都可以通过setAccessible(true)
来破除访问控制符来进行修改以及获取,如果没有获取的话JVM就会报出一个IllegalAccessException:
Exception in thread "main" java.lang.IllegalAccessException: Class com.alascanfu.ReflectTest.ConstructorTest can not access a member of class com.alascanfu.ReflectTest.User with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at com.alascanfu.ReflectTest.ConstructorTest.main(ConstructorTest.java:94)
后面在JUC高并发编程的专题下涉及到JMM(Java Memory Model)与Volatile(可见性、不支持原子性、禁止指令重排)
那块会再次提到单例模式,卖个关子,后面大家一起学习哦~
8、 通过反射技术运行配置文件——JDBC的应用
public class JDBCUtils {
public static Connection getConnection() throws Exception{
//1.加载配置文件
InputStream resourceAsStream = JDBCUtils.class
.getClassLoader()
.getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
//2.读取配置信息
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void main(String[] args) {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(connection);
}
}
jdbc.properties 放在src的工程目录之下,学过JDBC的同学应该都知道哦,这里不再细讲了。
外部配置文件jdbc.properties
user=root
password=123456
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
控制台结果:
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
com.mysql.cj.jdbc.ConnectionImpl@5d740a0f
写在后面
今天室友打了一把英雄联盟的时间总结了一下Java基础——反射的相关基础使用
说实话,反射会在很多底层框架中使用到,如果不会最基本的反射使用,
那很多底层源码都无法读懂,就更不要说理解原理了。
最后的最后 一定要记得实践,不实践 ,等于白学
!
我也坚信 这篇通俗易懂的文章也能获得你的认可~
加油 基础不牢地动山摇
,一定要好好看看哦 一把游戏的时间足以
。
最后
每天进步点 每天收获点
愿诸君 事业有成 学有所获
如果觉得不错 别忘啦一键三连哦~