Java中的反射机制和动态代理

1、反射概述

  反射机制指的是Java在运行时候有一种自观的能力,可以了解自身的状况为下一步作准备,其想表达的意思就是:在运行状态中,对于任意一个类,都可以获取到这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,经过反射,该类对咱们来讲是彻底透明的,想要获取任何东西均可以,这是一种动态获取类的信息以及动态调用对象方法的能力。java

  想要使用反射机制,就必需要先获取到该类的字节码文件对象(.class),经过该类的字节码对象,就可以经过该类中的方法获取到咱们想要的全部信息(方法,属性,类名,父类名,实现的全部接口等等),每个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象数组

  Java提供的反射机制,依赖于咱们下面要讲到的Class类和java.lang.reflect类库。咱们下面要学习使用的主要类有:①Class表示类或者接口;②java.lang.reflect.Field表示类中的成员变量;③java.lang.reflect.Method表示类中的方法;④java.lang.reflect.Constructor表示类的构造方法;⑤Array提供动态数组的建立和访问数组的静态方法。ide

2、反射之Class类

(1)初识Class类

  在类Object下面提供了一个方法:,此方法将会被全部的子类继承,该方法的返回值为一个Class,这个Class类就是反射的源头。那么Class类是什么呢?Class类是Java中用来表达运行时类型信息的对应类,咱们刚刚也说过全部类都会继承Object类的getClass()方法,那么也体现了着Java中的每一个类都有一个Class对象,当咱们编写并编译一个建立的类就会产生对应的class文件并将类的信息写到该class文件中,当咱们使用正常方式new一个对象或者使用类的静态字段时候,JVM的累加器子系统就会将对应的Class对象加载到JVM中,而后JVM根据这个类型信息相关的Class对象建立咱们须要的实例对象或者根据提供静态变量的引用值。将Class类称为类的类型,一个Class对象称为类的类型对象。函数

(2)Class有下面的几个特色

  ①Class也是类的一种(不一样于class,class是一个关键字);学习

  ②Class类只有一个私有的构造函数,只有JVM可以建立Class类的实例;测试

  ③对于同一类的对象,在JVM中只存在惟一一个对应的Class类实例来描述其信息;this

  ④每一个类的实例都会记得本身是由哪一个Class实例所生成;spa

  ⑤经过Class能够完整的获得一个类中的完整结构;代理

(3)获取Class类实例

  刚刚说到过Class只有一个私有的构造函数,因此咱们不能经过new建立Class实例 ,有下面这几种获取Class实例的方法:code

  ①Class.forName("类的全限定名"),该方法只能获取引用类型的类类型对象。该方法会抛出异常(a.l类加载器在类路径中没有找到该类  b.该类被某个类加载器加载到JVM内存中,另一个类加载器有尝试从同一个包中加载)

1 //Class<T> clazz = Class.forName("类的全限定名");这是经过Class类中的静态方法forName直接获取一个Class的对象
2 Class<?> clazz1 = null;
3 try {
4     clazz1 = Class.forName("reflect.Person");
5 } catch (ClassNotFoundException e) {
6     e.printStackTrace();
7 }
8 System.out.println(clazz1); //class reflect.Person

  ②若是咱们有一个类的对象实例,那么经过这个对象的getClass()方法能够得到他的Class对象,以下所示

 1 //Class<T> clazz = xxx.getClass(); //经过类的实例获取类的Class对象
 2 Class<?> clazz3 = new Person().getClass();
 3 System.out.println(clazz3); //class reflect.Person
 4 
 5 Class<?> stringClass = "string".getClass();
 6 System.out.println(stringClass); //class java.lang.String
 7 
 8 /**
 9  * [表明数组,
10  * B表明byte;
11  * I表明int;
12  * Z表明boolean;
13  * L表明引用类型
14  * 组合起来就是指定类型的一维数组,若是是[[就是二维数组
15  */
16 Class<?> arrClass = new byte[20].getClass();
17 System.out.println(arrClass); //class [B
18 
19 Class<?> arrClass1 = new int[20].getClass();
20 System.out.println(arrClass1); //class [I
21 
22 Class<?> arrClass2 = new boolean[20].getClass();
23 System.out.println(arrClass2); //class [Z
24 
25 Class<?> arrClass3 = new Person[20].getClass();
26 System.out.println(arrClass3); //class [Lreflect.Person;
27 
28 Class<?> arrClass4 = new String[20].getClass();
29 System.out.println(arrClass4); //class [Ljava.lang.String;

  ③经过类的class字节码文件获取,经过类名.class获取该类的Class对象

