Java的反射

来源:互联网 发布:幸运28最新源码 编辑:程序博客网 时间:2024/06/05 08:57

转自“大苞米”的博客 : http://blog.csdn.net/a396901990/article/category/2302221

(为了方便看,合成了一篇帖子!感谢大苞米的分享!)


Java反射学习总结一(基础篇)

Class类是Reflection API中核心的类,他位于java.lang.Class

列出一些常用的方法。

- getName() : 获得类的完整名字

- getFields() : 获得类的public类型的属性

- getDeclaredFields() : 获得类的所有属性

- getMethods() : 获得类的public类型的方法

- getDeclaredMethods() : 获得类的所有方法

- getMethod(String name, Class[] parameterTypes) : 获取类的特定方法(name参数指定方法名字,parameterTypes参数指定方法参数类型)

- getConstructors() : 获得类的public类型的构造方法

- getConstructor(Class[] parameterTypes) : 获得类的特定构造方法(parameterTypes参数指定构造方法的参数类型)

- newInstance() : 通过类的不带参数的构造方法创建这个类的一个对象


如果想使用反射,有2个关键的部分

1.获取Class对象

2.获得对象实例

下面来介绍这两个部分:


1.如何获取Class对象

获取某个类或某个对象所对应的Class对象的常用的3种方法

a) 使用Class类的静态方法forName:       

  Class.forName("java.lang.String");

b) 使用类的.class语法: 

   String.class;

c) 使用对象的getClass()方法(java.lang.Object类中的方法): 

   String s = "aa"; 

   Class<?> clazz = s.getClass();


2.如何获得对象实例

大家都知道获得对象实例就是去new一个,其实就是调用对象的构造方法

这里将调用构造方法参数的不同分为两种类型:

a)调用无参数的构造方法:

1.调用Class对象的newInstance()方法:

Class<?> classType = ClassClass.forName("java.lang.String");

Object object = classTpye.newInstance();

         2.调用Class对象的Constructor对象的newInstance()方法,传递一个空的Class对象数组作为参数:

Class<?> classType = ClassClass.forName("java.lang.String");

Constructor cons = classType.getConstructor(new Class[]{});  

Object object =cons.newInstance(new Object[]{});

b)调用有参数的构造方法:

1.调用Class对象的Constructor对象的newInstance()方法,传递一个可变长的Class对象数组作为参数,本例传递String,int两个参数:

Class<?> classType = ClassClass.forName("java.lang.String");

Constructor cons = classType.getConstructor(new Class[]{String.class, int.class});

  Object object =cons.newInstance(new Object[]{"hello",3});


下面来一个小Demo,由于过于简单这里就只贴代码了

这个demo简单的实现了上面介绍的一些关于反射的用法,算是一个复习巩固:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. class Person {  
  2.   
  3.     private long id;  
  4.     private int age;  
  5.     private String name;  
  6.       
  7.     public Person() {  
  8.           
  9.     }  
  10.       
  11.     public Person(String name, int age) {  
  12.         this.name = name;  
  13.         this.age = age;  
  14.     }  
  15.       
  16.     @Override  
  17.     public String toString() {  
  18.           
  19.         return "Name="+getName()+"  Age="+getAge()+"  Id="+getId();  
  20.     }  
  21.       
  22.     public long getId() {  
  23.         return id;  
  24.     }  
  25.   
  26.     public void setId(long id) {  
  27.         this.id = id;  
  28.     }  
  29.   
  30.     public int getAge() {  
  31.         return age;  
  32.     }  
  33.   
  34.     public void setAge(int age) {  
  35.         this.age = age;  
  36.     }  
  37.   
  38.     public String getName() {  
  39.         return name;  
  40.     }  
  41.   
  42.     public void setName(String name) {  
  43.         this.name = name;  
  44.     }  
  45.   
  46. }  
很简单的一个自定义类,两个构造方法,和一些get,set方法,还有一个重写的toString


下面是调用反射的main方法中的代码,主要就是用反射的方法调用Person的带参构造方法给age,name赋值,在调用setId给id赋值,最后调用toString方法打印出结果

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) throws Exception {  
  2.           
  3.         //获取Person类的Class对象  
  4.         Class<?> classType = Class.forName("Person");  
  5.           
  6.         //调用Person类的两个参数构造方法生成对象  
  7.         Constructor constructor = classType.getConstructor(new Class[]{String.classint.class});  
  8.         Object object = constructor.newInstance(new Object[]{"Dean",25});  
  9.           
  10.         //获取setId方法  
  11.         Method setId = classType.getMethod("setId"new Class[]{long.class});  
  12.           
  13.         //调用setId方法设置Id  
  14.         setId.invoke(object, new Object[]{10});  
  15.           
  16.         //调用toString输出结果  
  17.         Method toString = classType.getMethod("toString"new Class[]{});  
  18.         String result = (String) toString.invoke(object, new Object[]{});  
  19.         System.out.println(result);  
  20. }  
输出结果为:Name=Dean  Age=25  Id=10


Java反射学习总结二(用反射调用对象的私有属性和方法)

大家都知道正常的调用是不可以访问对象的private修饰的属性和方法的,这也是java的封装性原则。

但是有没有方法可以强制去访问对象的private修饰的属性和方法呢?那就是用反射!(这个可能在面试题中被问到哦)

