参考《Java编程思想》反射:运行时的类信息 一章 以及学习阅读其他技术大牛的文章
在运行状态时访问和使用类的信息。
首先我们要了解两个概念:
静态编译 就是在编译时,把所有模块都编译进可执行文件(exe)里,当启动这个可执行文件时,所有模块都被加载进来;
动态编译 是将应用程序需要的模块都编译成动态链接库,启动程序(初始化)时,这些模块不会被加载,运行时用到哪个模块就调用哪个
Java反射机制使用的就是动态编译,在程序运行时,获取类的Class对象,把Class对象加载到JVM中,JVM通过反编译获取到类的信息,然后实例化对象。
动态编译最大限度发挥了Java的灵活性,体现了多态的应用,灵活并松耦合。
Class对象包含了一个类的有关信息,类是程序的一部分,每个类都对应一个Class对象,每当我们编写或是编译一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中),为了生成这个类的对象,运行这个程序的JVM将使用被称为“类加载器”的子系统。
所有的类都是在第一次使用时,动态加载到JVM中的。
当程序创建第一个对类的静态成员引用时,就会加载这个类。
构造器也是类的静态方法,即使在构造器之前并没有使用static关键字,使用new操作符创建类的新对象也会被当作类的静态成员的引用。
——摘自《Java编程思想》
有三种方法,代码如下
public static void main(String[] args) {
//方法一
Class c1 = null;
try {
c1 = Class.forName("com.reflectTest.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c1);
//方法二:使用类字面常量
Class c2 = Student.class;
System.out.println(c2);
//方法三
Student student = new Student();
Class c3 = student.getClass();
System.out.println(c3);
//以上输出的都是:class com.reflectTest.Student
}
Class Class.forName() 方法是Class类的一个static成员,我们可以获取并操作这个Class对象的引用(这也是类加载器的工作)
Class.forName()中必须使用全限定名(包含包名)。
你想在程序运行时使用类型信息,就必须获得Class对象的引用。
//Student类
public class Student {
public String name; //这一行是一个Field对象(成员变量)
private int age;
private int tel;
private int sex;
public String getName() {
return name;
}
private int getAge() {
//定义私有的get方法
return age;
}
}
//测试类
public class FieldTest01 {
Class c2 = Student.class;
//获取field[]
Field[] field = c2.getFields();
System.out.println("成员属性个数="+field.length);
//Output:成员属性个数=1
Field[] field1 = c2.getDeclaredFields();
System.out.println("成员属性个数="+field1.length);
//Output:成员属性个数=4
//获取每个属性的访问修饰符和类型
for(Field f:field1){
//属性修饰符
int i = f.getModifiers(); // 获取访问修饰符的数字代号
String modifiersName = Modifier.toString(i);
//属性类型
Class type = f.getType(); //java.lang.String 注意getType()方法返回类型为Class
String simpleType = type.getSimpleName(); //String
//属性名
String name = f.getName();
System.out.println(modifiersName+" "+simpleType+" "+name);
}
/* Output:
public String name
private int age
private int tel
private int sex
*/
}
getFields()方法:只能获取到public修饰的属性
getDelaredFields()方法:可以获取到全部属性
同样类似的还有getConstructors()和getDeclaredConstructors()、getMethods()和getDeclaredMethods(),这两者分别表示获取某个类的方法、构造函数。
public static void main(String[] args) throws Exception {
Class c = null;
c = c.forName("com.reflectTest.Student");
Object obj = c.newInstance();//底层调用无参构造器实例化对象
Field fName = c.getDeclaredField("name");
fName.set(obj,"张三");
System.out.println(fName.get(obj));
// Output: 张三
Field fAge = c.getDeclaredField("age");
//setAccessible(true)使私有属性外部也可以访问,这是反射机制的缺点,不安全破坏封装
fAge.setAccessible(true);
fAge.set(obj,20);
System.out.println(fAge.get(obj));
// Output: 20
/*没有setAccessible(true)这个设置,应该报错 Class com.reflectTest.FieldTest02 can
not access a member of class com.reflectTest.Student with modifiers "private"*/
}
Class<Student> studentClass = Student.class;
//获取Student中getName的方法
Method getName = studentClass.getDeclaredMethod("getName");
//创建一个student对象,invoke方法需要一个student对象
Student stu = new Student("张三",18);
Object name= getName.invoke(stu); //相当于调用stu.getName()方法
System.out.println(name);
//获取私有的getAge方法
Method getAge = studentClass.getDeclaredMethod("getAge");
//暴力反射,无视访问权限,否则报错
getAge.setAccessible(true);
Object age = getAge.invoke(stu);
System.out.println(age);
/* Output:
张三
18 */
//有参数的方法:setName方法 需要方法名和参数类型
Method setName = studentClass.getDeclaredMethod("setName", String.class);
setName.invoke(stu,"你好"); //...args就是一个可变长度的参数列表,传入的参数跟在后面即可
getDeclaredMethod(“方法名”) 是获取一个方法,invoke是调用。
invoke方法的应用是Spring AOP面向切面编程,在切面方法之前的前后使用invoke调用业务方法。
public class InvokeTest {
public static void main(String[] args) {
Student student = new Student("zhangsan",20);
// invokeMethod(student,"getName",null);
invokeMethod(student,"setName","李四");
}
public static Object invokeMethod(Object obj, String methodName, Object... params) {
long startTime = System.currentTimeMillis();
Object result = null;
Class<?> objClass = obj.getClass();
System.out.println("类:"+obj.getClass().getSimpleName());
System.out.println("方法:"+methodName);
if (params == null || params.length == 0) {
//无参方法
try {
Method method = objClass.getDeclaredMethod(methodName); //获取方法
result = method.invoke(obj); //调用方法
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else {
//有参方法
System.out.println("参数列表:"+ Arrays.toString(params));
int size = params.length;
Class<?>[] classes = new Class[size];
Object[] objArrays = new Object[size];
for (int i = 0; i < size; i++) {
objArrays[i] = params[i];
classes[i] = params[i].getClass();
}
try {
Method method = objClass.getDeclaredMethod(methodName,classes);//获取方法
//获取有参数的方法时,getDeclaredMethod有两个参数:方法名和参数类型params[i].getClass()
result = method.invoke(obj,objArrays);//调用方法
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间:"+ (endTime-startTime));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return result;
}
}
/* Output:
类:Student
方法:setName
参数列表:[李四]
方法执行时间:1
*/
调用invoke方法之前或之后进行一些操作,这样在setName()方法中就可以只实现核心业务代码,使关注点分离出来。详细的可以看这个SpringAOP的理解
List<Student> stuList = new ArrayList<>();
stuList.add(new Student("张三",18));
stuList.add(new Student("李四",18));
stuList.add(new Student("王五",18));
Class<? extends List> stuListClass = stuList.getClass();
Method method = stuListClass.getDeclaredMethod("add",Object.class);
//数字和字符串都可以加进去
method.invoke(stuList,123);
method.invoke(stuList,"123");
for (Student student : stuList) {
System.out.println(student);
}
缺点:
反射能够破坏掉JAVA类本身的封装性,进而获取其私有的或公开的信息,也就能突破封装进而调用私有的或公开的方法,例如这个设置fAge.setAccessible(true);
Java反射机制使用不当,会给JVM性能增加很大的负担
优点:
松耦合,易扩展,使代码灵活,后期方便测试。
Class对象中的一些方法
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.reflectTest.Student");
System.out.println("获取全限定类名"+c.getName());
System.out.println("获取当前类类名"+c.getSimpleName());
System.out.println("获取继承父类名称"+c.getSuperclass());
System.out.println("实现哪些接口"+ Arrays.toString(c.getInterfaces()));
}
/* Output:
获取全限定类名com.reflectTest.Student
获取当前类类名Student
获取继承父类名称class com.reflectTest.People
实现哪些接口[interface java.io.Serializable]
*/
动态代理可以将所有带哦用重定向到调用处理器,因此通常会向调用处理器的构造器传递一个“实例”对象的引用,从而使得调用处理器在执行其中介任务时,可以转发请求。
1、动态代理属于AOP(面向切面编程)的一种思想。
2、代理对象和被代理对象都实现了相同接口。
3、动态代理是指系统根据要代理的接口,自动帮我们生成并编译和加载代理类(实现了要代理接口),并将代理类中的代理逻辑抽象到 InvocationHandler 中,由开发者实现。静态代理中,我们需要自己实现代理类,将代理逻辑也写在代理类里面。而动态代理省去了我们自己写代理类的工作,系统只需要知道要代理的接口,然后利用反射获取接口的方法信息,生成代理类,代理类其实只是些模板代码,而又为了能定制代理逻辑,系统便定义了一个 InvocationHandler,代理类中的方法都会调用 InvocationHandler 中的invoke方法,并将代理类对象,接口的方法对象,方法参数传入。所以真正的代理逻辑在 InvocationHandler 类中;
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
Proxy.newProxyInstance()可以创建动态,并返回代理接口实例,对该实例调用的方法,都会转移到代理类中。这个方法你需要得到一个类加载器(你通常可以从已被加载的对象中获取其类加载器,然后传递给他),一个你希望代理实现的接口列表,以及InvocationHandler接口的一个实现。
loader 参数用来加载自动生成的代理类,interfaces 参数为要代理的接口数组,h参数为我们要自己实现的代理逻辑类。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
InvocationHandler的invoke方法中, proxy参数是系统帮我们生成的代理类对象,method参数为当前调用的方法对象,args为方法的参数。
反射是框架设计的灵魂。
在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制。
Spring中的IOC(控制反转)底层实现就是反射机制。
Spring容器能够帮我们创建实例,这个容器就是应用了反射机制的思想,通过解析XML文件,获取到id属性和class属性里的内容,利用反射原理创建配置文件中的实例对象,存入Spring的bean容器中。
举例:
1、我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;
2、Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:
1)将程序内所有 XML 或 Properties 配置文件加载入内存中;
2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息
3)使用反射机制,获得某个类的Class实例;
4)动态代理实例的属性