反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(4);
上面这样子进行类对象的初始化,我们可以理解为「正」。
而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。
这时候,我们使用 JDK 提供的反射 API 进行反射调用:
Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。
所以说什么是反射?
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
1、什么是反射?
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态(或准动态,为啥要说是准动态,因为一般而言的动态语言定义是程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。)语言的一个关键性质。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.。
反射就是把java类中的各种成分映射成一个个的Java对象。
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中都有一个类来描述)如图是类的正常加载过程:反射的原理在与class对象。熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
2、Class类的常用方法
方法名 | 功能说明 |
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该Class对象的一个实例 |
Object newInstance(Object []args) | 调用当前格式构造函数,返回该Class对象的一个实例 |
getName() | 返回此Class对象所表示的实体(类、接口、数组类、基本类型或void)名称 |
Class getSuperClass() | 返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此Class所表示的实体的超类的Class |
3、反射能做什么?
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
4、反射的具体实现
下面是一个基本的类 Person
1 package com.ys.reflex;
2 public class Person {
3 //私有属性
4 private String name = "Tom";
5 //公有属性
6 public int age = 18;
7 //构造方法
8 public Person() {
9 }
10 //私有方法
11 private void say(){
12 System.out.println("private say()...");
13 }
14 //公有方法
15 public void work(){
16 System.out.println("public work()...");
17 }
18 }
①、得到 Class 的三种方式
1 //1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
2 // 类型的对象,而我不知道你具体是什么类,用这种方法
3 Person p1 = new Person();
4 Class c1 = p1.getClass();
5
6 //2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
7 // 这说明任何一个类都有一个隐含的静态成员变量 class
8 Class c2 = Person.class;
9
10 //3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
11 // 但可能抛出 ClassNotFoundException 异常
12 Class c3 = Class.forName("com.ys.reflex.Person");
需要注意的是:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true
②、通过 Class 类获取成员变量、成员方法、接口、超类、构造方法等
查阅 API 可以看到 Class 有很多方法:
getName():获得类的完整名字。
getFields():获得类的public类型的属性。
getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
getMethods():获得类的public类型的方法。
getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
getConstructors():获得类的public类型的构造方法。
getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
我们通过一个例子来综合演示上面的方法:
1 //获得类完整的名字
2 String className = c2.getName();
3 System.out.println(className);//输出com.ys.reflex.Person
4
5 //获得类的public类型的属性。
6 Field[] fields = c2.getFields();
7 for(Field field : fields){
8 System.out.println(field.getName());//age
9 }
10
11 //获得类的所有属性。包括私有的
12 Field [] allFields = c2.getDeclaredFields();
13 for(Field field : allFields){
14 System.out.println(field.getName());//name age
15 }
16
17 //获得类的public类型的方法。这里包括 Object 类的一些方法
18 Method [] methods = c2.getMethods();
19 for(Method method : methods){
20 System.out.println(method.getName());//work waid equls toString hashCode等
21 }
22
23 //获得类的所有方法。
24 Method [] allMethods = c2.getDeclaredMethods();
25 for(Method method : allMethods){
26 System.out.println(method.getName());//work say
27 }
28
29 //获得指定的属性
30 Field f1 = c2.getField("age");
31 System.out.println(f1);
32 //获得指定的私有属性
33 Field f2 = c2.getDeclaredField("name");
34 //启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
35 f2.setAccessible(true);
36 System.out.println(f2);
37
38 //创建这个类的一个对象
39 Object p2 = c2.newInstance();
40 //将 p2 对象的 f2 属性赋值为 Bob,f2 属性即为 私有属性 name
41 f2.set(p2,"Bob");
42 //使用反射机制可以打破封装性,导致了java对象的属性不安全。
43 System.out.println(f2.get(p2)); //Bob
44
45 //获取构造方法
46 Constructor [] constructors = c2.getConstructors();
47 for(Constructor constructor : constructors){
48 System.out.println(constructor.toString());//public com.ys.reflex.Person()
49 }
4、根据反射获取父类属性
父类 Parent.java
1 public class Parent { 2 public String publicField = "parent_publicField"; 3 protected String protectField = "parent_protectField"; 4 String defaultField = "parent_defaultField"; 5 private String privateField = "parent_privateField"; 6 7 }
子类 Son.java
1 public class Son extends Parent { 2 }
测试类:
1 public class ReflectionTest { 2 3 @Test 4 public void testGetParentField() throws Exception{ 5 Class c1 = Class.forName("com.ys.model.Son"); 6 //获取父类私有属性值 7 System.out.println(getFieldValue(c1.newInstance(),"privateField")); 8 } 9 10 public static Field getDeclaredField(Object obj,String fieldName) { 11 Field field = null; 12 Class c = obj.getClass(); 13 for(; c != Object.class ; c = c.getSuperclass()){ 14 try { 15 field = c.getDeclaredField(fieldName); 16 field.setAccessible(true); 17 return field; 18 }catch (Exception e){ 19 //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。 20 //如果这里的异常打印或者往外抛,则就不会执行c = c.getSuperclass(),最后就不会进入到父类中了 21 } 22 } 23 return null; 24 } 25 public static Object getFieldValue(Object object,String fieldName) throws Exception{ 26 Field field = getDeclaredField(object,fieldName); 27 28 return field.get(object); 29 } 30 }
通过执行上述代码,我们获得了父类的私有属性值,这里要注意的是直接通过反射获取子类的对象是不能得到父类的属性值的,必须根据反射获得的子类 Class 对象在调用 getSuperclass() 方法获取父类对象,然后在通过父类对象去获取父类的属性值。
5、动态代理的概述和实现
动态代理:一种设计模式,其非常简单,很容易理解,你自己可以做这件事,但是觉得自己做非常麻烦或者不方便,所以就叫一个另一个人(代理)来帮你做这个事情,而你就不用管了,这就是动态代理。举个例子,买火车票叫人代买。
在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理。
1、静态代理
静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
2、静态代理简单实现
根据上面代理模式的类图,来写一个简单的静态代理的例子。我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费,
班长就是学生的代理。
首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。
/** * 创建Person接口 * @author Gonjan */ public interface Person { //上交班费 void giveMoney(); }
Student类实现Person接口。Student可以具体实施上交班费的动作。
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { System.out.println(name + "上交班费50元"); } }
StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。
/** * 学生代理类,也实现了Person接口,保存一个学生实体,这样既可以代理学生产生行为 * @author Gonjan * */ public class StudentsProxy implements Person{ //被代理的学生 Student stu; public StudentsProxy(Person stu) { // 只代理学生对象 if(stu.getClass() == Student.class) { this.stu = (Student)stu; } } //代理上交班费,调用被代理学生的上交班费行为 public void giveMoney() { stu.giveMoney(); } }
下面测试一下,看如何使用代理模式:
public class StaticProxyTest { public static void main(String[] args) { //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成 Person zhangsan = new Student("张三"); //生成代理对象,并将张三传给代理对象 Person monitor = new StudentsProxy(zhangsan); //班长代理上交班费 monitor.giveMoney(); } }
运行结果:
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。
代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。上面说到,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:
public class StudentsProxy implements Person{ //被代理的学生 Student stu; public StudentsProxy(Person stu) { // 只代理学生对象 if(stu.getClass() == Student.class) { this.stu = (Student)stu; } } //代理上交班费,调用被代理学生的上交班费行为 public void giveMoney() { System.out.println("张三最近学习有进步!"); stu.giveMoney(); } }
运行结果:
可以看到,只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
三、动态代理
1.动态代理
代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法:
public void giveMoney() { //调用被代理方法前加入处理方法 beforeMethod(); stu.giveMoney(); }
这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果出了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。那看看下面动态代理如何实现。
2、动态代理简单实现
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
创建一个动态代理对象步骤,具体代码见后面:
- 创建一个InvocationHandler对象
//创建一个与代理对象相关联的InvocationHandler InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
- 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass
Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
- 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
- 通过构造器constructor来创建一个动态实例stuProxy
Person stuProxy = (Person) cons.newInstance(stuHandler);
就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:
//创建一个与代理对象相关联的InvocationHandler InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu); //创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法 Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。
首先是定义一个Person接口:
/** * 创建Person接口 * @author Gonjan */ public interface Person { //上交班费 void giveMoney(); }
创建需要被代理的实际类:
public class Student implements Person { private String name; public Student(String name) { this.name = name; } @Override public void giveMoney() { try { //假设数钱花了一秒时间 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name + "上交班费50元"); } }
再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。
public class MonitorUtil { private static ThreadLocal<Long> tl = new ThreadLocal<>(); public static void start() { tl.set(System.currentTimeMillis()); } //结束时打印耗时 public static void finish(String methodName) { long finishTime = System.currentTimeMillis(); System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms"); } }
创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。
public class StuInvocationHandler<T> implements InvocationHandler { //invocationHandler持有的被代理对象 T target; public StuInvocationHandler(T target) { this.target = target; } /** * proxy:代表动态代理对象 * method:代表正在执行的方法 * args:代表调用目标方法时传入的实参 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理执行" +method.getName() + "方法"); */ //代理过程中插入监测方法,计算该方法耗时 MonitorUtil.start(); Object result = method.invoke(target, args); MonitorUtil.finish(method.getName()); return result; } }
做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:
public class ProxyTest { public static void main(String[] args) { //创建一个实例对象,这个对象是被代理的对象 Person zhangsan = new Student("张三"); //创建一个与代理对象相关联的InvocationHandler InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan); //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法 Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler); //代理执行上交班费的方法 stuProxy.giveMoney(); } }
我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。
运行结果:
上面说到,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
动态代理的过程,代理对象和被代理对象的关系不像静态代理那样一目了然,清晰明了。因为动态代理的过程中,我们并没有实际看到代理类,也没有很清晰地的看到代理类的具体样子,而且动态代理中被代理对象和代理对象是通过InvocationHandler来完成的代理过程的,其中具体是怎样操作的,为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行。带着这些问题,我们就需要对java动态代理的源码进行简要的分析,弄清楚其中缘由。
4、反射总结
灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!
参考:
https://www.cnblogs.com/whgk/p/6122036.html
https://www.cnblogs.com/ysocean/p/6516248.html