接下来就来看看是如何实现的:

我们先去jdk里看一下描述属性的类Field,和方法的类Method:

java.lang.reflect

Class Field

  • java.lang.Object
  • java.lang.reflect.Field
java.lang.reflect

Class Method

  • java.lang.Object
  • java.lang.reflect.Method


可以看到这两个类有个共通的特点,就是他们都继承自java.lang.reflect.AccessibleObject这个类,我们好好看看这个类的描述

java.lang.reflect

Class AccessibleObject

  • java.lang.Object
  • java.lang.reflect.AccessibleObject
  • All Implemented Interfaces:
    AnnotatedElement
    Direct Known Subclasses:
    Constructor, Field, Method


    public class AccessibleObjectextends Objectimplements AnnotatedElement
    The AccessibleObject class is the base class for Field, Method and Constructor objects. It provides the ability to flag a reflected object as suppressing default Java language access control checks when it is used. The access checks--for public, default (package) access, protected, and private members--are performed when Fields, Methods or Constructors are used to set or get fields, to invoke methods, or to create and initialize new instances of classes, respectively.

大致意思就是:

这个AccessibleObject类是Field, Method and Constructor对象的一个父类,他可以让一个反射对象去禁止Java语言的访问控制检测。控制检测有public, default (package) access, protected, and private。。。blah blah blah。。。

这里我贴出控制访问控制检测的这个方法:(这个类里还有一些相关的方法,有兴趣的大家可以自己去看看)

setAccessible

public void setAccessible(boolean flag)                   throws SecurityException
Set the accessible flag for this object to the indicated boolean value. A value of true indicates that the reflected object should suppress Java language access checking when it is used. A value of falseindicates that the reflected object should enforce Java language access checks.

大致意思:

设置标志去指示对象的boolean值,如果是true则禁止java访问控制检查,如果是false则强制反射对象使用java访问控制检查


知道了这个方法就可以做一个小例子测试一下啦。

下面这个例子很简单,就是定义一个dog类,里面有个private的属性dogName,和private的方法say。

main函数里用反射先去修改dogName,然后在调用say方法打印出来:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class Test2 {  
  2.   
  3.     public static void main(String[] args) throws Exception {  
  4.         //获得Dog类的Class对象  
  5.         Class<?> classType = Class.forName("Dog");  
  6.         //生成对象的实例  
  7.         Object obj = classType.newInstance();  
  8.           
  9.         //取得dogName属性  
  10.         Field dogName = classType.getDeclaredField("dogName");  
  11.         //禁止Field的访问控制检查  
  12.         dogName.setAccessible(true);  
  13.         //将Field的值设为“Xiao Qiang”  
  14.         dogName.set(obj, "Xiao Qiang");  
  15.           
  16.         //取得say()方法  
  17.         Method say = classType.getDeclaredMethod("say"new Class[]{});  
  18.         //禁止say方法的访问控制检查  
  19.         say.setAccessible(true);  
  20.         //调用say方法  
  21.         say.invoke(obj, new Object[]{});  
  22.     }  
  23.   
  24. }  
  25.   
  26.  class Dog {  
  27.     //私有的属性  
  28.     private String dogName = "Wang Cai";  
  29.     //私有的方法  
  30.     private void say() {  
  31.         System.out.println(dogName + ": Wang Wang");  
  32.     }  
  33. }  
输出结果:Xiao Qiang: Wang Wang

这里需要特别注意一个地方:

如果想用反射修改访问控制检查的话,获取Method和Field对象的时候一定要用getDeclaredField和getDeclaredMethod。不要用getField和getMethod。

虽然这两个方法的参数都是相同的,但不同点在于getMethod和getField只能获得public修饰的属性和方法。而getDeclared可以获取任何类型的属性和方法,因为这个例子要调用私有的属性和方法,所以要用getDeclaredXX。

 

Java反射学习总结三(静态代理)


反射最常见的应用就是代理模式了。

本文先简单介绍一下代理模式,并写一个静态代理的例子。为下一篇重要的动态代理做点铺垫


代理模式的作用是

为其他对象提供一种代理以控制对这个对象的访问。
另外在某些情况下,一个客户不想或着不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
说白了,代理模式就是一个"中介",他有这客户们的引用,所以可以用这个"中介"来代替客户来做操作。


代理模式设计的角色
想使用代理模式,需要定义如下的3个角色:

抽象角色

声明真实对象和代理对象的共同接口

真实对象

代理角色所代表的真实对象,是我们最终要引用的对象

代理角色: 

1.代理对象内部含有对真实对象的引用,从而可以操作真实的对象。
2.代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。
3.代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装


可能看完概念后有点迷糊,接下来直接上代码看例子应该会比较容易理解:

1.首先定义抽象角色对象,使用抽象类或者接口来定义一个代理角色和真实对象共有的一个方法。这个方法就是被代理角色委托给"中介"来操作的方法

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //这里定义一个抽象类  
  2. public abstract class Subject {  
  3.     //空的抽象方法,这个方法需要代理角色和真实角色都去实现它  
  4.     public abstract void request();  
  5. }  

