java反射(reflect)真实应用(读取解析配置文件)
需求:我们需要读取配置文件,然后根据配置文件信息进行动态的创建连接驱动(mysql或者oracle)
-
理解需求:无法通过new创建驱动对象,因为我们不知道用户给我们传什么参数!
-
解决需求:只能通过动态创建对象解决问题!
-
需求总结:利用反射动态原理,根据用户传入具体参数,创建对应的对象。
需求原型如下图所示(拿properties文件举例)
driverName=edu.xja.demo.MysqlConnection url=jdbc:mysql://localhost:3306/test userName=root password=123456
用户给出对应信息,我们返回连接对象!(解耦)
分3步说明白如何解决问题
1.创建不同的驱动类
2.创建测试类
3.配置不同的信息测试连接
1.创建驱动类,拿MySQL和Oracle举例
-
创建mysql驱动类(简写代码只为突出反射思想,具体实现未写)
/** * 模拟Mysql驱动类 **/ public class MysqlConnection { /** * 为了简化理解 ,我们模仿创建三个连接数据库必须的参数 */ private String url = ""; private String userName = ""; private String password = ""; /** * 获取mysql连接(简写,只为突出反射思想) * @param username 用户名 * @param password 密码 * @param url 连接地址 * @return String */ public String getConnection(String username,String password,String url){ this.url = url; this.userName = username; this.password = password; // 打印出信息,此时证明我们反射成功。 System.out.println("获取mysql驱动:\n username:"+this.userName+"\n password:"+this.password+"\n url:"+this.url); return "mysqlConnection"; } }
-
创建oracle驱动(简写代码只为突出反射思想,具体实现未写)
/** * 模拟oracle驱动类 **/ public class OracleConnection { /** * 为了简化理解 ,我们模仿创建三个连接数据库必须的参数 */ private String url = ""; private String userName = ""; private String password = ""; /** * 获取oracle连接(简写,只为突出反射思想) * @param username 用户名 * @param password 密码 * @param url 连接地址 * @return String */ public String getConnection(String username,String password,String url){ this.url = url; this.userName = username; this.password = password; // 打印出信息,此时证明我们反射成功。 System.out.println("获取oracle驱动: \n username:"+this.userName+"\n password:"+this.password+"\n url:"+this.url); return "oracleConnection"; } }
-
2.创建测试类(重点实现反射),此处代码如有不懂的方法请看本文附录(反射知识)。
/**
* 测试反射
**/
public class TestReflect {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 获取反射类的class
Class cla = Class.forName(getValue("driverName"));
// 获取反射类的获取连接方法(此方法不同的连接必须方法名一致)
Method getConnection = cla.getMethod("getConnection",String.class,String.class,String.class);
Object connection = cla.getConstructor().newInstance();
// 传入参数,操作connection
getConnection.invoke(connection,getValue("userName"),getValue("password"),getValue("url"));
}
/**
* 读取配置文件
* @param key 配置文件中的key
* @return String 配置文件中key对应的value
* @throws IOException 异常
*/
public static String getValue(String key) throws IOException {
Properties properties = new Properties();
// 文件名自定义
FileReader fileReader = new FileReader("jdbc.properties");
properties.load(fileReader);
fileReader.close();
// 在properties文件中的信息是key-value关系
return properties.getProperty(key);
}
}
3.模拟配置文件,测试代码!
-
当用户配置properties的信息为mysql时,如下所示。
driverName=edu.xja.demo.MysqlConnection url=jdbc:mysql://localhost:3306/test userName=root password=123456
打印结果为:
获取mysql驱动: username:root password:123456 url:jdbc:mysql://localhost:3306/test Process finished with exit code 0
-
当用户配置properties的信息为orcal时,如下所示。
driverName=edu.xja.demo.OracleConnection url=jdbc:oracle:thin:@localhost:1521:orcl userName=scott password=trigger
打印结果为:
获取oracle驱动: username:scott password:trigger url:jdbc:oracle:thin:@localhost:1521:orcl Process finished with exit code 0
附录(反射知识)
本附录通过六个方面进行讲解
-
反射概念
-
获得class
-
获得构造器
-
获得属性
-
获得方法
-
获得注解(暂时不考虑)
1.反射概念(reflect)
我们并不能像JavaScript这样的语言可以动态创建对象,例如这样的语法在js中是合法的。
var x = "var a = 3; var b = 4; alert(a+b)";
eval(x);
在我们java中这种语法是不可以的,因为我们的认知不能把字符串也当作java代码!
上面的js代码可以在运行中,自动创建变量等操作,我们认为是动态的。我们Java如果实现动态化,Java一切皆对象。我们拿创建对象来说。
看一下羞涩难懂的定义反射:在java运行状态(不是我们手动new对象)下,对于任意一个类,都能够知道这个类的所有方法和属性;对于任何一个对象,都能都调用它的任意一个方法和属性;这种动态的获取的信息以及动态调用对象的方法的功能叫做Java的反射机制。
看我们上面的这段话,有同学以及优美中国话了。有同学让说人话,那么我谈下我的认知吧!
什么叫动态?说人话就是如果你以及写出来的对象叫做静态,因为不会变了,而且不需要再运行过程中创建新东西,例如:
// User是一个类名
User user = new User();
我已经创建好了,不需要别人来帮我创建,但是这个坏处就能马上体现出来了,例如这样
// 此处Undefined代表未定义的类,我们不确定
Undefined undefined = new Undefined();
有逻辑鬼才可以这样解决,既然我们不确定这个类是什么,那我们定义一个变量就好了!例如这样
// 定义一个变量储存类名
String className = "";
// 例如:我们得到入参为User
className = User;
ClassName className = new ClassName();
不愧是你,不过我们必须唯一确定一个类,不然的话,多个类jvm会报错!然后你又想到全限定类名
// 定义一个变量储存类名
String className = "";
// 例如:我们得到入参为com.fu.entity.User
className = com.fu.entity.User;
ClassName className = new ClassName();
不对啊,这 字符串如何和无参构造的()拼接啊 ,这整体是个字符串啊,java没办法识别啊。好吧!我们不偷鸡摸狗了。我们看下jvm运行原理,从底层了解吧!
我想要个对象 ,行,new 一个。等等…我还没说我要什么对象。我给你个模板,你给我按照这个模板来造一个吧!
我要开始写模板了。我们给出我们大概需要什么对象!如果要多个对象我们用这个 模板复制即可。
package edu.xja.demo;
public class GrilFirend {
private String name = "波多野结衣";
private int age = 18;
public String sex = "女";
public static long phone = 13298107421L;
public GrilFirend(){
}
private GrilFirend(String name){
this.name = name;
}
public void method4(){
System.out.println("执行了public修饰的method1");
}
void method3(String name){
this.name = name;
System.out.println("执行了默认修饰符的method3,name="+this.name);
}
protected void method2(){
System.out.println("执行了protected修饰的method2");
}
private void method1(){
System.out.println("执行了private修饰method1");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "GrilFirend{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
那么这个模板放在哪里啊?因为这个模板是共享的,所以我们把类编译的class对象放在堆中(jvm)。而我们创建对象仅仅利用堆中的class创建一个对象引用class文件。而且会在类的基础上修改!举个例子说明下class和对象的区别和练习。
public class TestClass {
public static void main(String[] args) {
// 基本的类模板,标准的对象
GrilFirend grilFirend = new GrilFirend();
System.out.println(grilFirend);
// 改造特有的对象
grilFirend.setAge(12);
grilFirend.setName("女优");
System.out.println(grilFirend.toString());
}
}
-
我们可以得知,姑且认为我们可以根据class创建出来的对象,根据改造可以得出我们需要的特有的对象。
-
我们第一步获取标准的对象,也就是class。
-
我们对class进行改造,变成我们想要的。
2.获取class(为了得到标准的对象)
-
我们知道所有类的父类是object,object有个共有的方法返回一个class,其方法为getClass。那么我们可以这样用。
List<String> list = new ArrayList<>(); System.out.println(list.getClass());
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbqlLCEt-1599718235499)(C:\Users\13298\Desktop\object.PNG)]
不能new对象的原因是我们new出来对象了,就不需要反射创建对象了,这样违背了我们利用class动态创建不确定的对象原则。
-
任何一个类,或者接口,或者枚举,或者基本类型。都有一个属性叫做.class
import java.util.ArrayList; /** * @author: fudy * @date: 2020/9/10 上午 08:17 * @Decription: **/ public class TestClass { public static void main(String[] args) { Class intClass = int.class; Class listClass = ArrayList.class; System.out.println(intClass); System.out.println(listClass); } }
这样做我们没办法解耦,也没有办法通过字符串创建对象,需要导包。
-
我们得知jvm底层的类加载机制可知,我们通过classpath得到类的全路径名,就可以唯一确定一个类。那么有这个Class类利用ClassLoader.getClassLoader这个机制帮我们根据全限定类名创建一个对象(常用)。
Class stringClass = Class.forName("java.lang.String"); System.out.println("stringClass = " + stringClass);
至此,我们得到class对象了,class对象。class对象有什么用?我们是根据class创建对象,必须知道所有的字段,方法,注解等所有的信息,因为class就是通过类编译出来的。里面class内容会标识所有的类方法,类属性,以及我们这个类的修饰符等等。class就是一面镜子,可以映射出类的所有信息传递给jvm。
3.通过构造方法创建对象(Constructor)
-
获取构造方法
-
通过构造方法创建对象
-
一个方法有私有方法,有共有方法,也就是修饰符不同。
-
同一个方法名,参数列表不同也是不同的方法。
// 获取指定类的class Class cla = Class.forName("edu.xja.demo.GrilFirend"); // 获取所有的public修饰的构造方法 Constructor[] constructors = cla.getConstructors(); // 获取public修饰的无参构造 Constructor constructor = cla.getConstructor(null); // 获取public修饰的无参构造 Constructor constructor1 = cla.getConstructor(); // 获取所有构造方法 Constructor[] declaredConstructors = cla.getDeclaredConstructors(); // 获取特有的构造方法,如果有参数,传入参数的数据类型对应的class Constructor declaredConstructor = cla.getDeclaredConstructor(String.class); // 如果是私有方法,我们需要突破访问修饰符(暴力破解) declaredConstructor.setAccessible(true); // 通过构造方法的newInstance方法创建对象,如果该方法有参数,则需要 传递参数 Object girlFirend = declaredConstructor.newInstance("迪丽热巴"); System.out.println(girlFirend.toString());
4.获得属性值(Field)
??为啥不先写获得方法??,因为有时候我们的方法传参,可能用到我们先获取到的属性值作为入参,所以我们从获得属性值讲起!
public class TestClass { // 这里数据类型必须是包装类型,可以参考java值传递! private static final Long PHONE = 13298107421L; public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 获取指定类的class Class cla = Class.forName("edu.xja.demo.GrilFirend"); // 通过构造函数创建对象 Object girlFirend = cla.getConstructor().newInstance(); // 获取所有public修饰的属性 Field[] fields = cla.getFields(); // 获取指定属性名且被public修饰的属性 Field sex = cla.getField("sex"); // 获取所有属性 Field[] declaredFields = cla.getDeclaredFields(); // 获取任意修饰符指定名字的属性 Field name = cla.getDeclaredField("name"); // 如果是private修饰,我们需要越过检查(暴力破解) name.setAccessible(true); // 我们修改我们创建的girlFirend对象的属性值,得到我们特有的对象 name.set(girlFirend,"仓老师"); // TestClass是这个测试类 Field phone1 = TestClass.class.getDeclaredField("PHONE"); // Field这个类的modifiers属性是private修饰的int值 Field modifiers = Field.class.getDeclaredField("modifiers"); // 放开权限 modifiers.setAccessible(true); // 设置本类的静态字段为可修改 modifiers.setInt(phone1,phone1.getModifiers()&~Modifier.FINAL); // 设置静态变量 phone1.set(null,123L); System.out.println(PHONE); } }
5.获得方法(Method)
// 获取指定类的class Class cla = Class.forName("edu.xja.demo.GrilFirend"); // 通过构造函数创建对象 Object girlFirend = cla.getConstructor().newInstance(); // 获得所有的public修饰的方法 Method[] methods = cla.getMethods(); // 获取指定名称的public修饰的方法 Method getName = cla.getMethod("getName"); // 获取所有的方法 Method[] declaredMethods = cla.getDeclaredMethods(); // 获取指定名字的方法 Method method4 = cla.getDeclaredMethod("method4"); // 方法执行 method4.invoke(girlFirend); // 如果这个方法有入参,需要传入入参的class类型 Method method3 = cla.getDeclaredMethod("method3", String.class); // 执行方法,记得传参 method3.invoke(girlFirend,"日本老师"); // 这个method1是private修饰的方法 Method method1 = cla.getDeclaredMethod("method1"); // 放开权限检查(暴力破解) method1.setAccessible(true); // 执行方法 method1.invoke(girlFirend);
6.获得注解(暂不讨论)
-
-