1 //Class<T> clazz = XXXClass.class; 当类已经被加载为.class文件时候,
2 Class<Person> clazz2 = Person.class;
3 System.out.println(clazz2);
4 System.out.println(int [][].class);//class [[I
5 System.out.println(Integer.class);//class java.lang.Integer

(4)关于包装类的静态属性

  咱们知道,在Java中对于基本类型和void都有对应的包装类。在包装类中有一个静态属性TYPE保存了该类的类类型。以下所示

1     /**
2      * The {@code Class} instance representing the primitive type
3      * {@code int}.
4      *
5      * @since   JDK1.1
6      */
7     @SuppressWarnings("unchecked")
8     public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

  咱们使用这个静态属性来得到Class实例,以下所示

1 Class c0 = Byte.TYPE; //byte
2 Class c1 = Integer.TYPE; //int
3 Class c2 = Character.TYPE; //char
4 Class c3 = Boolean.TYPE; //boolean
5 Class c4 = Short.TYPE; //short
6 Class c5 = Long.TYPE; //long
7 Class c6 = Float.TYPE; //float
8 Class c7 = Double.TYPE; //double
9 Class c8 = Void.TYPE; //void

(5)经过Class类的其余方法获取

①public native Class<? super T> getSuperclass():获取该类的父类

1 Class c1 = Integer.class;
2 Class par = c1.getSuperclass();
3 System.out.println(par); //class java.lang.Number

②public Class<?>[] getClasses():获取该类的全部公共类、接口、枚举组成的Class数组,包括继承的;

③public Class<?>[] getDeclaredClasses():获取该类的显式声明的全部类、接口、枚举组成的Class数组;

④(Class/Field/Method/Constructor).getDeclaringClass():获取该类/属性/方法/构造器所在的类

3、Class类的API

  这是下面测试用例中使用的Person类和实现的接口

 1 package reflect;
 2 
 3 interface Test {
 4     String test = "interface";
 5 }
 6 
 7 public class Person  implements Test{
 8 
 9     private String id;
10     private String name;
11 
12     public void sing(String name) {
13         System.out.println(getName() + "会唱" + name +"歌");
14     }
15 
16     private void dance(String name) {
17         System.out.println(getName() + "会跳" + name + "舞");
18     }
19 
20     public void playBalls() {
21         System.out.println(getName() + "会打篮球");
22     }
23 
24     public String getId() {
25         return id;
26     }
27 
28     public void setId(String id) {
29         this.id = id;
30     }
31 
32     public String getName() {
33         return name;
34     }
35 
36     public void setName(String name) {
37         this.name = name;
38     }
39 
40 
41     public Person() {
42     }
43 
44     public Person(String id, String name) {
45         this.id = id;
46         this.name = name;
47     }
48 
49     public Person(String id) {
50         this.id = id;
51     }
52 
53     @Override
54     public String toString() {
55         return "Person{" +
56                 "id='" + id + '\'' +
57                 ", name='" + name + '\'' +
58                 '}';
59     }
60 }
Person

一、建立实例对象

1 public void test4() throws Exception{
2     Class clazz =Class.forName("reflect.Person");
3     Person person = (Person)clazz.newInstance();
4     System.out.println(person);
5 }

  建立运行时类的对象,使用newInstance(),实际上就是调用运行时指定类的无参构造方法。这里也说明要想建立成功,须要对应的类有无参构造器,而且构造器的权限要足够,不然会抛出下面的异常。

  ①咱们显示声明Person类一个带参构造,并无无参构造,这种状况会抛出InstantiationException

  

  ②更改无参构造器访问权限为private

  

二、获取构造器

(1)获取指定可访问的构造器建立对象实例

  上面咱们所说的使用newInstance方法建立对象,若是不指定任何参数的话默认是调用指定类的无参构造器的。那么若是没有无参构造器,又想建立对象实例怎么办呢,就使用 Class类提供的获取构造器的方法,显示指定咱们须要调用哪个无参构造器。

1 @Test
2 public void test5() throws Exception {
3     Class clazz = Class.forName("reflect.Person");
4     //获取带参构造器
5     Constructor constructor = clazz.getConstructor(String.class, String .class);
6     //经过构造器来实例化对象
7     Person person = (Person) constructor.newInstance("p1", "person");
8     System.out.println(person);
9 }

  当咱们指定的构造器所有不够(好比设置为private),咱们在调用的时候就会抛出下面的异常

  

