详解Java反射机制

1.Java反射机制:反射机制允许程序在运行时通过反射的API获取类中的描述,方法,并且允许我们在运行时改变fields内容或者去调用methods

2.反射机制提供的功能:
             1.运行时判断任意一个对象所属的类
             2.运行时构造任意一个类的对象
             3.运行时判断任意一个类所具有的成员变量和方法
             4.运行时调用任意一个对象的方法

     1.运行时检测对象的类型
     2.动态构造某个类的对象
     3.检测类的属性和方法
     4.任意调用对象的方法
     5.修改构造函数、方法、属性的可见性             
     6.通过Class对象,获取该类实现的接口,父类,声明的属性
     7.获取该类对象所拥有的方法和属性,还可以获得该类的构造方法及通过构造方法获得实例。也可以动态的调用这个实例的成员方法。
     
     eg:
       1.获取对象类型名:
                   obj.getClass.getName();
                   
       2.调用未知对象的方法:   --- 如果对象类型是未知的,通过反射,判断它是否包含  print 方法,并调用它
       
                   public class ReflectionHelloWorld {
                        public static void main(String[] args){
                            Foo f = new Foo();
                     
                            Method method;
                            try {
                                method = f.getClass().getMethod("print", new Class<?>[0]);
                                method.invoke(f);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }          
                        }
                    }
                     
                    class Foo {
                        public void print() {
                            System.out.println("abc");
                        }
                    }           


       3.创建对象:        
                1.通过Class对象的 newInstance()方法
                    
                    Class<?> c =Class.forName("demo.Foo");
                    Foo f = (Foo)c.newInstance();
                    
                2.获取构造函数,通过构造函数,创建对象     --  Constructor<?>对象 的newInstance()方法
                
                    import java.lang.reflect.Constructor;
 
                    public class ReflectionHelloWorld {
                        public static void main(String[] args){
                            // 创建Class实例
                            Class<?> c = null;
                            try{
                                c=Class.forName("myreflection.Foo");
                            }catch(Exception e){
                                e.printStackTrace();
                            }
                     
                            // 创建Foo实例
                            Foo f1 = null;
                            Foo f2 = null;
                     
                            // 获取所有的构造函数
                            Constructor<?> cons[] = c.getConstructors();
                     
                            try {
                                f1 = (Foo) cons[0].newInstance();
                                f2 = (Foo) cons[1].newInstance("abc");
                            } catch (Exception e) {
                                e.printStackTrace();
                            }  
                     
                            f1.print();
                            f2.print();
                        }
                    }
                     
                    class Foo {
                        String s;
                     
                        public Foo(){}
                     
                        public Foo(String s){
                            this.s=s;
                        }
                     
                        public void print() {
                            System.out.println(s);
                        }
                    }


       4.实例化Class类对象的三种方式:
                    Class.forName("Reflect.Demo");
                    new Demo().getClass();
                    Demo.class;
       
       5.通过Class实例化其他类对象:
                    Class<?>  demo=Class.forName("Reflect.Person");
                    Constructor<?> cons[]=demo.getConstructors();
                    Object object = cons[0].newInstance(args);

       6.通过Class实例化接口 

                Class<?> intes[]=demo.getInterfaces();
                
       7.取得其他类中的父类 
                
                Class<?> superClass=demo.getSuperclass();
       
       8.调用类中的方法:1.获取到要执行的方法  Method method = obj.getMethod("方法名");
                    2.调用方法  method.invoke(对象实例)
                
                Demo demo = Class.forName("demo.Demo");
                Method method = demo.getMethod("toString");
                method.invoke(demo.newInstance());
                
                //获得所有的方法
                Method[] methods = demo.getDeclaredMethods(); 
                 
       9.通过反射操作属性:
        
                  Field field = demo.getDeclaredField("sex");
                  field.setAccessiable(true);
                  field.set(obj,"男");
                  
                  Field[] fields = Demo.class.getDeclaredFields(); //类中任何可见性的属性不包括基类
                  fields = Demo.class.getFields();  //只能获得public属性包括基类的
       
       10.使用数组:
       
                Class string = Class.forName("java.lang.String"); 
                Object object= Array.newInstance(string, 10); 
                Array.set(object, 5, "this is a test"); 
                 String s = (String) Array.get(arr, 5);