2.定义真实角色对象

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //真实角色对象,继承自抽象角色,重写定义的方法。  
  2. public class RealSubject extends Subject{  
  3.   
  4.     //在重写的方法中实现需要的操作,这里只是简单打印一句  
  5.     @Override  
  6.     public void request() {  
  7.         System.out.println("this is real subject");  
  8.     }  
  9.   
  10. }  

3.定义代理角色对象

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //代理角色,同样继承抽象角色,重写抽象角色中的方法  
  2. public class ProxySubject extends Subject{  
  3.     //在代理角色内部对真实角色的引用  
  4.     private RealSubject realSubject;      
  5.       
  6.     //重写的实现抽象角色中的方法  
  7.     @Override  
  8.     public void request() {  
  9.           
  10.         this.preRequest();  //在调用真实角色操作之前所附加的操作  
  11.           
  12.         if (realSubject == null) {  
  13.             realSubject = new RealSubject();  
  14.         }  
  15.           
  16.         realSubject.request();  //真实角色所完成的事情  
  17.           
  18.         this.postRequest();  //在真实角色操作之后所附加的操作  
  19.     }  
  20.       
  21.     //自定义的方法,在真实方法执行之前调用的方法  
  22.     private void preRequest() {  
  23.         System.out.println("pre request");  
  24.     }  
  25.       
  26.     //自定义的方法,在真实方法执行之后调用的方法  
  27.     private void postRequest() {  
  28.         System.out.println("post request");  
  29.     }  
  30. }  
在实现代理角色对象时,最重要的一点是要有一个真实对象的引用。

通过这个引用在代理对象中去调用真实对象的方法,并且可以自定义一些其他的操作,比如本例子中的preRequest()和postRequest(),他们分别在之前和之后被调用。

这样做可以记录一下运行时间或打log之类的操作,但这里仅仅是打印了一句话。


4.最后看看main方法中如何调用:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class TestProxy {  
  2.   
  3.     public static void main(String[] args) {  
  4.         //实例化一个代理角色对象  
  5.         Subject subject = new ProxySubject();  
  6.         //用代理对象去调用定义的通用方法  
  7.         subject.request();  
  8.     }  
  9. }  
输出为:

pre request

this is real subject

post request

大家看代码后可以发现,这里并没有用到反射的知识。而且对于上面的例子来说,真实角色对象是事先必须已经存在的,并且必须是作为代理对象的内部属性。

如果这样的话,有一个真实角色那就必须在代理角色中有一个他的引用,如果在不知道真实角色的情况下又需要怎么办?这就需要"动态代理"来解决了。


Java反射学习总结四(动态代理使用实例和内部原理解析)

通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成。

动态代理知识点:

Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类:

1.InvocationHandler接口:    该接口中仅有一个方法

public object invoke(Object obj, Method method, Object[] args)

在实际使用时,obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象的invoke方法在代理类中动态实现。


2.Proxy类:  该类即为动态代理类,这里只介绍一下newProxyInstance()这个方法

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

这个方法是最主要的方法,它会返回代理类的一个实例,返回后的代理类可以当做被代理类使用

实现动态代理需4步骤:

1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法。

2.通过Proxy的静态方法newProxyInstance创建一个代理

3.创建被代理的类以及接口

4.通过代理调用方法

下面看这个例子具体说明如何通过上面的4个步骤来建立一个动态代理:


步骤1和步骤2合并写在一个类中,命名为DynamicProxy

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class DynamicProxy implements InvocationHandler {  
  2.   
  3.     // 需要被代理类的引用  
  4.     private Object object;  
  5.   
  6.     // 通过构造方法传入引用  
  7.     public DynamicProxy(Object object) {  
  8.         this.object = object;  
  9.     }  
  10.   
  11.     // 定义一个工厂类,去生成动态代理  
  12.     public Object getProxy() {  
  13.         // 通过Proxy类的newProxyInstance方法动态的生成一个动态代理,并返回它  
  14.         return Proxy.newProxyInstance(object.getClass().getClassLoader(), object  
  15.                 .getClass().getInterfaces(), this);  
  16.     }  
  17.   
  18.     // 重写的invoke方法,这里处理真正的方法调用  
  19.     @Override  
  20.     public Object invoke(Object obj, Method method, Object[] args)  
  21.             throws Throwable {  
  22.           
  23.         beforeDoing();  
  24.           
  25.         Object invoke = method.invoke(object, args);  
  26.           
  27.         afterDoing();  
  28.           
  29.         return invoke;  
  30.     }  
  31.       
  32.     public void beforeDoing() {  
  33.         System.out.println("before ............");  
  34.     }  
  35.       
  36.     public void afterDoing() {  
  37.         System.out.println("after ............."+"\n");  
  38.     }  
  39.   
  40. }  
该类实现了InvocationHandler接口,并且自定义了一个getProxy()方法去调用Proxy类的newProxyInstance()去生成一个动态代理。


步骤3:创建被代理的类以及接口

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //真实角色对象,继承自抽象角色,重写定义的方法。  
  2. public class RealSubject implements Subject1,Subject2{  
  3.   
  4.     //Subject1接口中的方法  
  5.     @Override  
  6.     public void request() {  
  7.         System.out.println("this is real subject");  
  8.     }  
  9.   
  10.     //Subject1接口中的方法  
  11.     @Override  
  12.     public void ask() {  
  13.         System.out.println("this is real ask");  
  14.           
  15.     }  
  16.       
  17.     //Subject2接口中的方法  
  18.     @Override  
  19.     public void request2() {  
  20.         System.out.println("this is real subject2");  
  21.           
  22.     }  
  23. }  
