【面试篇】Java的反射机制以及应用场景
Java反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
1.什么是反射
假如你写了一段代码:Object o=new Object();
运行起来之后!!!
首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。
反射的应用场景:
- 数据库的配置驱动连接上。
我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,用不同的数据库调用连接数据库的时候代码中那几步过程基本一样的,唯一区别就是个驱动名称不一样吧,这样我们就可以把驱动名称通过xml文件配置,代码中class.forName(xml中配置的驱动名称).newInstance()直接使用;这样我们每次用不同的数据库,只需要修改xml
- spring的注解
在使用Spring写业务代码的时候,我们经常会用到注解来进行类的实例化,我们自定义一个注解
GPService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GPService {
String value() default "";
}
我们的业务类QuerySerivce,这里就是模拟Spring的Service注解,我们用一个自定义注解。
@GPService
public class QuerySerivce implements IQueryService {
public String query(String name) {
return "query service result";
}
}
然后我们在容器初始化的时候,我们需要扫描标有service注解的类,然后实例化后放进容器内。大概的代码如下:
//拿到全类名,用于定位类,这一步一般Spring是通过扫描项目路径来获取,这一步是动态获取的,反射的作用其实就在这里,思考下如果不用反射,我们要怎么实例化,不可能一个类一个类去定位,然后实例化
String className = "com.demo.QueryService";
//反射获取类的Class对象
Class<?> clazz=Class.forName(className);
//如果该类标注有GPService注解,我们就实例化这个类
if(clazz.isAnnotationPresent(GPService.class){
Object instance = clazz.newInstance();
//用map来模拟容器
map.put(clazz.getSimpleName(),instance);
}
2.获取Class对象的四种方式
如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
1.知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取Class对象不会进行初始化
2.通过 Class.forName()
传入类的路径获取:(最常用)
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
Class.forName(className)方法,内部实际调用的是一个native方法 forName0(className, true, ClassLoader.getClassLoader(caller), caller);
第2个boolean参数表示类是否需要初始化,Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static块代码执行,static参数也会被再次初始化。
3.通过对象实例instance.getClass()
获取:
Employee e = new Employee();
Class alunbarClass2 = e.getClass();
4.通过类加载器xxxClassLoader.loadClass()
传入类路径获取
class clazz = ClassLoader.LoadClass("cn.javaguide.TargetObject");
通过类加载器获取Class对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
3.反射的代码实例
package com.lcz.reflect;
public class TargetObject {
private String value;
public TargetObject() {
value = "reflect";
}
public void publicMethod(String s){
System.out.println("hello public method!!!" + s);
}
private void privateMethod(){
System.out.println("hello private method!!!" + value);
}
}
package com.lcz.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class reflectDemo{
// 主函数
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
/**
* 获取TargetObject类的Class对象并且创建TargetObject类实例
*/
Class<?> tagetClass = Class.forName("com.lcz.reflect.TargetObject");
TargetObject targetObject = (TargetObject) tagetClass.newInstance();
/**
* 获取所有类中所有定义的方法
*/
Method[] methods = tagetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "reflect test");
/**
* 获取指定参数并对参数进行修改
*/
Field field = tagetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "reflect test");
/**
* 调用 private 方法
*/
Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
输出内容:
4.静态编译和动态编译
- 静态编译: 在编译时确定类型,绑定对象
- 动态编译: 运行时确定类型,绑定对象
5.反射机制优缺点
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 1,性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。2,安全问题,让我们可以动态操作改变类的属性同时也增加了类的安全隐患。
6.反射的应用场景
举例:
- 我们在使用 JDBC 连接数据库时使用
Class.forName()
通过反射加载数据库的驱动程序; - Spring 框架的 IOC(动态加载管理 Bean)创建对象以及 AOP(动态代理)功能都和反射有联系;
- 动态配置实例的属性;
- …