一、反射概念
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
通过下面的代码初步理解反射机制,通过这个大概理解反射机制,不需要每行代码都理解,下文会细讲。创建了一个Person类。
1 public class Person { 2 3 private String name; 4 public int age; 5 6 @Override 7 public String toString() { 8 return "Person{" + 9 "name='" + name + '\'' + 10 ", age=" + age + 11 '}'; 12 } 13 14 public String getName() { 15 return name; 16 } 17 18 public void setName(String name) { 19 this.name = name; 20 } 21 22 public int getAge() { 23 return age; 24 } 25 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 public Person(String name, int age) { 31 32 this.name = name; 33 this.age = age; 34 } 35 36 private Person(String name) { 37 this.name = name; 38 } 39 40 public Person() { 41 System.out.println("Person()"); 42 } 43 44 public void show(){ 45 System.out.println("你好,我是一个人"); 46 } 47 48 private String showNation(String nation){ 49 System.out.println("我的国籍是:" + nation); 50 return nation; 51 } 52 }
1 public class ReflectionTest { 2 3 //反射之前,对于Person的操作 4 @Test 5 public void test1(){ 6 //1.创建Person类的对象 7 Person p1 = new Person("Tom", 12); 8 9 //2.通过对象,调用其内部的属性、方法 10 p1.age = 10; 11 System.out.println(p1.toString()); 12 13 p1.show(); 14 15 //在Person类外部,不可以通过Person类的对象调用其内部私有结构。 16 //比如:name、showNation()以及私有的构造器 17 } 18 19 //反射之后,对于Person的操作 20 @Test 21 public void test2() throws Exception{ 22 23 Class clazz = Person.class; 24 //1.通过反射,创建Person类的对象 25 // Person p1 = (Person) clazz.newInstance(); 26 Constructor constructor = clazz.getConstructor(String.class, int.class); 27 Person p1 = (Person) constructor.newInstance("Tom", 12); 28 System.out.println(p1.toString()); 29 30 //2.通过反射,调用对象指定的属性、方法 31 //调用属性 32 Field age = clazz.getDeclaredField("age"); 33 age.set(p1, 12); 34 System.out.println(p1.toString()); 35 36 //调用方法 37 Method show = clazz.getDeclaredMethod("show"); 38 show.invoke(p1); 39 40 System.out.println("*******************************"); 41 42 //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性 43 //调用私有的构造器 44 Constructor constructor1 = clazz.getDeclaredConstructor(String.class); 45 constructor1.setAccessible(true); 46 Person p2 = (Person) constructor1.newInstance("Jerry"); 47 System.out.println(p2.toString()); 48 49 //调用私有的属性 50 Field name = clazz.getDeclaredField("name"); 51 name.setAccessible(true); 52 name.set(p2, "HaiMeimei"); 53 System.out.println(p2.toString()); 54 55 //调用私有的方法 56 Method showNation = clazz.getDeclaredMethod("showNation", String.class); 57 showNation.setAccessible(true); 58 String nation = (String) showNation.invoke(p2, "中国"); 59 System.out.println(nation); 60 } 61 }
从上述代码可以大概了解到Java反射机制提供的功能有
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造器
二、 Class类的理解
1.类的加载过程:程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
2.换句话说,Class的实例就对应着一个运行时类。
3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
三、获取Class实例的几种方式:(前三种方式需要掌握)
1 @Test 2 public void test3() throws ClassNotFoundException { 3 //方式一:调用运行时类的属性:.class 4 Class clazz1 = Person.class; 5 System.out.println(clazz1); 6 7 //方式二:通过运行时类的对象,调用getClass() 8 Person p = new Person(); 9 Class clazz2 = p.getClass(); 10 System.out.println(clazz2); 11 12 //方式三:调用Class的静态方法:forName(String classPath) 13 Class clazz3 = Class.forName("com.test.java.Person"); 14 System.out.println(clazz3); 15 16 System.out.println(clazz1 == clazz2); 17 System.out.println(clazz1 == clazz3); 18 19 //方式四:使用类的加载器:ClassLoader (了解) 20 ClassLoader classLoader = ReflectionTest.class.getClassLoader(); 21 Class clazz4 = classLoader.loadClass("com.test.java.Person"); 22 System.out.println(clazz4); 23 24 System.out.println(clazz1 == clazz4); 25 }
四、创建运行时类的对象
创建类的对象:调用Class对象的newInstance()方法
要求:
1)类必须有一个无参数的构造器。
2)类的构造器的访问权限需要足够。
代码举例:
1 Class<Person> clazz = Person.class; 2 Person obj = clazz.newInstance(); 3 System.out.println(obj);
难道没有无参的构造器就不能创建对象了吗?不是!只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,才可以实例化操作。
步骤如下:
1)通过Class类的getDeclaredConstructor(Class ... parameterTypes)取得本类的指定形参类型的构造器
2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
3)通过Constructor实例化对象。
1 //1.获取对应的Class对象 2 Class clazz= Class.forName(Person); 3 4 //2.调用指定参数结构的构造器,生成Constructor的实例 5 Constructor con = clazz.getDeclaredConstructor(String.class,Integer.class); 6 7 //3.通过Constructor的实例创建对应类的对象,并初始化类属性 8 Person p2 = (Person) con.newInstance("Peter",20); 9 System.out.println(p2)
五、获取运行时类的完整结构
我们可以通过反射,获取对应的运行时类中所有的属性(Field)、方法(Method)、构造器(Constructor)、父类(Superclass)、接口(Interface)、父类的泛型、包(package)、注解(Annotation)、异常等。。。。
获取属性:
1 /** 2 * 获取当前运行时类的属性结构 3 * 4 */ 5 public class FieldTest { 6 7 @Test 8 public void test1(){ 9 10 Class clazz = Person.class; 11 12 //获取属性结构 13 //getFields():获取当前运行时类及其父类中声明为public访问权限的属性 14 Field[] fields = clazz.getFields(); 15 for(Field f : fields){ 16 System.out.println(f); 17 } 18 System.out.println(); 19 20 //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性) 21 Field[] declaredFields = clazz.getDeclaredFields(); 22 for(Field f : declaredFields){ 23 System.out.println(f); 24 } 25 } 26 27 //权限修饰符 数据类型 变量名 28 @Test 29 public void test2(){ 30 Class clazz = Person.class; 31 Field[] declaredFields = clazz.getDeclaredFields(); 32 for(Field f : declaredFields){ 33 //1.权限修饰符 34 int modifier = f.getModifiers(); 35 System.out.print(Modifier.toString(modifier) + "\t"); 36 37 //2.数据类型 38 Class type = f.getType(); 39 System.out.print(type.getName() + "\t"); 40 41 //3.变量名 42 String fName = f.getName(); 43 System.out.print(fName); 44 45 System.out.println(); 46 } 47 48 } 49 50 }
获取方法:
1 /** 2 * 获取运行时类的方法结构 3 * 4 */ 5 public class MethodTest { 6 7 @Test 8 public void test1(){ 9 10 Class clazz = Person.class; 11 12 //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法 13 Method[] methods = clazz.getMethods(); 14 for(Method m : methods){ 15 System.out.println(m); 16 } 17 System.out.println(); 18 //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法) 19 Method[] declaredMethods = clazz.getDeclaredMethods(); 20 for(Method m : declaredMethods){ 21 System.out.println(m); 22 } 23 } 24 25 /* 26 @Xxxx 27 权限修饰符 返回值类型 方法名(参数类型1 形参名1,...) throws XxxException{} 28 */ 29 @Test 30 public void test2(){ 31 Class clazz = Person.class; 32 Method[] declaredMethods = clazz.getDeclaredMethods(); 33 for(Method m : declaredMethods){ 34 //1.获取方法声明的注解 35 Annotation[] annos = m.getAnnotations(); 36 for(Annotation a : annos){ 37 System.out.println(a); 38 } 39 40 //2.权限修饰符 41 System.out.print(Modifier.toString(m.getModifiers()) + "\t"); 42 43 //3.返回值类型 44 System.out.print(m.getReturnType().getName() + "\t"); 45 46 //4.方法名 47 System.out.print(m.getName()); 48 System.out.print("("); 49 //5.形参列表 50 Class[] parameterTypes = m.getParameterTypes(); 51 if(!(parameterTypes == null && parameterTypes.length == 0)){ 52 for(int i = 0;i < parameterTypes.length;i++){ 53 54 if(i == parameterTypes.length - 1){ 55 System.out.print(parameterTypes[i].getName() + " args_" + i); 56 break; 57 } 58 59 System.out.print(parameterTypes[i].getName() + " args_" + i + ","); 60 } 61 } 62 63 System.out.print(")"); 64 65 //6.抛出的异常 66 Class[] exceptionTypes = m.getExceptionTypes(); 67 if(exceptionTypes.length > 0){ 68 System.out.print("throws "); 69 for(int i = 0;i < exceptionTypes.length;i++){ 70 if(i == exceptionTypes.length - 1){ 71 System.out.print(exceptionTypes[i].getName()); 72 break; 73 } 74 75 System.out.print(exceptionTypes[i].getName() + ","); 76 } 77 } 78 79 80 System.out.println(); 81 } 82 83 } 84 }
获取其他结构:
1 /** 2 */ 3 public class OtherTest { 4 5 /* 6 获取构造器结构 7 */ 8 @Test 9 public void test1(){ 10 11 Class clazz = Person.class; 12 //getConstructors():获取当前运行时类中声明为public的构造器 13 Constructor[] constructors = clazz.getConstructors(); 14 for(Constructor c : constructors){ 15 System.out.println(c); 16 } 17 18 System.out.println(); 19 //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器 20 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); 21 for(Constructor c : declaredConstructors){ 22 System.out.println(c); 23 } 24 25 } 26 27 /* 28 获取运行时类的父类 29 30 */ 31 @Test 32 public void test2(){ 33 Class clazz = Person.class; 34 35 Class superclass = clazz.getSuperclass(); 36 System.out.println(superclass); 37 } 38 39 /* 40 获取运行时类的带泛型的父类 41 42 */ 43 @Test 44 public void test3(){ 45 Class clazz = Person.class; 46 47 Type genericSuperclass = clazz.getGenericSuperclass(); 48 System.out.println(genericSuperclass); 49 } 50 51 /* 52 获取运行时类的带泛型的父类的泛型 53 54 55 代码:逻辑性代码 vs 功能性代码 56 */ 57 @Test 58 public void test4(){ 59 Class clazz = Person.class; 60 61 Type genericSuperclass = clazz.getGenericSuperclass(); 62 ParameterizedType paramType = (ParameterizedType) genericSuperclass; 63 //获取泛型类型 64 Type[] actualTypeArguments = paramType.getActualTypeArguments(); 65 // System.out.println(actualTypeArguments[0].getTypeName()); 66 System.out.println(((Class)actualTypeArguments[0]).getName()); 67 } 68 69 /* 70 获取运行时类实现的接口 71 */ 72 @Test 73 public void test5(){ 74 Class clazz = Person.class; 75 76 Class[] interfaces = clazz.getInterfaces(); 77 for(Class c : interfaces){ 78 System.out.println(c); 79 } 80 81 System.out.println(); 82 //获取运行时类的父类实现的接口 83 Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); 84 for(Class c : interfaces1){ 85 System.out.println(c); 86 } 87 88 } 89 /* 90 获取运行时类所在的包 91 92 */ 93 @Test 94 public void test6(){ 95 Class clazz = Person.class; 96 97 Package pack = clazz.getPackage(); 98 System.out.println(pack); 99 } 100 101 /* 102 获取运行时类声明的注解 103 104 */ 105 @Test 106 public void test7(){ 107 Class clazz = Person.class; 108 109 Annotation[] annotations = clazz.getAnnotations(); 110 for(Annotation annos : annotations){ 111 System.out.println(annos); 112 } 113 } 114 115 }
六、调用运行类的指定结构
1 public class ReflectionTest { 2 3 @Test 4 public void testField() throws Exception { 5 Class clazz = Person.class; 6 7 //创建运行时类的对象 8 Person p = (Person) clazz.newInstance(); 9 10 //获取指定的属性:要求运行时类中属性声明为public 11 //通常不采用此方法 12 Field id = clazz.getField("id"); 13 14 /* 15 设置当前属性的值 16 17 set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少 18 */ 19 id.set(p, 1001); 20 21 /* 22 获取当前属性的值 23 get():参数1:获取哪个对象的当前属性值 24 */ 25 Integer pId = (Integer) id.get(p); 26 System.out.println(pId); 27 } 28 29 /* 30 如何操作运行时类中的指定的属性 -- 需要掌握 31 */ 32 @Test 33 public void testField1() throws Exception { 34 Class clazz = Person.class; 35 36 //创建运行时类的对象 37 Person p = (Person) clazz.newInstance(); 38 39 //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性 40 Field name = clazz.getDeclaredField("name"); 41 42 //2.保证当前属性是可访问的 43 name.setAccessible(true); 44 45 //3.获取、设置指定对象的此属性值 46 name.set(p, "Tom"); 47 48 System.out.println(name.get(p)); 49 } 50 51 /* 52 如何操作运行时类中的指定的方法 -- 需要掌握 53 */ 54 @Test 55 public void testMethod() throws Exception { 56 Class clazz = Person.class; 57 58 //创建运行时类的对象 59 Person p = (Person) clazz.newInstance(); 60 61 /* 62 1.获取指定的某个方法 63 getDeclaredMethod():参数1 :指明获取的方法的名称 参数2:指明获取的方法的形参列表 64 */ 65 Method show = clazz.getDeclaredMethod("show", String.class); 66 //2.保证当前方法是可访问的 67 show.setAccessible(true); 68 69 /* 70 3. 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参 71 invoke()的返回值即为对应类中调用的方法的返回值。 72 */ 73 Object returnValue = show.invoke(p, "CHN"); 74 System.out.println(returnValue); 75 76 System.out.println("*************如何调用静态方法*****************"); 77 78 79 Method showDesc = clazz.getDeclaredMethod("showDesc"); 80 showDesc.setAccessible(true); 81 //如果调用的运行时类中的方法没有返回值,则此invoke()返回null 82 // Object returnVal = showDesc.invoke(null); 83 Object returnVal = showDesc.invoke(Person.class); 84 System.out.println(returnVal); 85 } 86 87 /* 88 如何调用运行时类中的指定的构造器 89 */ 90 @Test 91 public void testConstructor() throws Exception { 92 Class clazz = Person.class; 93 94 /* 95 1.获取指定的构造器 96 getDeclaredConstructor():参数:指明构造器的参数列表 97 */ 98 Constructor constructor = clazz.getDeclaredConstructor(String.class); 99 //2.保证此构造器是可访问的 100 constructor.setAccessible(true); 101 //3.调用此构造器创建运行时类的对象 102 Person tom = (Person) constructor.newInstance("Tom"); 103 System.out.println(tom); 104 } 105 }
关于setAccessible方法的使用
- Method和Field、Constructor对象都有setAccessible()方法。
- setAccessible启动和禁用访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
- 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。
- 使得原本无法访问的私有成员也可以访问参数值为false则指示反射的对象应该实施Java语言访问检查。
七、反射的应用:动态代理
代理设计模式的原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原 始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原 始对象上。
静态代理
1 /** 2 * 静态代理举例 3 * 4 * 特点:代理类和被代理类在编译期间,就确定下来了。 5 * 6 */ 7 interface ClothFactory { 8 9 void produceCloth(); 10 11 } 12 //被代理类 13 class NikeClothFactory implements ClothFactory{ 14 15 public void produceCloth() { 16 System.out.println("Nike工厂生产一批运动服"); 17 } 18 } 19 //代理类 20 class ProxyClothFactory implements ClothFactory{ 21 22 private ClothFactory factory; //用被代理类对象进行实例化 23 24 public ProxyClothFactory(ClothFactory factory){ 25 this.factory = factory; 26 } 27 28 public void produceCloth() { 29 System.out.println("代理工厂做一些准备工作"); 30 factory.produceCloth(); 31 System.out.println("代理工厂做一些收尾工作"); 32 } 33 } 34 public class StaticProxyTest { 35 public static void main(String[] args) { 36 //创建被代理类的对象 37 ClothFactory nike = new NikeClothFactory(); 38 //创建代理类的对象 39 ClothFactory proxyClothFactory = new ProxyClothFactory(nike); 40 41 proxyClothFactory.produceCloth(); 42 } 43 }
动态代理
Java动态代理相关API
Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一 个或多个接口动态地生成实现类。提供用于创建动态代理类和动态代理对象的静态方法
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
创建一个动态代理类所对应的Class对象
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h) 直接创建一个动态代理对象
代码如下:
1 /** 2 * 3 * 动态代理的举例 4 * 5 */ 6 7 interface Human { 8 String getBelief(); 9 10 void eat(String food); 11 } 12 13 //被代理类 14 class SuperMan implements Human{ 15 16 public String getBelief() { 17 return "I believe I can fly!"; 18 } 19 20 public void eat(String food) { 21 System.out.println("我喜欢吃" + food); 22 } 23 } 24 25 class HumanUtil { 26 public static void method1(){ 27 System.out.println("=========通用方法一========="); 28 } 29 30 public static void method2(){ 31 System.out.println("=========通用方法二========="); 32 } 33 } 34 35 36 /* 37 要想实现动态代理,需要解决的问题? 38 问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。 39 问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。 40 41 42 */ 43 44 class ProxyFactory { 45 //调用此方法,返回一个代理类的对象。解决问题一 46 public static Object getProxyInstance(Object obj) { 47 MyInvocationHandler handler = new MyInvocationHandler(); 48 handler.bind(obj); 49 50 return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler ); 51 } 52 } 53 54 class MyInvocationHandler implements InvocationHandler{ 55 56 private Object obj;//需要使用被代理类的对象进行赋值 57 58 public void bind(Object obj){ 59 this.obj = obj; 60 } 61 62 //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke() 63 //将被代理类要执行的方法a的功能就声明在invoke()中 64 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 65 System.out.println("动态代理准备"); 66 HumanUtil.method1(); 67 68 //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法 69 //obj:被代理类的对象 70 Object val = method.invoke(obj, args); 71 72 HumanUtil.method2(); 73 System.out.println("动态代理收尾"); 74 75 //上述方法的返回值就作为当前类中的invoke()的返回值。 76 return val; 77 } 78 } 79 80 81 public class ProxyTest { 82 public static void main(String[] args) { 83 Human superMan = new SuperMan(); 84 //proxyInstance:代理类的对象 85 Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan); 86 //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法 87 System.out.println(proxyInstance.getBelief()); 88 proxyInstance.eat("大米"); 89 90 System.out.println("*****************"); 91 92 NikeClothFactory nikeClothFactory = new NikeClothFactory(); 93 ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory); 94 95 proxyClothFactory.produceCloth(); 96 97 } 98 }
动态代理与AOP
使用Proxy生成一个动态代理时,往往并不会凭空产生一个动态代理,这样没有 太大的意义。通常都是为指定的目标对象生成动态代理
这种动态代理在AOP中被称为AOP代理,AOP代理可代替目标对象,AOP代理 包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:
AOP代理里的方法可以在执行目标方法之前、之后插入一些通用处理