(2)得到所有构造器

 1 @Test
 2 public void test6() throws Exception {
 3     Class clazz1 = Class.forName("reflect.Person");
 4     Constructor[] constructors = clazz1.getConstructors();
 5     for (Constructor constructor : constructors) {
 6         Class[] parameters = constructor.getParameterTypes();
 7         System.out.println("构造函数名:" + constructor + "\n" + "参数");
 8         for (Class c: parameters) {
 9             System.out.print(c.getName() + " ");
10         }
11         System.out.println();
12     }
13 }

  运行结果以下

  

三、获取成员变量并使用Field对象的方法

  (1)Class.getField(String)方法能够获取类中的指定字段(可见的), 若是是私有的能够用getDeclaedField("name")方法获取,经过set(对象引用,属性值)方法能够设置指定对象上该字段的值, 若是是私有的须要先调用setAccessible(true)设置访问权限,用获取的指定的字段调用get(对象引用)能够获取指定对象中该字段的值。

 1 @Test
 2 public void test7() throws Exception {
 3     Class clazz1 = Class.forName("reflect.Person");
 4     //得到实例对象
 5     Person person = (Person) clazz1.newInstance();
 6     /**
 7      * 得到类的属性信息
 8      * 使用getField(name),经过指定的属性name得到
 9      * 若是属性不是public的,使用getDeclaredField(name)得到
10      */
11     Field field = clazz1.getDeclaredField("id");
12     //若是是private的,须要设置权限为可访问
13     field.setAccessible(true);
14     //设置成员变量的属性值
15     field.set(person, "person1");
16     //获取成员变量的属性值,使用get方法,其中第一个参数表示得到字段的所属对象,第二个参数表示设置的值
17     System.out.println(field.get(person)); //这里的field就是id属性,打印person对象的id属性的值
18 }

  (2)得到所有成员变量

 1 @Test
 2 public void test8() throws Exception{
 3     Class clazz1 = Class.forName("reflect.Person");
 4     //得到实例对象
 5     Person person = (Person) clazz1.newInstance();
 6     person.setId("person1");
 7     person.setName("person1_name");
 8     Field[] fields = clazz1.getDeclaredFields();
 9     for (Field f : fields) {
10         //打开private成员变量的可访问权限
11         f.setAccessible(true);
12         System.out.println(f+ ":" + f.get(person));
13     }
14 }

  

四、获取方法并使用method

  (1)使用Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法能够获取类中的指定方法,若是为私有方法,则须要打开一个权限。setAccessible(true);用invoke(Object, Object...)能够调用该方法。若是是私有方法而使用的是getMethod方法来得到会抛出NoSuchMethodException

 1 @Test
 2 public void test9() throws Exception{
 3     Class clazz1 = Class.forName("reflect.Person");
 4     //得到实例对象
 5     Person person = (Person) clazz1.newInstance();
 6     person.setName("Person");
 7     //①不带参数的public方法
 8     Method playBalls = clazz1.getMethod("playBalls");
 9     //调用得到的方法,须要指定是哪个对象的
10     playBalls.invoke(person);
11 
12     //②带参的public方法:第一个参数是方法名,后面的可变参数列表是参数类型的Class类型
13     Method sing = clazz1.getMethod("sing",String.class);
14     //调用得到的方法,调用时候传递参数
15     sing.invoke(person,"HaHaHa...");
16 
17     //③带参的private方法:使用getDeclaredMethod方法
18     Method dance = clazz1.getDeclaredMethod("dance", String.class);
19     //调用得到的方法,须要先设置权限为可访问
20     dance.setAccessible(true);
21     dance.invoke(person,"HaHaHa...");
22 }

  (2)得到全部方法(不包括构造方法)

 1 @Test
 2 public void test10() throws Exception{
 3     Class clazz1 = Class.forName("reflect.Person");
 4     //得到实例对象
 5     Person person = (Person) clazz1.newInstance();
 6     person.setName("Person");
 7     Method[] methods = clazz1.getDeclaredMethods();
 8     for (Method method: methods) {
 9         System.out.print("方法名" + method.getName() + "的参数是:");
10         //得到方法参数
11         Class[] params = method.getParameterTypes();
12         for (Class c : params) {
13             System.out.print(c.getName() + " ");
14         }
15         System.out.println();
16     }
17 }