这个类就是我们需要被代理的类,他继承了两个接口分别是Subject1,Subject2

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. interface Subject1 {  
  2.       
  3.     public  void request();  
  4.       
  5.     public void ask();  
  6. }  
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. interface Subject2 {  
  2.       
  3.     public  void request2();  
  4. }  


4.通过代理调用方法

接下来在main方法中通过动态生成的代理来调用方法

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) {  
  2.           
  3.         //需要被代理的类  
  4.         RealSubject realSubject = new RealSubject();  
  5.         //用于创建动态代理的类,将被代理类的引用传递进去  
  6.         DynamicProxy dynamicProxy = new DynamicProxy(realSubject);  
  7.           
  8.         //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法  
  9.         Subject1 s1 = (Subject1) dynamicProxy.getProxy();  
  10.         s1.request();  
  11.         s1.ask();  
  12.           
  13.         //通过getProxy方法动态的获取代理类,转换成需要调用的接口类型后调用方法  
  14.         Subject2 s2 = (Subject2) dynamicProxy.getProxy();  
  15.         s2.request2();  
  16.     }  

最后打印:

before ............
this is real subject
after .............

before ............
this is real ask
after .............

before ............
this is real subject2
after .............

简单介绍动态代理内部实现原理:

例子看完了,肯定有如下疑问:

动态代理在哪里应用了反射机制?仅仅通过一个InvocationHandler接口和一个Proxy类的newProxyInstance方法是如何动态的生成代理?

下面就来简单的分析一下InvocationHandler,和Proxy的newProxyInstance方法是如何在运行时动态的生成代理的:


以下代码都是伪代码而且内容大部分参考马士兵动态代理的视频。如果感兴趣,建议找视频去学。


先看newProxyInstance是如何定义的

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

这里需要传入3个参数。先看第二个参数,传入一个接口类型的Class数组。

上面例子中传入的参数是object.getClass().getInterfaces()

object是被代理对象,这个参数就是通过反射拿到被代理对象的所有接口

在上面例子中就是我们定义的Subject1,Subject2接口了

有了接口数组,就可以通过类似下面的代码使用反射拿到接口中的所有方法

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. for (interface infce : interfaces[]) {  
  2.     Method[] methods = infce.getMethods();  
  3.     for (Method m : method) {  
  4.         m.getName();  
  5.     }  
  6. }  
在正常情况下,知道了被代理的接口和接口里面的方法就可以去生成代理类了。

大概就是下面这种的一个简单的实现:一个很固定的套路,只要知道实现接口和方法就仿照写出。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class ProxySubject implements Subject{  
  2.       
  3.         private RealSubject realSubject;      
  4.       
  5.     @Override  
  6.     public void request() {  
  7.           
  8.         realSubject.request();    
  9.     }  
  10. }  


动态代理还会在代理的方法中做一些其他的操作,如添加日志,时间,权限等操作。这时候就要靠InvocationHandler接口中的invoke方法。看看例子中如何实现的。

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public Object invoke(Object obj, Method method, Object[] args) throws Throwable {  
  3.           
  4.     beforeDoing();  
  5.           
  6.     Object invoke = method.invoke(object, args);  
  7.           
  8.     afterDoing();  
  9.           
  10.     return invoke;  
  11. }  

这些代码是我们自定义的,需要实现什么操作就写在里面。

这段代码存在于Invocationhandler对象中,这个对象会在调用Proxy的newProxyInstance的方法中传递进去。

这时候可以通过反射知道被调用方法的名字等信息,之后还是通过字符串的形式拼接处类似下面的动态代理类

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class ProxySubject implements Subject{  
  2.       
  3.         private RealSubject realSubject;      
  4.     @Override  
  5.     public void request() {  
  6.         Methond md = Subject.getMethod("methodName");  
  7.         handler.invoke(this, md);    
  8.     }  
  9. }  

这个大概就是根据传递的接口对象和InvocationHandler结合后应该生成的代理类。但现在的问题是如何去动态的生成上面这样的代理类。

答案是使用字符串拼接的方式。

从看上面的代码可以看出,除了接口和调用方法不同其他都相同。而且我们已经通过反射获得了方法和接口名字,这样就可以按着这个“套路”去用字符串拼接成这样的一类。

大概就是下面这种代码:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. String source = "package com.gxy.proxy;" + rt   
  2.   
  3.         + "public class "+ClassName+"implements "+InterfaceName+ rt    
  4.         + "{" + rt    
  5.         +       "private "+ ClassName + ClassName.toLowerCase()+" ; " + rt  
  6.                
  7.         +       "@Override"  
  8.         +       "public Void "+InterfaceName+ "()" + rt  + " {"  
  9.         +           "Method md = "+InterfaceName+".getMethod("+ methodName+");" +rt   
  10.         +           "hander.invoke(this, md);" + rt  
  11.         +       "}" + rt   
  12.         + "}";  

用反射生成的出来类名,接口名,方法名去动态的创建这样一个类的字符串。

之后就特定的方法去将这个字符串生成成类。在用反射把这个类取出来。这样就有了这个“动态”生成的代理类了。

