VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 网络工程 > Linux 教程 >
  • Java反射

  1. Java反射是什么?可以做什么?
  2. Java反射怎么实现?
  3. Java反射需要注意什么?
  4. 下期Spi

1. Java反射是什么?可以做什么?

Java反射从功能上来说可以看做为:仅根据类路径来将类实例化,从而操作该类。
例如,假设现在我们知道有一个放在:java.lang.String。然后我们就可以通过反射,将这个类加载并实例化。
这时候大家或许会有疑问,既然知道这个类在这里,为什么不直接导包然后手动实例化呢?
反射和手动实例化的区别,就是这个类路径。当我们确切的知道类路径时,我们可以轻松的将其实例化。然而在大多数框架中(先让我们站在设计框架的视角),我们其实无从得知需要的类在什么地方。
以Spring为例,Spring如何将类实例化并置于IOC容器中?难道Spring早已预测到我们的包放在哪儿并提前实例化了吗?
事实上当然不可能。我们使用Spring时,为Spring提供了一个包的位置(SpringBoot则是main类所在的包),Spring以此为根递归扫描所有子包,扫描之后,Spring才获取到我们需要置于IOC容器的类路径。
有了类路径就好办了,使用反射就能实例化出来。反射与手动实例化的区别就在这。
实例化之后,还有很多反射的操作,便在代码环节进行介绍。

2. Java反射怎么实现?

接下来是喜闻乐见的代码环节。
我们先准备好一个类用于反射的实验。

package org.jdbc.driver;

public class MyDriver {
    
    private String username;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

}

类放在org.jdbc.driver下,类路径则为org.jdbc.driver.MyDriver。当然自己可以根据个人喜好创建,样例使用这个包名与下期有关,姑且买个关子。

接下来为了方便起见,便在同一个包下写一个main类。

package org.jdbc.driver;

public class Main {
    
    public static void main(String[] args) {
        // 获取包名,使用反射技术时,通常这个类路径无法直接获得,而是由使用框架的用户提供
        // 此处为了方便起见,所以假设已经获取到了用户提供的类路径
        String classPath = "org.jdbc.driver.MyDriver";
        
        try {
            // 得到MyDriver的字节码
            Class<?> classForMyDriver = Class.forName(classPath);
            
            // 根据获取的字节码将该类实例化,实例化后只能是Object对象
            // 因为如果强制转化为MyDriver时,代表我们知道这个类,不符合反射的前提条件
            Object myDriver = classForMyDriver.newInstance();
            
            System.out.println(myDriver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

由此得到一个Object对象。org.jdbc.driver.MyDriver@15db9742,从这里可以看出确实实例化成功了,但仅仅获取到一个Object对象,有什么意义呢?
对于Spring的IOC容器而言,实例化成功已经完成需求了,实例化之后便可以被用户获取并调用。当然实例化一个对象还需要注入对象,MyDriver中就有一个值username,Spring中经常也需要通过注入对象来装配对象。

在Class中,有一个概念叫做Field。它代表着我们常说的成员变量。

package org.jdbc.driver;

import java.lang.reflect.Field;

public class Main {
    
    public static void main(String[] args) {
        
        String classPath = "org.jdbc.driver.MyDriver";
        
        try {
            Class<?> classForMyDriver = Class.forName(classPath);
            
            // 获取声明的成员变量
            Field[] declaredFields = classForMyDriver.getDeclaredFields();
            for (Field field : declaredFields) {
                // 获取成员变量的名称
                System.out.println(field.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

只要获取到字节码Class,我们就可以获取到类的相关信息。包括成员变量,注解(上一节中便是反射获取到的注解),也包括类方法Method
类方法获取方式类似:

public static void main(String[] args) {
    
    String classPath = "org.jdbc.driver.MyDriver";
    
    try {
        Class<?> classForMyDriver = Class.forName(classPath);
        
        // 获取声明的类方法
        Method[] declaredMethods = classForMyDriver.getDeclaredMethods();
        for (Method method : declaredMethods) {
            System.out.println(method.getName());
        }
        
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

既然Java中使用了FeildMethod代表着成员变量和类方法,也就代表着我们可以操作他们,例如为MyDriver初始化。

public static void main(String[] args) {
    
    String classPath = "org.jdbc.driver.MyDriver";
    
    try {
        Class<?> classForMyDriver = Class.forName(classPath);
        
        Object myDriver = classForMyDriver.newInstance();
        // 获取声明的成员变量
        Field[] fields = classForMyDriver.getDeclaredFields();
        for (Field field : fields) {
            // 为myDriver对象的成员变量赋值John
            field.set(myDriver, "John");
        }
        
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

使用Field.set(Object, Object)方法可以为已经实例化的对象赋值。
为了演示,上述样例代码是存在问题的:

  1. 不应该强行为所有成员变量赋值为字符串,因为不是所有变量都能赋值为字符串
  2. 无法得知注入是否成功,没有结果显示。

对于第二个问题,大家可以给MyDriver添加toString方法打印username。

重点说说第一个问题:
我们都知道Spring注入需要使用注解,那为了什么使用了注解就能实现注入呢
从上一篇文章中,我们知道Java可以让我们获取到注解的一些描述。那现在我们把反射和注解结合一下,看看触发了什么样的化学反应!

我们修改一下MyDriver

public class MyDriver {
    
    // 比较熟练注解的同学可以自己尝试创建一个注解并使用在这
    @Resource
    private String username;
    // 用于对比的无注解成员变量
    private String password;

    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public String toString() {
        return "MyDriver [username=" + username + "]";
    }

}

然后回到main

public static void main(String[] args) {
    
    String classPath = "org.jdbc.driver.MyDriver";
    
    try {
        Class<?> classForMyDriver = Class.forName(classPath);
        
        Object myDriver = classForMyDriver.newInstance();
        // 获取声明的成员变量
        Field[] fields = classForMyDriver.getDeclaredFields();
        for (Field field : fields) {
            
            // 成员变量上是否有主键
            if (field.isAnnotationPresent(Resource.class)) {
                System.out.println(field.getName());
            }
        }
        
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
}

从这段代码,我们就可以知道,我们需要注入的是username这个变量。那我们应该向它注入什么数据?
答案来自于Field.getType()方法。通过这个方法,我们可以获取到username的类型描述,然后只需要在IOC容器中查找相同的类(父类、子类)即可。

这么些代码下来,我们可以看见,所谓反射,基本围绕着Class这个类展开操作,我们得到类的Class后,通过Class内部的方法可以获取并操作类内的成员变量、方法等,最后再将其实例化,装配。如果想深入的了解Java反射还有什么内容,多尝试Class的不同方法就足够了。文章篇幅有限,姑且介绍到这。

  1. Java反射需要注意什么
    Java反射因为可以直接操作对象,甚至进行注入的操作,类的安全性自然是要打折扣的。
    其次,使用反射实例化对象效率上不如直接实例化,因此非必要无需使用反射。

反射主要用于框架中,多用于实现微内核+插件化形式。框架设计者无从得知用户提供的类在何处,因此只能通过反射来将用户的类加载进来。了解了Java注解和Java反射之后,我们已经可以自己定义一个简单的自动注入的项目,感兴趣的同学不妨自己尝试一下,算是给自己的巩固作业了。

  1. 下期Spi
    下期准备介绍微内核+插件化的核心思想Spi,会介绍到大家很熟悉的一个类,敬请期待!
原文:https://www.cnblogs.com/mrjanon/p/java-reflect.html

相关教程