五、得到该类的全部接口

  Class[] getInterfaces():肯定此对象所表示的类或接口实现的接口,返回值:接口的字节码文件对象的数组

1 @Test
2 public void test11() throws Exception{
3     Class clazz1 = Class.forName("reflect.Person");
4     Class[] interfaces = clazz1.getInterfaces();
5     for (Class inter : interfaces) {
6         System.out.println(inter);
7     }
8 }

六、获取指定资源的输入流

  InputStream getResourceAsStream(String name),返回值:一个 InputStream 对象;若是找不到带有该名称的资源,则返回 null;参数:所需资源的名称,若是以"/"开始,则绝对资源名为"/"后面的一部分。

 1 @Test
 2 public void test12() throws Exception {
 3     ClassLoader loader = this.getClass().getClassLoader();
 4     System.out.println(loader);//sun.misc.Launcher$AppClassLoader@18b4aac2 ,应用程序类加载器
 5     System.out.println(loader.getParent());//sun.misc.Launcher$ExtClassLoader@31befd9f ,扩展类加载器
 6     System.out.println(loader.getParent().getParent());//null ,不能得到启动类加载器
 7 
 8     Class clazz = Person.class;//自定义的类
 9     ClassLoader loader2 = clazz.getClassLoader();
10     System.out.println(loader2);//sun.misc.Launcher$AppClassLoader@18b4aac2
11 
12     //下面是得到InputStream的例子
13     ClassLoader inputStreamLoader = this.getClass().getClassLoader();
14     InputStream inputStream = inputStreamLoader.getResourceAsStream("person.properties");
15     Properties properties = new Properties();
16     properties.load(inputStream);
17     System.out.println("id:" + properties.get("id"));
18     System.out.println("name:" + properties.get("name"));
19 }

  其中properties文件内容

1 id = person001
2 name = person-name1
View Code

5、反射的应用之动态代理

  代理模式在Java中应用十分普遍,它说的是使用一个代理将对象包装起来而后用该代理对象取代原始对象,任何原始对象的调用都须要经过代理对象,代理对象决定是否以及什么时候将方法调用转到原始对象上。这种模式能够这样简单理解:你本身想要作某件事情(被代理类),可是以为本身作很是麻烦或者不方便,因此就叫一个另外一我的(代理类)来帮你作这个事情,而你本身只须要告诉要作啥事就行了。上面咱们讲到了反射,在下面咱们会说一说java中的代理

 一、静态代理

  静态代理其实就是程序运行以前,提早写好被代理类的代理类,编译以后直接运行便可起到代理的效果,下面会用简单的例子来讲明。在例子中,首先咱们有一个顶级接口(ProductFactory),这个接口须要代理类(ProxyTeaProduct)和被代理类(TeaProduct)都去实现它,在被代理类中咱们重写须要实现的方法(action),该方法会交由代理类去选择是否执行和在何处执行;被代理类中主要是提供顶级接口的的一个引用可是引用实际指向的对象则是实现了该接口的代理类(使用多态的特色,在代理类中提供构造器传递实际的对象引用)。分析以后,咱们经过下面这个图理解一下这个过程。

 1 package proxy;
 2 
 3 /**
 4  * 静态代理
 5  */
 6 //产品接口
 7 interface ProductFactory {
 8     void action();
 9 }
10 
11 //一个具体产品的实现类,做为一个被代理类
12 class TeaProduct implements ProductFactory{
13     @Override
14     public void action() {
15         System.out.println("我是生产茶叶的......");
16     }
17 }
18 
19 //TeaProduct的代理类
20 class ProxyTeaProduct implements ProductFactory {
21     //咱们须要ProductFactory的一个实现类,去代理这个实现类中的方法(多态)
22     ProductFactory productFactory;
23 
24     //经过构造器传入实际被代理类的对象,这时候代理类调用action的时候就能够在其中执行被代理代理类的方法了
25     public ProxyTeaProduct(ProductFactory productFactory) {
26         this.productFactory = productFactory;
27     }
28 
29     @Override
30     public void action() {
31         System.out.println("我是代理类,我开始代理执行方法了......");
32         productFactory.action();
33     }
34 }
35 public class TestProduct {
36 
37     public static void main(String[] args) {
38         //建立代理类的对象
39         ProxyTeaProduct proxyTeaProduct = new ProxyTeaProduct(new TeaProduct());
40         //执行代理类代理的方法
41         proxyTeaProduct.action();
42     }
43 }

  那么程序测试的输出结果也很显然了,代理类执行本身实现的方法,而在其中有调用了被代理类的方法

  

  那么咱们想一下,上面这种称为静态代理的方式有什么缺点呢?由于每个代理类只能为一个借口服务(由于这个代理类须要实现这个接口,而后去代理接口实现类的方法),这样一来程序中就会产生过多的代理类。好比说咱们如今又来一个接口,那么是否是也须要提供去被代理类去实现它而后交给代理类去代理执行呢,那这样程序就不灵活了。那么若是有一种方式,就能够处理新添加接口的以及实现那不就更加灵活了吗,在java中反射机制的存在为动态代理创造了机会