就简单介绍到这吧。。。

最后大家可以发现例子中的动态代理里都是通过接口来实现的,如果对于不能实现接口的类就不能用JDK的动态代理了。如果想用就需要使用cglib了,因为cglib是针对类来实现的。

关于动态代理我研究了一个多礼拜,觉得理解起来还是比较困难的,勉勉强强的知道了个大概。

以后有时间我会继续深入的学习动态代理,但暂时还是以反射为主。下一篇准备写一下反射与注解的内容,希望大家多多支持。

Java反射学习总结五(Annotation(注解)-基础篇)


Annotation(注解)简介:

注解大家印象最深刻的可能就是JUnit做单元测试,和各种框架里的使用了。本文主要简单介绍一下注解的使用方法,下篇文章再深入的研究。

annotation并不直接影响代码语义,但是它能够被看作类似程序的工具或者类库,它会反过来对正在运行的程序语义有所影响。

annotation可以从源文件,class文件或者以在运行时反射的多种方式被读取


java注解系统自带有主要以下几个注解:

Override注解表示子类要重写(override)父类的对应方法

Deprecated注解表示方法是不建议被使用的

Suppress Warnings注解表示抑制警告


如何自定义注解:

只需要使用@interface来定义一个注解,例如:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //使用@interface来声明一个注解(实际上是自动继承了java.lang.annotation.Annotation接口)  
  2. public @interface AnnotationTest {  
  3.     String value1() default "hello";  //为注解设置String类型的属性Value1,并使用defalut关键字设置默认值  
  4.     EnumTest value2();      //设置枚举类型的value2  
  5.     String[] value3();      //设置数组类型的value3  
  6. }  
如何来使用注解呢,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @AnnotationTest(value2 = EnumTest.age, value3={""})  
  2. public class AnnotationUsage {  
  3.   
  4.     @AnnotationTest(value1 = "Test", value2 = EnumTest.name, value3={""})  
  5.     String test;  
  6.       
  7.     @AnnotationTest(value1 = "Test", value2 = EnumTest.name, value3={""})  
  8.     public void method(){  
  9.         System.out.println("usage of Annotation");  
  10.     }  
  11. }  
如上,注解可以标注在属性,方法,类上。

需要使用name=value这种赋值方式为属性赋值,因为value1设置了默认属性,所以可以忽略,如果没有设置默认值则所有的属性都要一一赋值。



还有一种特殊情况,如果注解里只定义了一个属性,名字是value,那么可以直接赋值,不需要使用name=value这种赋值方式,如下:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public @interface AnnotationTest {  
  2.     String value();  
  3. }  
  4.   
  5. @AnnotationTest("test")  
  6. public void method(){  
  7.     System.out.println("usage of Annotation");  
  8. }  

修饰注解的“注解”

注解也可以添加注解的“注解”去修饰,常用的有以下两个,一个是Retention,一个Target

Retention:

使用Retention必须要提供一个为java.lang.annotation.RetentionPolicy类型的的枚举

RetentionPolicy枚举有以下3个类型:

SOURCE编译程序时处理完Annotation信息后就完成任务

CLASS:编译程序将Annotation存储于class文件中,不可以由虚拟机读入

RUNTIME:编译程序将Annotation存储于class文件中,可以由虚拟机读入

用这三种Retention的Prolicy可以决定注解是从源文件,class文件或者以在运行时反射被读取

关于Retention的例子在最后


Target:

使用java.lang.annotation.Target可以定义注解被使用的位置

同样,在使用时要指定一个java.lang.annotation.ElementType的枚举值类型为他的“属性”

ElementType枚举的类型如下:

ANNOTATION_TYPE:适用于annotation
CONSTRUCTOR适用于构造方法
FIELD适用于field
LOCAL_VARIABLE适用于局部变量
METHOD适用于方法
PACKAGE适用于package
PARAMETER:适用于method上的parameter
TYPE适用于class,interface,enum

如下:定义一个注解MyTarget,设置Target类型为Method来修饰这个注解,这样这个注解只能标注在method的方法上

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Target(ElementType.METHOD)  
  2. public @interface MyTarget {  
  3.      String hello() default "hello";  
  4. }  

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @MyTarget  //这里则会报错,因为他标注在类上面了  
  2. public class MyTargetTest {  
  3.     @MyTarget   //标注在方法上不会报错  
  4.     public void doSomething(){  
  5.         System.out.println("hello world");  
  6.     }  
  7. }  


使用反射调用注解

在以下的类中Class Constructor Field Method Package等类都实现了AnnotatedElement接口
在接口中有以下重要的方法:
getAnnotations(Class annotationType)获取一个指定的annotation类型
getAnnotations() 获取所有的Annotation
getDeclaredAnnotations() 获取声明过的所有Annotation
isAnnotationPresent(Class<? extends Annotation> annotationClass)这个annotation是否出现

