1、反射的概念
Java反射是指在运行时动态地获取一个类的信息,包括其方法、属性、构造器等,并且可以在运行时修改类的行为(在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息)。Java反射主要是通过java.lang.reflect包下的类实现的。
通过反射的能力,可以让Java语言知识动态获取程序的信息,以及动态调用方法的能力。在Java里面专门有一个叫java.lang.reflect这样一个包,来实现反射相关的一些类库,包括Construct、Field、Method、Class等这样一些类,分别用来获取类的构造方法、成员变量、方法信息和属性方法。
反射使用的场景还挺多的,比如在动态代理这样一个场景里面,使用动态深层的代理类,来提升代码的复用性。在Spring框架里面有大量用到反射,比如用反射来实例化Bean对象等等。
补充:
- 编译期:编译器把源代码翻译成机器码
- 运行期:将可执行文件交给操作系统去执行
2、反射的实现方式(获取Class对象的几种方法)
(1)调用某个类的class属性来获取该类对应的Class对象(知道具体类的情况下可以使用)。类名.class:
Class class1 = Person.class;//获取到类对象
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化。
(2)调用某个对象的getClass()方法。对象名.getClass():
Person p =new Person();
Class class2 = p.getClass();
(3)通过包名,调用class的forName方法(最安全/性能最好)。Class.forName(“类的路径”):
Class class3 = Class.forName("day07.Person");
(4)通过类加载器 xxxClassLoader.loadClass() 传入类路径获取
class clazz = ClassLoader.LoadClass("com.xxx.TargetObject");
其中第3种方式是用的最多,只知道包名类名,其他啥也拿不到那种;个人认为这才是反射存在的意义。
3、new和反射创建有什么区别?
除了使用new创建对象之外,还可以用什么方法创建对象?——使用Java反射可以创建对象!
- new:静态编译,在编译期就将模块编译进来,执行该字节码文件,所有的模块都被加载;
- 反射:动态编译,编译期没有加载,等到模块被调用时才加载;
注:spring的ioc就用到反射机制,newInstance创建。更加的通用,并且降低耦合,避免了硬编码,只需提供一个字符串就可以动态的创建。
String className = readfromXMlConfig;//从xml 配置文件中获得字符串
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
举例: 有自行车🚲,小轿车🚗。(1)静态:上班将自行车放车里去上班,一定得带着,一个不能落下;(2)动态:将自行车放家里,交通堵塞骑自行车去,车放家里。
拓展:Java反射创建对象效率高还是通过new创建对象的效率高?
答:通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低
4、反射的优缺点
优点:
- 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作(面试时可以先说后半句,再说前半句,似乎会流畅一些)
- 提高代码的复用率,比如动态代理,就是用到了反射来实现
- 可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用
缺点:
- 性能问题
- 使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。
- 反射会涉及到动态类型的解析,所以JVM无法对这些代码进行优化,导致性能要比非反射调用更低。
- 模糊内部逻辑
- 程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
- 内部暴露
- 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用——代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
- 安全问题
- 相对不安全,破坏了封装性(因为通过反射可以获得私有方法和属性)
5、反射提供了哪些功能?(可以实现什么)
- 在运行时,能够通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息
- 在运行时,可以通过反射创建任意一个类的实例,并访问该实例的成员
- 在运行时,可以通过反射机制生成一个类的动态代理类或动态代理对象
6、实现Java反射的类:
- Class:表示正在运行的Java应用程序中的类和接口(注意: 所有获取对象的信息都需要Class类来实现)
- Field:提供有关类和接口的属性信息,以及对它的动态访问权限。
- Constructor:提供关于类的单个构造方法的信息以及它的访问权限
- Method:提供类或接口中某个方法的信息
7、反射 API
反射 API 用来生成 JVM 中的类、接口或者对象的信息。
- Class 类:反射的核心类,可以获取类的属性,方法等信息。
- Field 类:Java.lang.reflect 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method 类:Java.lang.reflect 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor 类:Java.lang.reflect 包中的类,表示类的构造方法。
8、反射使用步骤(获取Class对象、调用对象方法)
- 获取想要操作的类的 Class 对象,他是反射的核心,通过 Class 对象我们可以任意调用类的方法。
- 调用 Class 类中的方法,既就是反射的使用阶段。
- 使用反射 API 来操作这些信息。
9、利用反射动态创建对象实例
Class 对象的 newInstance()
-
使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器。
调用 Constructor 对象的 newInstance()
-
newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
10、为什么要引入反射?
- 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
- 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
- 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率
11、反射的应用场景
像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
JDK实现动态代理
下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 Method 来调用指定的方法。
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
注解/XML配置
另外,像 Java 中的一大利器 注解 的实现也用到了反射。(XML配置也用到了反射)
为什么你使用 Spring 的时候 ,一个@Component注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 @Value注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
//获取类名
String className = "com.example.demo.User";
try {
//使用反射机制加载类
Class clazz = Class.forName(className);
//创建对象实例
Object obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
使用 JDBC 连接数据库
在 JDBC 的操作中,如果要想进行数据库的连接,则需要先通过反射机制加载数据库的驱动程序。步骤如下:
- 通过 Class.forName() 加载数据库的驱动程序 (通过反射加载)
- 通过 DriverManager 类连接数据库,参数包含数据库的连接地址、用户名、密码
- 通过 Connection 接口接收连接
- 关闭连接
public static void main(String[] args) throws Exception {
Connection con = null; // 数据库的连接对象
// 1. 通过反射加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 2. 连接数据库
con = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test","root","root");
// 3. 关闭数据库连接
con.close();
}
Spring 框架
(实际上是因为使用了动态代理,所以才和反射机制有关)
反射机制是 Java 框架设计的灵魂,框架的内部都已经封装好了,我们自己基本用不着写。典型的除了Hibernate 之外,还有 Spring 也用到了很多反射机制,最典型的就是 Spring 通过 xml 配置文件装载 Bean(创建对象),也就是 Spring 的 IoC,过程如下:
- 加载配置文件,获取 Spring 容器
- 使用反射机制,根据传入的字符串获得某个类的 Class 实例
// 获取 Spring 的 IoC 容器,并根据 id 获取对象
public static void main(String[] args) {
// 1.使用 ApplicationContext 接口加载配置文件,获取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
// 2. 使用反射机制,根据这个字符串获得某个类的 Class 实例
IAccountService aService = (IAccountService) ac.getBean("accountServiceImpl");
System.out.println(aService);
}
另外,Spring AOP 由于使用了动态代理,所以也使用了反射机制
参考文章:请说说你对反射的了解