二、JDK中的动态代理

  动态代理是指经过代理类来调用它对象的方法,而且是在程序运行使其根据须要建立目标类型的代理对象。它只提供一个代理类,咱们只须要在运行时候动态传递给须要他代理的对象就能够完成对不一样接口的服务了。看下面的例子。(JDK提供的代理正能针对接口作代理,也就是下面的newProxyInstance返回的必需要是一个接口)

 1 package proxy;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 
 7 /**
 8  * JDK中的动态代理
 9  */
10 //第一个接口
11 interface TargetOne {
12     void action();
13 }
14 //第一个接口的被代理类
15 class TargetOneImpl implements TargetOne{
16     @Override
17     public void action() {
18         System.out.println("我会实现父接口的方法...action");
19     }
20 }
21 
22 
23 //动态代理类
24 class DynamicProxyHandler implements InvocationHandler {
25     //接口的一个引用,多态的特性会使得在程序运行的时候,它实际指向的是实现它的子类对象
26     private TargetOne targetOne;
27     //咱们使用Proxy类的静态方法newProxyInstance方法,将代理对象假装成那个被代理的对象
28     /**
29      * ①这个方法会将targetOne指向实际实现接口的子类对象
30      * ②根据被代理类的信息返回一个代理类对象
31      */
32     public Object setObj(TargetOne targetOne) {
33         this.targetOne = targetOne;
34         //    public static Object newProxyInstance(ClassLoader loader, //被代理类的类加载器
35         //                                          Class<?>[] interfaces, //被代理类实现的接口
36         //                                          InvocationHandler h) //实现InvocationHandler的代理类对象
37         return Proxy.newProxyInstance(targetOne.getClass().getClassLoader(),targetOne.getClass().getInterfaces(),this);
38     }
39     //当经过代理类的对象发起对接口被重写的方法的调用的时候,都会转换为对invoke方法的调用
40     @Override
41     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
42         System.out.println("这是我代理以前要准备的事情......");
43         /**
44          *      这里回想一下在静态代理的时候,咱们显示指定代理类须要执行的是被代理类的哪些方法;
45          *      而在这里的动态代理实现中,咱们并不知道代理类会实现什么方法,他是根据运行时经过反射来
46          *  知道本身要去指定被代理类的什么方法的
47          */
48         Object returnVal = method.invoke(this.targetOne,args);//这里的返回值,就至关于真正调用的被代理类方法的返回值
49         System.out.println("这是我代理以后要处理的事情......");
50         return returnVal;
51     }
52 }
53 public class TestProxy {
54     public static void main(String[] args) {
55         //建立被代理类的对象
56         TargetOneImpl targetOneImpl = new TargetOneImpl();
57         //建立实现了InvocationHandler的代理类对象,而后调用其中的setObj方法完成两项操做
58         //①将被代理类对象传入,运行时候调用的是被代理类重写的方法
59         //②返回一个类对象,经过代理类对象执行接口中的方法
60         DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
61         TargetOne targetOne = (TargetOne) dynamicProxyHandler.setObj(targetOneImpl);
62         targetOne.action(); //调用该方法运行时都会转为对DynamicProxyHandler中的invoke方法的调用
63     }
64 }

  运行结果以下。如今咱们对比jdk提供的动态代理和咱们刚刚实现的静态代理,刚刚说到静态代理对于新添加的接口须要定义对应的代理类去代理接口的实现类。而上面的测试程序所使用的动态代理规避了这个问题,即咱们不须要显示的指定每一个接口对应的代理类,有新的接口添加没有关系,只须要在使用的时候传入接口对应的实现类而后返回代理类对象(接口实现类型),而后调用被代理类的方法便可。

   

相关文章
相关标签/搜索