栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JavaSE进阶反射机制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE进阶反射机制

Java反射机制,reflect

写在前面:这个教程适合边看边练这种方式,如果你手边没有电脑又是第一次了解这部分知识的,建议不用看了!

反射机制的作用:

通过Java语言的反射机制可以操作字节码文件(class文件),有点类似与黑客(可以读和修改字节码文件),通过反射机制可以操作代码片段。

反射机制相关类的包在:

java.lang.reflect.*

反射机制相关重要的几个类:

java.lang.Class,代表整个字节码,代表一个类型,代表一个类;

java.lang.reflect.Method,代表字节码中的方法字节码,代表类中的方法;

java.lang.reflect.Constructor,代表字节码中的构造方法字节码,代表类中的构造方法;

java.lang.reflect.Field,代表字节码中的属性字节码,代表类中的成员变量(静态和实例变量);

// Class
public class User{
    // Field
    int no;
    // Constructor
    public User(){
        
    }
    public User(int no){
        this.no = no;
    }
    // Method
    public void setNo(int no){
        this.no = no;
    }
    public int getNo(){
        return no;
    }
}
1、获取字节码文件

要操作一个类的字节码,需要首先获取这个类的字节码,怎么获取java.lang.Class实例?

有三种方法:

  • 第一种:Class c = Class.forName(“完整的类名带包名”);
  • 第二种:Class c = 对象.getClass();
  • 第三种:Class c = 任何类型.class;
package javase.reflect;

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {
        
        Class c1 = null;
        Class c2 = null;
        try {
            // c1 代表String.class文件,或者说c1代表String类型
            c1 = Class.forName("java.lang.String");
            // c2 代表Date类型
            c2 = Class.forName("java.util.Date");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // java中任何一个对象都有一个方法(Object中定义的):getClass()
        String s = "abc";
        // x 代表String.class字节码文件,即String类型
        Class x = s.getClass();
        // 这里c1和x两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件,所以返回true
        System.out.println(c1 == x); // true , 双等号比较的两个对象保存的内存地址

        Date time = new Date();
        Class y = time.getClass();
        System.out.println(c2 == y); // true

        // java语言中任何一种类型,包括基本数据类型,它都有.class属性
        Class z = String.class; // z 代表String类型
        Class k = Date.class; // k 代表Date类型
        Class f = int.class; // f 代表int类型
        System.out.println(z == x); // true
    }
}

2、通过反射获取实例

获取到Class能干啥?

  • 通过Class的newInstance()方法来实例化对象。
  • 注意:newInstance()方法内部实际上调用了类的无参数构造方法,必须保证无参构造方法存在才可以。
package javase.reflect;


import javase.bean.User;

