反射概述
反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作对象内部属性及方法。
反射最大的作用之一就在于我们可以不在编译时知道某个对象的类型,而在运行时通过提供完整的 “包名+类名.class” 得到。注意:不是在编译时,而是在运行时。
框架 = 反射 + 注解 + 设计模式
加载完类之后,在堆内存的方法区中就产生了一个Class
类型的对象(一个类只有一个Class
对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。
动态语言 vs 静态语言
动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言:Object-C
、C#
、JavaScript
、PHP
、Python
、Erlang
。
静态语言与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java
、C
、C++
。
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活。
反射相关类
java.lang.Class
:反射的源头
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor
反射优点和缺点
- 优点:可以动态地创建和使用对象(也是框架底层核心)
- 缺点:使用反射基本是解释执行,对执行速度有影响,并且可能破坏类的私有性
Class 类的理解
类的加载过程:程序经过javac.exe
命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe
命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class
的一个实例。
换句话说,Class的实例就对应着一个运行时类,其包含该运行时类的完整结构信息。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类,得到的是同一个Class对象。
获取Class实例的几种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Class clazz1 = Person.class; System.out.println(clazz1);
Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz2);
clazz3 = Class.forName("java.lang.String"); System.out.println(clazz3);
ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("com.zhao.java.Person"); System.out.println(clazz4);
|
补充:ClassLoader
类的使用:使用Classloader加载src目录下的配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Test public void test() throws Exception { Properties pros = new Properties();
此时的文件默认在当前的module下,与src同级。 FileInputStream fis = new FileInputStream("jdbc.properties"); FileInputStream fis = new FileInputStream("src\\jdbc1.properties"); pros.load(fis);
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("jdbc1.properties"); pros.load(is);
String user = pros.getProperty("user"); String password = pros.getProperty("password"); System.out.println("user = " + user + ",password = " + password); }
|
反射应用一:创建运行时类的对象
代码举例
1 2 3
| Class<Person> clazz = Person.class; Person obj = clazz.newInstance(); System.out.println(obj);
|
说明newInstance():
调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
要想此方法正常的创建运行时类的对象,要求:
- 运行时类必须提供空参的构造器
- 空参的构造器的访问权限得够。通常,设置为public。
在javabean中要求提供一个public的空参构造器。原因:
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,子类默认调用super()时能保证父类有默认构造器
反射应用二:获取运行时类的完整结构
通过反射,可以获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
| @Test public void test1(){ Class clazz = Person.class;
Field[] fields = clazz.getFields(); for(Field f : fields){ System.out.println(f); } System.out.println();
Field[] declaredFields = clazz.getDeclaredFields(); for(Field f : declaredFields){ System.out.println(f); } }
@Test public void test1(){ Class clazz = Person.class;
Method[] methods = clazz.getMethods(); for(Method m : methods){ System.out.println(m); } System.out.println(); Method[] declaredMethods = clazz.getDeclaredMethods(); for(Method m : declaredMethods){ System.out.println(m); } }
@Test public void test1(){ Class clazz = Person.class; Constructor[] constructors = clazz.getConstructors(); for(Constructor c : constructors){ System.out.println(c); } System.out.println(); Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); for(Constructor c : declaredConstructors){ System.out.println(c); }
}
@Test public void test2(){ Class clazz = Person.class;
Class superclass = clazz.getSuperclass(); System.out.println(superclass); }
@Test public void test3(){ Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass(); System.out.println(genericSuperclass); }
@Test public void test4(){ Class clazz = Person.class;
Type genericSuperclass = clazz.getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) genericSuperclass; Type[] actualTypeArguments = paramType.getActualTypeArguments(); System.out.println(((Class)actualTypeArguments[0]).getName()); }
@Test public void test5(){ Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces(); for(Class c : interfaces){ System.out.println(c); } System.out.println(); Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); for(Class c : interfaces1){ System.out.println(c); }
}
@Test public void test6(){ Class clazz = Person.class;
Package pack = clazz.getPackage(); System.out.println(pack); }
@Test public void test7(){ Class clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations(); for(Annotation annos : annotations){ System.out.println(annos); } }
|
反射应用三:调用运行时类的指定结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void testField1() throws Exception { Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Field name = clazz.getDeclaredField("name");
name.setAccessible(true); name.set(p,"Tom");
System.out.println(name.get(p)); }
|
调用指定的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Test public void testMethod() throws Exception { Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method show = clazz.getDeclaredMethod("show", String.class); show.setAccessible(true);
Object returnValue = show.invoke(p,"CHN"); System.out.println(returnValue);
System.out.println("*************如何调用静态方法*****************");
Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); Object returnVal = showDesc.invoke(Person.class); System.out.println(returnVal); }
|
调用指定的构造器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void testConstructor() throws Exception { Class clazz = Person.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person per = (Person) constructor.newInstance("Tom"); System.out.println(per);
}
|
反射应用四:动态代理
代理模式的原理
使用一个代理将对象包装起来,,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理
举例:实现Runnable接口的方法创建多线程。
1 2 3 4 5 6 7 8
| Class MyThread implements Runnable{} Class Thread implements Runnable{}
main(){ MyThread t = new MyThread(); Thread thread = new Thread(t); thread.start(); }
|
静态代理的缺点
- 代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展;
- 每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。
动态代理的特点
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
动态代理的实现
需要解决的两个主要问题:
- 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象(通过
Proxy.newProxyInstance()
实现)
- 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a(通过
InvocationHandler
接口的实现类及其方法invoke()
)
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| interface Human{ String getBelief(); void eat(String food); }
class SuperMan implements Human{ @Override public String getBelief() { return "I believe I can fly!"; }
@Override public void eat(String food) { System.out.println("我喜欢吃" + food); } }
class HumanUtil{ public void method1(){ System.out.println("====================通用方法一===================="); }
public void method2(){ System.out.println("====================通用方法二===================="); } }
class ProxyFactory{ public static Object getProxyInstance(Object obj){ MyInvocationHandler handler = new MyInvocationHandler(); handler.bind(obj); return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler); } }
class MyInvocationHandler implements InvocationHandler{ private Object obj; public void bind(Object obj){ this.obj = obj; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { HumanUtil util = new HumanUtil(); util.method1();
Object returnValue = method.invoke(obj,args);
util.method2();
return returnValue; } }
public class ProxyTest {
public static void main(String[] args) { SuperMan superMan = new SuperMan(); Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); String belief = proxyInstance.getBelief(); System.out.println(belief); proxyInstance.eat("四川麻辣烫");
System.out.println("*****************************");
NikeClothFactory nikeClothFactory = new NikeClothFactory(); ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory); proxyClothFactory.produceCloth(); } }
|
动态代理的应用:反射技术常用在各类通用框架开发中。因为为了保证框架的通用性,需要根据配置文件加载不同的对象或类,并调用不同的方法,这个时候就会用到反射——运行时动态加载需要加载的对象。例如MyBatis中sqlSession
返回的每个mapper
对象都是一个代理类。