通过这些方法,配合反射我们就可以在程序运行时拿到注解的内容了,例子如下:
[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Retention(RetentionPolicy.RUNTIME) //定义一个注解,使用Retention标注为RUNTIME  
  2. public @interface MyAnnotation {  
  3.     String hello() default "hello";  
  4.     String world();  
  5. }  

该注解被标示为runtime类型,表示该注解最后可以保存在class文件中,并为java虚拟机在运行时读取到

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. @Retention(RetentionPolicy.CLASS)   //定义一个注解,Retention标注为RUNTIME  
  2. public @interface MyAnnotation2 {  
  3.     String hello() default "hello"//设置默认值为hello  
  4. }  
自定义的另一个注解Retention标示为class

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public class MyTest {  
  2.     @SuppressWarnings("unchecked")  //java自带的注解Retention的policy为SOURCE  
  3.     @Deprecated     //java自带的注解Retention的policy为RUNTIME  
  4.     @MyAnnotation(Name="Dean", Age="25")    //自定义的注解Retention的policy为RUNTIME  
  5.     @MyAnnotation2  //自定义的注解Retention的policy为CLASS  
  6.     public void TestMethod() {  
  7.         System.out.println("this is a method");  
  8.     }  
  9. }  
定义一个TestMethod方法,给他标示了4个注解,其中2个java自带的,2个我们自定义的。注解的的Retention属性各不相同。

下面定义一个测试类来验证结果:

[java] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. public static void main(String[] args) throws Exception {  
  2.           
  3.         MyTest myTest = new MyTest();  
  4.         //通过反射得到TestMethod方法  
  5.         Class<MyTest> c = MyTest.class;  
  6.         Method method = c.getMethod("TestMethod"new Class[]{});  
  7.           
  8.         //AnnotatedElement接口中的方法isAnnotationPresent(),判断传入的注解类型是否存在  
  9.         if (method.isAnnotationPresent(MyAnnotation.class)) {  
  10.             method.invoke(myTest, new Object[]{});  
  11.             //AnnotatedElement接口中的方法getAnnotation(),获取传入注解类型的注解  
  12.             MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);  
  13.             //拿到注解中的属性  
  14.             String name = myAnnotation.Name();  
  15.             String age = myAnnotation.Age();  
  16.             System.out.println("name:"+name +"   age:"+age);  
  17.         }  
  18.           
  19.         System.out.println("-----------------------------------");  
  20.           
  21.         //AnnotatedElement接口中的方法getAnnotations(),获取所有注解  
  22.         Annotation[] annotations = method.getAnnotations();  
  23.         //循环注解数组打印出注解类型的名字  
  24.         for (Annotation annotation : annotations) {  
  25.             System.out.println(annotation.annotationType().getName());  
  26.         }  
  27.     }  
打印结果为:

this is a method
name:Dean   age:25
-----------------------------------
java.lang.Deprecated
gxy.text.annotation.MyAnnotation


分割线上:介绍了如何使用AnnotatedElement接口中的方法和反射去调用注解

分割线下:证明了只有定义了Retention的Policy为Runtime的注解才可以被反射读取出来


 

Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!

本文是Java反射学习总结系列的最后一篇了,这里贴出之前文章的链接,有兴趣的可以打开看看。

http://blog.csdn.net/a396901990/article/category/2302221

本文介绍了如何利用反射和注解去简单的模拟JUnit4单元测试框架,之所以选择JUnit4是因为4.0以后最大的改进就是使用了注解。需要注意的是这里并不是完全的模拟,只是简单实现了一下Runner类和JUnit注解相关的工作流程。所以本文的主要目的是介绍反射和注解的使用。废话不多说,直接进入正文。

首先来看一个Junit单元测试的小例子:

先定义一个简单的类,里面只有一个add计算加法的方法和一个divide计算除法的方法,divide方法需要判断除数不能为0否则抛出异常。

[java] view plaincopy
  1. public class calculate {  
  2.   
  3.     public int add(int a, int b) {  
  4.         return a + b;  
  5.     }  
  6.   
  7.     public int divide(int a, int b) throws Exception {  
  8.         if (0 == b) {  
  9.             throw new Exception("除数不能为0");  
  10.         }  
  11.         return a / b;  
  12.     }  
  13.   
  14. }  

接着写一个简单的JUnit测试类,对他进行单元测试

[java] view plaincopy
  1. import static org.junit.Assert.*;  
  2. import org.junit.After;  
  3. import org.junit.Before;  
  4. import org.junit.Test;  
  5.   
  6. public class calulateTest {  
  7.   
  8.     private calculate cal;  
  9.   
  10.     @Before     //使用JUint提供的注解标注此方法在执行测试方法前执行  
  11.     public void before() throws Exception {  
  12.         cal = new calculate();  
  13.         System.out.println("------------------");  
  14.         System.out.println("before test");  
  15.     }  
  16.   
  17.     @After      //使用JUint提供的注解标注此方法在执行测试方法后执行  
  18.     public void after() throws Exception {  
  19.         System.out.println("after test");  
  20.     }  
  21.   
  22.     @Test       //使用JUint提供的注解标注此方法为需要测试的方法  
  23.     public void addTest() {  
  24.         System.out.println("do add test");  
  25.         int result = cal.add(1020);  
  26.         //判断result和预期的值是否相等,在此例中如果result等于30则测试通过  
  27.         assertEquals(30, result);  
  28.     }  
  29.       
  30.     @Test(expected = Exception.class)  //使用JUnit的Test注解,并且判断预期值是否是Exception  
  31.     public void div() throws Exception {  
  32.         System.out.println("do divide test");  
  33.         cal.divide(10);   //调用1除以0,抛出异常  
  34.     }  
  35.   
  36. }  