public class ReflectTest02 {
    public static void main(String[] args) {
        // 不使用反射机制创建对象
        User user = new User();
        
        // 使用反射机制创建对象,相对于上面的那种方式,反射机制更加灵活!!!
        try {
            // 通过反射机制获取Class,在通过Class来实例化对象
            // 这里的 c 的类型为 User,也可以将 c 理解为 User 的对象
            Class c = Class.forName("javase.bean.User");
            // newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
            
            // 重点: newInstance()方法调用的是类的无参构造方法;
            Object obj = c.newInstance();
            System.out.println(obj);
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

User类

package javase.bean;

public class User {
    String no;
    public User() {
        System.out.println("无参数构造方法被执行了");
    }

    public User(String no) {
        this.no = no;
    }
}
3、通过配置文件创建实例

验证反射机制的灵活性:

  • java代码写一遍,在不修改java源代码的基础之上,可以到不同对象的实例化,非常之灵活!

  • 符合OCP开闭原则:即对扩展开放,对修改关闭。

  • 后期在学习高级框架以及工作的过程之中使用的都是高级框架,如:ssh、ssm…

  • 即:Spring Struts Hibernate 、Spring SpringMVC Mybatis…

  • 这些高级框架底层实现原理都是采用了反射机制。所以反射机制还是很重要的。

package javase.reflect;

import javase.bean.User;

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class ReflectTest03 {
    public static void main(String[] args) {
        // 这种方式代码就写死了,只能创建 User 类型的对象
        User user = new User();

        // 以下代码是灵活的,代码不需要改动,可以在修改配置文件的基础上创建出不同的实例对象。
        // 通过IO流读取classinfo.properties文件
        FileReader reader = null;
        Properties properties = new Properties();
        try {
            reader = new FileReader("javase-reflect/classinfo.properties");
            properties.load(reader);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 通过key获取value
        String className = properties.getProperty("className");
        Object o = null;
        // 通过反射机制创建对象
        try {
            Class c = Class.forName(className);
            o = c.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        System.out.println(o);
    }
}
4、Class.forName()执行过程

研究一下:Class.forName()执行过程放生了什么?

  • 重点:如果只是希望一个类的静态代码块执行,其他代码一律不执行,则可以使用Class.forName(“完整类名”);
  • 这个方法的执行会导致类加载,类加载时,被反射类的静态代码块会被执行;
  • 提示:这个在后面JDBC技术的时候需要。
package javase.reflect;


public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            // Class.forName()这个方法的执行会导致类的加载
            Class.forName("javase.reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass{
    // 静态代码快在类加载时会被执行,并且只执行一次
    static {
        System.out.println("MyClass类的静态代码块执行了");
    }
}

5、类路径下文件的绝对路径

研究文件路径:

  • 怎么获取一个文件的绝对路径?一下讲解的这种方式是通用的,但前提该文件必须是在类路径下才能使用这种方式。
package javase.reflect;

import java.io.FileReader;
import java.io.InputStream;
import java.util.Properties;


public class AboutPath {
    public static void main(String[] args) throws Exception{
        // 这种方式的路径缺点是:移植性查,在IDEA中默认当前路径是project的根。
        // 这种写法只要将文件的位置稍微更换一下,这个路径就无效了。
        // FileReader fileReader = new FileReader("javase-reflect/classinfo.properties");

        
        
        // 这种方式可以获取到类路径下文件的绝对路径
        String dbPath = Thread.currentThread().getContextClassLoader()
                .getResource("javase/bean/db.properties").getPath();
        System.out.println(dbPath);

        // 通过以上的方法获取文件绝对路径变得更加灵活了,下面直接使用流的形式返回文件内容
        // 以前获取文件流的方式为:
        //FileReader fileReader = new FileReader(dbPath);

        // 直接以流的形式返回
        InputStream resource = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("javase/bean/db.properties");
        Properties properties = new Properties();
        properties.load(resource);
        // 关闭流
        resource.close();
        // 通过key获取value
        String username = properties.getProperty("username");
        System.out.println(username);
    }
}
6、资源绑定器
  • java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容;
  • 使用资源绑定器获取的属性配置文件xxx.properties必须位于类路径下(即以src作为根路径)。
package javase.reflect;

import java.util.ResourceBundle;

public class ResourceBundleTest {
    public static void main(String[] args) {
        // 资源绑定器,只能绑定properties文件,并且在写文件路径时,属性配置文件的后缀不用写
        ResourceBundle db = ResourceBundle.getBundle("db");
        ResourceBundle info = ResourceBundle.getBundle("javase/bean/info");
        String username = db.getString("username");
        String service = info.getString("service");
        System.out.println(username);  // root
        System.out.println(service);   // javaweb
    }
}
7、类加载器(了解)

关于JDK中自带的类加载器,什么是类加载器?

专门负责加载类的命令/工具:ClassLoader

JDK中自带了三个类加载器:

启动类加载器:rt.jar

扩展类加载器:ext/*.jar

应用类加载器:classpath

假设有这样一段代码:

String s = "abc";

代码在开始执行之前,会将所需要的类全部加载到JVM当中,通过类加载器加载,看到以上代码类加载器会找到String.class文件,找到就加载,那么是如何加载的呢?

首先通过“启动类加载器”加载:

注意:启动类加载器专门加载:%JAVA_HOME%/jre/lib/rt.jar,rt.jar中都是JDK最核心的类库。

如果通过“启动类加载器”加载不到的时候,会通过“扩展类加载器”加载:

扩展类加载器专门加载:%JAVA_HOME%/jre/lib/ext/*.jar

如果扩展类加载器没有加载到,那么会通过“应用类加载器”加载:

应用类加载器专门加载:classpath中的类

Java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”,当它无法加载到时,再从扩展类加载器中加载,这个称为“母”。双亲委派如果都加载不到,才会考虑从应用类加载器中加载,直到加载到位置。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/976168.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号