</pre><pre code_snippet_id="660991" snippet_file_name="blog_20150507_1_5011483" name="code" class="html">       11.调用对象的方法
</pre><pre code_snippet_id="660991" snippet_file_name="blog_20150507_1_5011483" name="code" class="html">public class InvokeTester {

 public int add(int param1, int param2) {
  return param1 + param2;
 }

 public String echo(String msg) {
  return "echo: " + msg;
 }

 public static void main(String[] args) throws Exception {
  Class<?> classType = InvokeTester.class;
  Object invokeTester = classType.newInstance();

  // Object invokeTester = classType.getConstructor(new
  // Class[]{}).newInstance(new Object[]{});

  // 获取InvokeTester类的add()方法
  Method addMethod = classType.getMethod("add", new Class[] { int.class,
    int.class });
  // 调用invokeTester对象上的add()方法
  Object result = addMethod.invoke(invokeTester, new Object[] {
    new Integer(100), new Integer(200) });
  System.out.println((Integer) result);

  // 获取InvokeTester类的echo()方法
  Method echoMethod = classType.getMethod("echo",
    new Class[] { String.class });
  // 调用invokeTester对象的echo()方法
  result = echoMethod.invoke(invokeTester, new Object[] { "Hello" });
  System.out.println((String) result);
 }

}

 

 

输出结果:

300
echo: Hello
</pre><pre code_snippet_id="660991" snippet_file_name="blog_20150507_1_5011483" name="code" class="html">

InvokeTester类的main()方法中,运用反射机制调用一个InvokeTester对象的add()和echo()方法

       add()方法的两个参数为int 类型,获得表示add()方法的Method对象的代码如下:

       Method addMethod=classType.getMethod("add",new Class[]{int.class,int.class}); Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象,如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象,如果实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回。

       在本例中,尽管InvokeTester 类的add()方法的两个参数以及返回值都是int类型,调用add Method 对象的invoke()方法时,只能传递Integer 类型的参数,并且invoke()方法的返回类型也是Integer 类型,Integer 类是int 基本类型的包装类:

      Object result=addMethod.invoke(invokeTester, new Object[]{new Integer(100),new Integer(200)});  

      System.out.println((Integer)result); //result 为Integer类型

  12.<span style="font-family: Arial, Helvetica, sans-serif;">动态创建和访问数组</span><pre name="code" class="java">
 

       java.lang.Array 类提供了动态创建和访问数组元素的各种静态方法。 例程ArrayTester1 类的main()方法创建了一个长度为10 的字符串数组,接着把索引位置为5 的元素设为“hello”,然后再读取索引位置为5 的元素的值:

 

package reflect3;

 

import java.lang.reflect.Array;

public class ArrayTester1 {

 public static void main(String[] args) throws Exception{
  Class<?> classType = Class.forName("java.lang.String");
  // 创建一个长度为10的字符串数组
  Object array = Array.newInstance(classType, 10);
  // 把索引位置为5的元素设为"hello"
  Array.set(array, 5, "hello");
  // 获得索引位置为5的元素的值
  String s = (String) Array.get(array, 5);
  System.out.println(s);

 }
}
输出结果:

hello

 
     
13. 运行时变更field内容
 
        与先前两个动作相比,“变更field内容”轻松多了,因为它不需要参数和自变量。首先调用Class的getField()并指定field名称。获得特定的Field object之后便可直接调用Field的get()和set()。

 

package reflect3;

 

import java.lang.reflect.Field;

public class RefField {

 public double x;

 public Double y;

 public static void main(String args[]) throws NoSuchFieldException,
   IllegalAccessException {
  Class c = RefField.class;
  Field xf = c.getField("x");
  Field yf = c.getField("y");

  RefField obj = new RefField();

  System.out.println("变更前x=" + xf.get(obj));
  //变更成员x值
  xf.set(obj, 1.1);
  System.out.println("变更后x=" + xf.get(obj));

  System.out.println("变更前y=" + yf.get(obj));
  //变更成员y值
  yf.set(obj, 2.1);
  System.out.println("变更后y=" + yf.get(obj));
 }

}
运行结果:

变更前x=0.0
变更后x=1.1
变更前y=null
变更后y=2.1


=============================反射应用========================== JDK动态代理: 与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。 public class MyHandler implements InvocationHandler { private Object target; public Object bind(Object target) { this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("事物开始"); Object result = method.invoke(target, args); System.out.println("事物结束"); return result; } }Cglib动态代理 JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 public class MyCglib implements MethodInterceptor { private Object target; public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override // 回调方法 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("事物开始"); proxy.invokeSuper(obj, args); System.out.println("事物结束"); return null; } }
 



3.Java Reflection APIs:


           1.Class类:       代表一个类
           2.Field类:      代表类的成员变量(类的属性)
           3.Method类:   代表类的方法
           4.Constructor类:   代表类的构造方法
           5.Array类:     提供了动态创建数组,以及访问数组的元素的静态方法
           
                      
4.获取要处理的类或对象的Class对象: 三种方式:   ---- 获取Class对象,通过完整类名或对象的方式


   1.使用Class的 forName()
                        eg:Class.forName("java.lang.Class");
                        
   2.使用XXX.class语法
                        eg:String.class
                        
   3.使用具体某个对象.getClass方法
                        eg:
                           String str = "abc";
                           Class<?> clz = str.getClass;                     


先看一个例子:这个例子对于指定的类名,使用反射来获取该类中的所有声明的方法,(使用第一种获取Class对象的方法)(主要代码如下:):

?

/**
  * 使用反射来获取Class中的生命的方法,包括私有的方法
  */
import java.lang.reflect.Method;
public class Reflection1 {
     public static void main(String[] args)  throws Exception {
         //使用Class去调用静态方法forName()获得java.lang.Class的Class对象
         Class<?> tClass = Class.forName( "java.lang.Class" );
         //获取该class中声明的所有方法
         Method[] methods = tClass.getDeclaredMethods();
         for (Method method : methods) {
             System.out.println(method);
         }
     }
}
      


5.Class类的API常用方法:

            ①: getName():获得类的完整名字。
            ②: getFields():获得类的public类型的属性。
            ③: getDeclaredFields():获得类的所有属性。
            ④: getMethods():获得类的public类型的方法。
            ⑤: getDeclaredMethods():获得类的所有方法。

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

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

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

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


         先看上面的⑧和⑨其中都能生成对象,但是因为构造函数有无参和有参构造函数两种,所以我们分两种情况考虑


 情况一:如果是无参的构造函数来生成对象:

          <a>首先我们去获取Class对象,然后直接通过Class对象去调用newInstance()方法就可以

?
1
2
Class<?> tclass = Reflection2. class ;
Object reflection2 = classType.newInstance();
          <b>首先我们也是去获取Class对象,然后去去调用getConstructor()得到Constructor对象,接着直接调用newInstance()即可

?
1
2
3
4
      Class<?> classType = Reflection2. class ;
// Object reflection2 = classType.newInstance();
      Constructor<?> constructor = classType.getConstructor( new Class[] {});
Object reflection2 = constructor.newInstance( new Object[] {});
            
 情况二:现在是有参构造函数,那我们只有一种方法来通过反射生成对象:           

?
1
2
3
Class<?> tClass = Person. class
Constructor cons = classType.getConstructor( new Class[]{String. class int . class });  
Object obj = cons.newInstance( new Object[]{“zhangsan”,  19 });
            

   接下来根据以上的一些常用的方法,使用反射举几个例子(使用反射来访问类中的方法):

?
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
package com.jiangqq.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
  * 反射练习二,使用反射访问类中的方法
  *
  * @author jiangqq
  *
  */
public class Reflection2 {
     public int sum( int a,  int b) {
         return a + b;
     }
     public String addStr(String str) {
         return "This is the:" + str;
     }
     public static void main(String[] args)  throws Exception {
         Class<?> classType = Reflection2. class ;
         // Object reflection2 = classType.newInstance();
         Constructor<?> constructor = classType.getConstructor( new Class[] {});
         Object reflection2 = constructor.newInstance( new Object[] {});
         // 通过反射进行反射出类中的方法
         Method sumMethod = classType.getMethod( "sum" new Class[] {  int . class ,
                 int . class });
         //invoke方法的值永远只是对象
         Object result1 = sumMethod.invoke(reflection2,  new Object[] {  6 10 });
         System.out.println((Integer) result1);
         Method addStrMethod = classType.getMethod( "addStr" ,
                 new Class[] { String. class });
         Object result2 = addStrMethod.invoke(reflection2,
                 new Object[] {  "tom" });
         System.out.println((String) result2);
     }
}


           ④:通过反射机制调用对象的私有方法,访问对象的私有变量....

我们大家都知道,在Java语言中,如果我们对某些变量,或者方法进行private的声明,然后我们在其他类中进行不能去调用这些方法和变量,但是通过反射机制,这些私有声明将不复存在【提醒一点:在写程序的时候,我们最好不要故意经常去使用反射机制来打破这种私有保护...】

            要实现这种功能,我们需要用到AccessibleObject类中的public void setAccessible(boolean flag)方法:

 使用这个方法,把参数flag设置成true,然后我们的field或者method就可以绕过Java语言的语法访问的检查

  具体使用如下:

  <a>使用反射去访问私有方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.jiangqq.reflection;
public class Test01 {
     private String getName(String name) {
         return "This i:" + name;
     }
}
 
 
package com.jiangqq.reflection;
import java.lang.reflect.Method;
public class TestPrivate01 {
     public static void main(String[] args)  throws Exception {
         Test01 p =  new Test01();
         Class<?> classType = p.getClass();
         Method method = classType.getDeclaredMethod( "getName" ,
                 new Class[] { String. class });
         method.setAccessible( true );
         Object object = method.invoke(p,  new Object[] {  "tom" });
         System.out.println((String)object);
     }
}

<b>使用反射机制去访问私有变量:

  

?
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
package com.jiangqq.reflection;
public class Test02 {
   private String name= "张三" ;
   private String getName()
   {
       return name;
   }
}
 
 
package com.jiangqq.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestPrivate02 {
     public static void main(String[] args)  throws Exception {
         Test02 p =  new Test02();
         Class<?> classType = p.getClass();
         Field field = classType.getDeclaredField( "name" );
         //设置true,使用可以绕过Java语言规范的检查
         field.setAccessible( true );
         //对变量进行设置值
         field.set(p,  "李四" );
         Method method = classType.getDeclaredMethod( "getName" new Class[] {});
         method.setAccessible( true );
         Object object = method.invoke(p,  new Object[] {});
         System.out.println((String) object);
     }
}





反射技术的应用

反射技术大量用于Java设计模式和框架技术,最常见的设计模式就是工厂模式(Factory)和单例模式(Singleton)。

  1. 单例模式(Singleton)

       保证在Java应用程序中,一个类Class只有一个实例存在。

如:建立目录,数据库连接都需要这样的单线程操作。

目的: 节省内存空间,保证我们所访问到的都是同一个对象。

 

       单例模式要求保证唯一 可以通过静态变量保证单列模式的唯一性。

 

单例模式有以下两种形式:

  1. 第一种形式:

package reflect;

 

public class Singleton {

    /*

     * 注意这是private私有的构造方法, 只供内部调用

     * 外部不能通过new的方式来生成该类的实例

     */

    private Singleton() {

    }

 

    /*

     * 在自己内部定义自己一个实例,是不是很奇怪?

     * 定义一个静态的实例,保证其唯一性

     */

    private static Singleton instance = new Singleton();

 

    // 这里提供了一个供外部访问本class的静态方法,可以直接访问

    public static Singleton getInstance() {

           return instance;

    }

   

}

 

/**

 *测试单例模式

 */

class SingRun{

    public static void main(String[] args){

       //这样的调用不被允许,因为构造方法是私有的。

       //Singleton x=new Singleton();

      

       //得到一个Singleton类实例

       Singleton x=Singleton.getInstance();

      

       //得到另一个Singleton类实例

       Singleton y=Singleton.getInstance();

      

       //比较x和y的地址,结果为true。说明两次获得的是同一个对象

       System.out.println(x==y);

    }

}

 

 

  1. 第二种形式:

 

public class Singleton {

 

    //先申明该类静态对象

    private static Singleton instance = null;

   

    //创建一个静态访问器,获得该类实例。加上同步机制,防止两个线程同时进行对对象的创建

    public static synchronized Singleton getInstance() {

      

       //如果为空,则生成一个该类实例

       if (instance == null){

           instance = new Singleton();

       }

       return instance;

    }

}

 

两种形式大体上是差不多的

  1. 工厂模式(Factory)

       工厂模式:著名的Jive论坛 ,就大量使用了工厂模式。

为什么工厂模式是如此常用?因为工厂模式利用Java反射机制和Java多态的特性可以让我们的程序更加具有灵活性。用工厂模式进行大型项目的开发,可以很好的进行项目并行开发。就是一个程序员和另一个程序员可以同时去书写代码,而不是一个程序员等到另一个程序员写完以后再去书写代码。其中的粘合剂就是接口和配置文件。

利用接口可以将调用和实现相分离。

 

那么这是怎么样去实现的呢?工厂模式可以为我们解答。

 

我们先来了解一下软件的生命周期: 分析、设计、编码、调试、测试。

分析: 就是指需求分析,就是知道这个软件要做成什么样子,要实现什么样的功能。

设计: 设计的时候要考虑到怎么样高效的实现这个项目,如果让一个项目团队并行开发。这时候,通常先设计接口,把接口给实现接口的程序员和调用接口的程序员,在编码的时候,两个程序员可以互不影响的实现相应的功能,最后通过配置文件进行整合。

 

 

代码示例:

 

/**

 *

 *定义接口

 */

package com.reflection.Factory;

/**

 * @interface: InterfaceTest

 * @authorchenliang

 * @description: 声明一个测试接口

 */

public interface InterfaceTest {

      public void getName();//定义获得名字的方法

}

 

实现这个接口,重写其中定义的方法

 

接口实现方:

 

 

/**

 * @class: Test1

 * @authorchenliang

 * @description: 测试类

 */

 

package com.reflection.Factory;

 

public class Test1 implements InterfaceTest {

   @Override

   public void getName() {  //测试方法

      System.out.println("test1");

   }

}

 

 

 

/**

 * @class: Test2

 * @authorchenliang

 * @description: 测试类

 */

package com.reflection.Factory;

 

public class Test2 implements InterfaceTest {

 

   @Override

   public void getName() { //测试方法

      System.out.println("test2");

   }

}

 

可以发现,当接口定义好了以后,不但可以规范代码,而且可以让程序员有条不紊的进行功能的实现。实现接口的程序员根本不用去管,这个类要被谁去调用。

 

那么怎么能获得这些程序员定义的对象呢?在工厂模式里,单独定义一个工厂类来实现对象的生产,注意这里返回的接口对象。

 

工厂类,生产接口对象:

/**

 * 本类为工厂类,用于生成接口对象

 */

/**

 * @class: Factory

 * @author: chenliang

 * @description: 模仿创建一个工厂类

 */

 

package com.reflection.Factory;

 

import java.io.InputStream;

import java.util.Properties;

 

public class Factory{

 

  private static Properties prop=new Properties();  //创建私有的静态的Properties对象

 

  static{  //静态代码块,在创建这个类的实例之前执行,且只执行一次,用来加载配置文件

     try {

        InputStream ips = Factory.class.getClassLoader().getResourceAsStream("file.properties");  //加载配置文件

        prop.load(ips);

     } catch (Exception e) {

        e.printStackTrace();

     }

  }

 

  /**

   * 单例模式,保证该类只有一个Factory对象

   */

  private static Factory factory=new Factory();

 

  private Factory(){}   //构建一个私有构造方法

 

  public static Factory getFactory(){  //返回工厂对象的方法

     return factory;

  }

 

  /**

   * 本方法为公有方法,用于生产接口对象

   * @return: InterfaceTest接口对象

   */

  public  InterfaceTest getInterface(){

    

     InterfaceTest interfaceTest = null;  //定义接口对象

    

     try {

        String classInfo = prop.getProperty("test");   //根据键,获得值,这里的值是类的全路径

        Class<?> c = Class.forName(classInfo);  //利用反射,生成Class对象

        Object obj = c.newInstance();  //获得该Class对象的实例

        interfaceTest = (InterfaceTest)obj;  //将Object对象强转为接口对象

     } catch (Exception e) {

        e.printStackTrace();

     }

     return interfaceTest; //返回接口对象

  }

}

配置文件内容:

test=com.reflection.Factory.Test1

 

通过这个类,可以发现,在调用的时候,得到的是个接口对象。而一个接口变量可以指向实现了这个接口的类对象。在利用反射的时候,我们并没有直接把类的全路径写出来,而是通过键获得值。这样的话,就有很大的灵活性,只要改变配置文件里的内容,就可以改变我们调用的接口实现类,而代码不需做任何改变。在调用的时候,我们也是通过接口调用,甚至我们可以连这个接口实现类的名字都不知道。

 

调用方:

 

/**

 * @authorchenliang

 * @class: FactoryTest

 * @description: 测试工厂类

 */

 

 

package com.reflection.Factory;

 

public class FactoryTest {

 

   public static void main(String[] args) {

     

      Factory factory = Factory.getFactory(); //获得工厂类的实例

     

      InterfaceTest interObj = factory.getInterface();  //调用获得接口对象的方法,获得接口对象

     

      interObj.getName();   //调用接口定义的方法

   }

}

 

 

上面的代码就是调用方法。大家可以发现,在调用的时候,我们根本没有管这个接口定义的方法要怎么样去实现它,我们只知道这个接口定义这个方法起什么作用就行了。上面代码运行结果要根据配置文件来定。如果配置文件里的内容是test= com.reflection.Factory.Test2。那么表示调用com.reflection.Factory.Tes2这个类里实现接口的方法,这时候打印“test2”。如果配置文件里的内容是test= com.reflection.Factory.Test1。那么表示调用com.reflection.Factory.Test1这个类里实现接口的方法,这时候打印“test1”。

 

反射机制是框架技术的原理和核心部分。通过反射机制我们可以动态的通过改变配置文件(以后是XML文件)的方式来加载类、调用类方法,以及使用类属性。这样的话,对于编码和维护带来相当大的便利。在程序进行改动的时候,也只会改动相应的功能就行了,调用的方法是不用改的。更不会一改就改全身。




利用反射解析自定义注解


Java注解能够提供代码的相关信息,同时对于所注解的代码结构又没有直接影响。在这篇教程中,我们将学习Java注解,如何编写自定义注解,注解的使用,以及如何使用反射解析注解。

注解是Java 1.5引入的,目前已被广泛应用于各种Java框架,如Hibernate,Jersey,Spring。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效。

在注解诞生之前,程序的元数据存在的形式仅限于java注释或javadoc,但注解可以提供更多功能,它不仅包含元数据,还能作用于运行期,注解解析器能够使用注解决定处理流程。举个例子,在Jersey webservice中,我们在一个方法上添加了PATH注解和URI字符串,在运行期,jersey会对其进行解析,并决定作用于指定URI模式的方法。

在Java中创建自定义注解

创建自定义注解与编写接口很相似,除了它的接口关键字前有个@符号。我们可以在注解中定义方法,先来看个例子,之后我们会继续讨论它的特性。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.journaldev.annotations;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Documented
@Target (ElementType.METHOD)
@Inherited
@Retention (RetentionPolicy.RUNTIME)
public @interface MethodInfo{
     String author()  default "Pankaj" ;
     String date();
     int revision()  default 1 ;
     String comments();
}
  • 注解方法不能有参数。
  • 注解方法的返回类型局限于原始类型,字符串,枚举,注解,或以上类型构成的数组。
  • 注解方法可以包含默认值。
  • 注解可以包含与其绑定的元注解,元注解为注解提供信息,有四种元注解类型:

1. @Documented – 表示使用该注解的元素应被javadoc或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分。

2. @Target – 表示支持注解的程序元素的种类,一些可能的值有TYPE, METHOD, CONSTRUCTOR, FIELD等等。如果Target元注解不存在,那么该注解就可以使用在任何程序元素之上。

3. @Inherited – 表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。

4. @Retention – 表示注解类型保留时间的长短,它接收RetentionPolicy参数,可能的值有SOURCE, CLASS, 以及RUNTIME。

Java内置注解

Java提供3种内置注解。

1. @Override – 当我们想要覆盖父类的一个方法时,需要使用该注解告知编译器我们正在覆盖一个方法。这样的话,当父类的方法被删除或修改了,编译器会提示错误信息。大家可以学习一下为什么我们总是应该在覆盖方法时使用Java覆盖注解

2. @Deprecated – 当我们想要让编译器知道一个方法已经被弃用(deprecate)时,应该使用这个注解。Java推荐在javadoc中提供信息,告知用户为什么这个方法被弃用了,以及替代方法是什么。

3. @SuppressWarnings – 这个注解仅仅是告知编译器,忽略它们产生了特殊警告,比如:在java泛型中使用原始类型。它的保持性策略(retention policy)是SOURCE,在编译器中将被丢弃。

我们来看一个例子,展示了如何使用内置注解,以及上述示例中提及的自定义注解。

?
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
package com.journaldev.annotations;
 
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
 
public class AnnotationExample {
 
     public static void main(String[] args) {
     }
 
     @Override
     @MethodInfo (author =  "Pankaj" , comments =  "Main method" , date =  "Nov 17 2012" , revision =  1 )
     public String toString() {
         return "Overriden toString method" ;
     }
 
     @Deprecated
     @MethodInfo (comments =  "deprecated method" , date =  "Nov 17 2012" )
     public static void oldMethod() {
         System.out.println( "old method, don't use it." );
     }
 
     @SuppressWarnings ({  "unchecked" "deprecation" })
     @MethodInfo (author =  "Pankaj" , comments =  "Main method" , date =  "Nov 17 2012" , revision =  10 )
     public static void genericsTest()  throws FileNotFoundException {
         List l =  new ArrayList();
         l.add( "abc" );
         oldMethod();
     }
 
}

我相信这个例子是很明了的,展示了不同场景下注解的使用方式。

Java注解解析

我们将使用Java反射机制从一个类中解析注解,请记住,注解保持性策略应该是RUNTIME,否则它的信息在运行期无效,我们也不能从中获取任何数据。

?
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
package com.journaldev.annotations;
 
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
 
public class AnnotationParsing {
 
     public static void main(String[] args) {
         try {
             for (Method method : AnnotationParsing. class
                     .getClassLoader()
                     .loadClass(( "com.journaldev.annotations.AnnotationExample" ))
                     .getMethods()) {
                 // checks if MethodInfo annotation is present for the method
                 if (method
                         .isAnnotationPresent(com.journaldev.annotations.MethodInfo. class )) {
                     try {
                         // iterates all the annotations available in the method
                         for (Annotation anno : method.getDeclaredAnnotations()) {
                             System.out.println( "Annotation in Method '"
                                     + method +  "' : " + anno);
                         }
                         MethodInfo methodAnno = method
                                 .getAnnotation(MethodInfo. class );
                         if (methodAnno.revision() ==  1 ) {
                             System.out.println( "Method with revision no 1 = "
                                     + method);
                         }
 
                     catch (Throwable ex) {
                         ex.printStackTrace();
                     }
                 }
             }
         catch (SecurityException | ClassNotFoundException e) {
             e.printStackTrace();
         }
     }
 
}

以上程序的输出是:

?
1
2
3
4
5
6
Annotation  in Method  'public java.lang.String com.journaldev.annotations.AnnotationExample.toString()' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=1, comments=Main method,  date =Nov 17 2012)
Method with revision no 1 = public java.lang.String com.journaldev.annotations.AnnotationExample.toString()
Annotation  in Method  'public static void com.journaldev.annotations.AnnotationExample.oldMethod()' : @java.lang.Deprecated()
Annotation  in Method  'public static void com.journaldev.annotations.AnnotationExample.oldMethod()' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=1, comments=deprecated method,  date =Nov 17 2012)
Method with revision no 1 = public static void com.journaldev.annotations.AnnotationExample.oldMethod()
Annotation  in Method  'public static void com.journaldev.annotations.AnnotationExample.genericsTest() throws java.io.FileNotFoundException' : @com.journaldev.annotations.MethodInfo(author=Pankaj, revision=10, comments=Main method,  date =Nov 17 2012)

注解API非常强大,被广泛应用于各种Java框架,如Spring,Hibernate,JUnit。可以查看《Java中的反射》获得更多信息。





























































































































评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值