执行结果为:

before test
do add test
after test
------------------
before test
do divide test
after test


下面我们就用反射和注解的知识来模拟JUnit对于上面例子的实现。

这里先不着急看代码,先看梳理一下思路。

1.JUnit只可以知道一件事,那就是待测试类的名字,其他的一概不知。所以我们只能利用测试类的名字作为切入口

2.通过测试类的名字,使用反射去获取他的Class对象

3.然后通过该Class对象获得当前类中所有方法的Method数组

4.遍历这个Method数组,取得每一个Method对象

5.调用每一个Method对象的isAnnotationPresent(Annotation.class)方法,判断该方法是否被指定注解所修饰

6.本例中根据不同的注解,来判断调用方法的顺序。

7.如果Test注解有属性的话,则判断方法执行后的返回值,如果返回值等于预期的注解属性也就是expected = Exception.class则测试通过。

8.最后还有一个assertEquals方法,他去判断预期值和实际值是否相等来决定测试是否通过。


大致的思路有了,我们就可以开始模拟它了。

首先定义3个注解,分别是Before,Test,After。如果对于定义注解不清楚的同学请看我之前写的文章。

[java] view plaincopy
  1. @Target(ElementType.METHOD)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. public @interface Before {  
  4.       
  5. }  
[java] view plaincopy
  1. @Target(ElementType.METHOD)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. public @interface Test {  
  4.     Class<? extends Object> expected() default String.class;  
  5. }  
[java] view plaincopy
  1. @Target(ElementType.METHOD)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. public @interface After {  
  4.   
  5. }  
三个很简单的注解,都标注只能修饰方法,保留策略为运行时,这样可以被反射读取到。

只有Test注解中定义了一个属性,类型可以为任何类型的Class对象,默认值为String类型的Class对象。


接下来定义我们模拟的JUnit类,这里为了方便我将所有能用到的都写在一个MyJUnit类中。他对外只有一个构造方法和一个run方法。还有一个对比用的assertEquals方法

[java] view plaincopy
  1. public class MyJUnit {  
  2.   
  3.     // 存放所有标注了before注解的集合  
  4.     private List<Method> beforeMethod;  
  5.     // 存放所有标注了after注解的集合  
  6.     private List<Method> afterMethod;  
  7.     // 存放所有标注了test注解的集合  
  8.     private List<Method> testMethod;  
  9.     // 存放需要捕捉的异常集合  
  10.     private static List<Exception> exceptions;  
  11.     // 被测试类的实例化对象  
  12.     private Object object;  
  13.     // 被测试类的Class对象  
  14.     private Class<?> testClass;  
  15.   
  16.     //自定义MyJUnit类的构造方法,用于接收被测试类的名字然后初始化需要的变量和方法  
  17.     public MyJUnit(String testName) {  
  18.         super();  
  19.         try {  
  20.             beforeMethod = new ArrayList<>();  
  21.             afterMethod = new ArrayList<>();  
  22.             testMethod = new ArrayList<>();  
  23.             exceptions = new ArrayList<>();  
  24.               
  25.             //使用反射根据传递的类名生成对象  
  26.             testClass = Class.forName(testName);  
  27.             object = testClass.newInstance();  
  28.   
  29.             //获取所有的方法并根据注解进行分类  
  30.             getAllMethods();  
  31.               
  32.         } catch (Exception e) {  
  33.             e.printStackTrace();  
  34.         }  
  35.     }  
  36.   
  37.     // 根据注解获取所有的方法  
  38.     private void getAllMethods() {  
  39.         Method[] methods = testClass.getMethods();  
  40.         for (Method m : methods) {  
  41.             // 找到被before修饰的方法,放入before方法的集合中  
  42.             if (m.isAnnotationPresent(Before.class)) {  
  43.                 beforeMethod.add(m);  
  44.             }  
  45.             // 找到被After修饰的方法,放入after方法的集合中  
  46.             if (m.isAnnotationPresent(After.class)) {  
  47.                 afterMethod.add(m);  
  48.             }  
  49.             // 找到被test修饰的方法,放入test方法的集合中  
  50.             if (m.isAnnotationPresent(Test.class)) {  
  51.                 testMethod.add(m);  
  52.             }  
  53.         }  
  54.     }  
  55.   
  56.     // run方法  
  57.     public void run() {  
  58.         // 运行所有的测试方法  
  59.         for (Method method : testMethod) {  
  60.             runTest(method);  
  61.         }  
  62.   
  63.         // 判断捕捉的异常集合,如果大小为0则没有异常,测试通过。有异常则表示测试不通过并打印异常信息  
  64.         if (exceptions.size() == 0) {  
  65.             System.out.println("通过测试");  
  66.         } else {  
  67.             for (Exception e : exceptions) {  
  68.                 System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");  
  69.                 System.out.println("测试不通过,错误的内容为:");  
  70.                 e.printStackTrace();  
  71.             }  
  72.         }  
  73.     }  
  74.   
  75.     // 按照before test after的顺序执行  
  76.     private void runTest(Method method) {  
  77.         try {  
  78.             runBefores();  
  79.             runTestMethod(method);  
  80.             runAfters();  
  81.         } catch (Exception e) {  
  82.             e.getMessage();  
  83.             throw new RuntimeException(  
  84.                     "test should never throw an exception to this level");  
  85.         }  
  86.     }  
  87.   
  88.     // 执行所有after标注的方法  
  89.     private void runAfters() throws Exception {  
  90.         for (Method m : afterMethod) {  
  91.             m.invoke(object, new Object[] {});  
  92.         }  
  93.     }  
  94.   
  95.     // 执行所有before标注的方法  
  96.     private void runBefores() throws Exception {  
  97.         for (Method m : beforeMethod) {  
  98.             m.invoke(object, new Object[] {});  
  99.         }  
  100.     }  
  101.   
  102.     // 执行test方法  
  103.     private void runTestMethod(Method method) {  
  104.         // 判断测试是否通过的标志  
  105.         boolean passCheck = false;  
  106.         try {  
  107.             // 获得Test注解  
  108.             Test testAnnotation = method.getAnnotation(Test.class);  
  109.             // 判断test注解标注的属性是否为需要的类型,这里可以根据需要加入不同的判断条件  
  110.             if (testAnnotation.expected().newInstance() instanceof Exception) {  
  111.                 passCheck = true;  
  112.             }  
  113.             method.invoke(object);  
  114.         } catch (Exception e) {  
  115.             // 判断通过则测试通过,否者将异常加入到异常集合中  
  116.             if (passCheck) {  
  117.                 return;  
  118.             } else {  
  119.                 addExceptions(e);  
  120.             }  
  121.         }  
  122.     }  
  123.   
  124.     private static void addExceptions(Exception e) {  
  125.         exceptions.add(e);  
  126.     }  
  127.   
  128.     // 自定义的assertEquals方法,判断两个值是否相等,可以根据需要加入更多类型的判断,如果相等则通过,否则new一个异常加入到异常集合中  
  129.     static public void assertEquals(Object expected, Object actual) {  
  130.         if (expected.equals(actual)) {  
  131.             return;  
  132.         } else {  
  133.             addExceptions(new Exception("预期值与实际值不相等"));  
  134.         }  
  135.     }  
  136. }  
