java反射真实应用(终于把反射说明白了)

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
    

附录(反射知识)

本附录通过六个方面进行讲解

  1. 反射概念

  2. 获得class

  3. 获得构造器

  4. 获得属性

  5. 获得方法

  6. 获得注解(暂时不考虑)


    反射


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(为了得到标准的对象)

    1. 我们知道所有类的父类是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动态创建不确定的对象原则。


    2. 任何一个类,或者接口,或者枚举,或者基本类型。都有一个属性叫做.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);
          }
      }
      

      这样做我们没办法解耦,也没有办法通过字符串创建对象,需要导包。


    3. 我们得知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)

    • 获取构造方法

    • 通过构造方法创建对象

      1. 一个方法有私有方法,有共有方法,也就是修饰符不同。

      2. 同一个方法名,参数列表不同也是不同的方法。

        		// 获取指定类的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.获得注解(暂不讨论)

猜你喜欢

转载自blog.csdn.net/qq_44112474/article/details/108512986
今日推荐