注解和JUnit类都定义好后可以写测试的方法了,和之前的测试方法没有区别,只是这次导包导入的都是我们自定义的包。

[java] view plaincopy
  1. import static gxy.test.Junit.MyJUnit.*;  
  2.   
  3. public class MyCalulateTest {  
  4.   
  5.     private Calculate cal;  
  6.   
  7.     @Before  
  8.     public void before() throws Exception {  
  9.         cal = new Calculate();  
  10.         System.out.println("------------------");  
  11.         System.out.println("before test");  
  12.     }  
  13.   
  14.     @After  
  15.     public void after() throws Exception {  
  16.         System.out.println("after test");  
  17.     }  
  18.   
  19.     @Test  
  20.     public void addTest() {  
  21.         System.out.println("do add test");  
  22.         int result = cal.add(1020);  
  23.         // 这里的预期值为40,实际为30,所以这个方法通过不了测试  
  24.         assertEquals(40, result);  
  25.     }  
  26.   
  27.     @Test(expected = Exception.class)  
  28.     public void divTest() throws Exception {  
  29.         System.out.println("do divide test");  
  30.         // 调用1除以0,抛出异常  
  31.         cal.divide(10);  
  32.     }  
  33.   
  34. }  
为了检验测试效果,这里对于addTest的方法中assertEquals方法传入的预期值和实际值不同。

下面看最后的运行类。

[java] view plaincopy
  1. public static void main(String[] args) throws Exception {  
  2.         MyJUnit myJUnit = new MyJUnit("gxy.test.Junit.MyCalulateTest");  
  3.         myJUnit.run();  
  4.     }  
只有2行代码,传入需要测试的类的名字,然后执行run方法。

测试结果:

------------------

before test

do add test
after test
------------------
before test
do divide test
after test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
测试不通过,错误的内容为:
java.lang.Exception: 预期值与实际值不相等
at gxy.test.Junit.MyJUnit.assertEquals(MyJUnit.java:139)
at gxy.test.Junit.MyCalulateTest.addTest(MyCalulateTest.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at gxy.test.Junit.MyJUnit.runTestMethod(MyJUnit.java:119)
at gxy.test.Junit.MyJUnit.runTest(MyJUnit.java:85)
at gxy.test.Junit.MyJUnit.run(MyJUnit.java:66)
at gxy.test.Junit.FinalTest.main(FinalTest.java:13)

本文只是简单的模拟了一下在JUnit中反射和注解的使用,而且在很多框架中很多都利用了反射和注解这对黄金组合来实现一些如权限判断,调用等等很多功能。所以说反射还是值得好好学习和研究的。

反射学习总结系列博文断断续续写了一个多月,这篇是最后一篇了。通过这一个月的学习对反射的基本概念和使用算是有了一个了解,有时间还需要深入的学习。

这里需要提一下,学习的资料主要是从网上下的系列视频。主要借鉴了其中中的思路和一些概念类的东西,但是文章中的例子都是我自己写的。最后向大家推荐一下这个视频吧,不是做广告,讲的确实不错,讲课的老师叫张龙,口齿清晰讲的很深入。在大学时看的马士兵的视频,比较适合入门,这个适合晋级。再想继续晋级就得看书了,哈哈。


0 0
原创粉丝点击