21、java反射机制与注解

反射,reflection,听其名就像照镜子一样,可以看见自己也可以看见别人。在java语言中这是一个很重要的特性。下面是来自sun公司官网关于反射的介绍:

Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect(内省)" upon itself, and manipulate(操作) internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.
The ability to examine and manipulate a Java class from within itself may not sound like very much, but in other programming languages this feature simply doesn't exist. For example, there is no way in a Pascal, C, or C++ program to obtain information about the functions defined within that program.
One tangible(实际的) use of reflection is in JavaBeans, where software components can be manipulated visually via(通过) a builder tool. The tool uses reflection to obtain the properties of Java components (classes) as they are dynamically loaded.
反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和方法并且显示出来。这种能自我检查和操作内部属性的java类我们不常看到,但是在其他的比如C或者C++语言中很不就存在这个特性。像Pascal、C、C++无法获得内部方法的任何信息;一个实际运用是在JavaBean中,通过一个构造工具操作javaBean的各个组件,这个构造工具就是运用反射功能在javaBean动态加载时来获取他的属性。

 要了解反射机制就必须对java.lang.Class有所了解,见18、19章;

先看看Class的构造器:

    /*
     * Constructor. Only the Java Virtual Machine creates Class
     */
    private Class() {}

 注释很明确的告诉了我们,这个类是有JVM来创建的,所以我们就不用麻烦了。如果我们拿到一个类的类型信息,就可以利用反射获取其各种成员以及方法了。(注:Class 从JDK1.5版本后就开始更多为泛型服务了)那么我们怎么拿到一个类型的信息呢?假设我们有一个Role类:

package org.Smart;
   /*
    * a class Role has some methods and attribute
    */
public class Role {
    public Role(){}
    public Role(String s){}
    private String s;
    private int x;
    public void fun1(String s){
    System.out.println(s);
    }
    public  void  fun2(){
    	System.out.println("ok");
    }
}

 测试:

public class Sample { 
    @SuppressWarnings("rawtypes")
	public static void main(String...s)  {
    	Class c=Role.class;
    	Method[] m=c.getDeclaredMethods();
    	for(int x=0;x<m.length;x++){
    		System.out.println(m.toString());
    	}
    }
 }

 

获得Class 实例的几种方式:

Class cls1 = Role.class;
Class cls2 = Class.forName("org.Smart.Role");
Role r=new Role();
Class cls3=r.getClass();
Class cls4= Boolean.TYPE;  //仅适用基本数据类的包装类

注意第二种方式中,forName中的参数一定是完整的类名(包名+类名),并且这个方法需要捕获异常。现在得到cls1就可以创建一个Role类的实例了,利用Class的newInstance方法相当于调用类的默认的构造器

Class c;
try {
     c = Class.forName("org.Smart.Role");
     Role r=(Role) c.newInstance();        //等同Role r=new Role();利用newInstance()理解为Role的无参构造方法
     r.fun2();
} catch (ClassNotFoundException e) {
     e.printStackTrace();
}

获得构造器:getConstructors/getDeclaredConstructors  (不含私有/含私有)并可以设置为私有或公有的

获得属性:getFields/getDeclaredFields

获得方法:getMethods/getDeclaredMethods

 

调用类的方法:

Class c= Role.class;
    	Object obj=c.newInstance();
    	Method fun=c.getDeclaredMethod("fun1", String.class); //若方法fun1没有参数写null,有参数写入参数类型
    	fun.invoke(obj, "e");//没有参数写null,有参数传参数值

设置属性值:

Field f=c.getDeclaredField("x");
f.setInt(obj, 32); //int x;必须是public声明的,setInt与int型对应;

讲到这里使用了java.lang.reflect包中的Method/Field/Constructor类,这些类都是接收Class.get()获得的方法/属性/构造器,还有一个最重要的运用:jvm核心之一Proxy代理,将在下章内容中讲解;

Class的其他重要方法:

static Class<?>	              forName(String name, boolean initialize, ClassLoader loader) //Class内部方法
static Class<?>	              forName(String className)//外部调用方法
<A extends Annotation>A       getAnnotation(Class<A> annotationClass)
Annotation[]	              getAnnotations()
ClassLoader	              getClassLoader()//获取类加载器
Class<?>[]	              getInterfaces() //获取接口
String	                      getName()//获取类名
Package   	              getPackage()  //获取包类型
Class<? super T>	      getSuperclass()//获取父类

 

下面接着讲注解:

JDK 5中引入了源代码中的注解(annotation)这一机制。注解使得Java源代码中不但可以包含功能性的实现代码,还可以添加元数据。注解的功能类似于代码中的注释,所不同的是注解不是提供代码功能的说明,而是实现程序功能的重要组成部分。Java注解已经在很多框架中得到了广泛的使用,用来简化程序中的配置。

 

java注解又叫java标注,java提供了一套机制,使得我们可以对方法、类、参数、包、域以及变量等添加标准(即附上某些信息)。且在以后某个时段通过反射将标注的信息提取出来以供使用。

Java从1.5版本以后默认内置三个标注: 

@Override:只能用在方法之上的,用来告诉别人这一个方法是改写父类的。 

@Deprecated:建议别人不要使用旧的API的时候用的,编译的时候会用产生警告信息,可以设定在程序里的所有的元素上. 

@SuppressWarnings:这一个类型可以来暂时把一些警告信息消息关闭. 

但是,仅仅这三个标注是不能满足我们开发时一些需求的。所以java允许我们自定义注解来使用。

从某种角度来说,可以把注解看成是一个XML元素,该元素可以有不同的预定义的属性。而属性的值是可以在声明该元素的时候自行指定的。在代码中使用注解,就相当于把一部分元数据从XML文件移到了代码本身之中,在一个地方管理和维护。

 

如何自己设计一个注解?

先看一下rt.jar下面的java.lang.annotation包:

Target.class

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {        //@Target自己用自己来声明自己,只能用在ANNOTATION_TYPE之上
    ElementType[] value();
}
  //Java中提供了四种元注解,专门负责注解其他的注解,分别如下
  //@Documented将注解包含在JavaDoc中32  public @interface Documented {
  //@Inheried允许子类继承父类中的注解     
  //@Retention元注解,表示保存在什地方。可选的RetentionPoicy参数包括:public @interface Retention{   
  //RetentionPolicy.SOURCE: 停留在java源文件,编译器被丢掉  public enum RetentionPolicy {
  //RetentionPolicy.CLASS:停留在class文件中,但会被VM丢弃(默认)
  //RetentionPolicy.RUNTIME:内存中的字节码,VM将在运行时也保留注解,因此可以通过反射机制读取注解的信息

  //@Target元注解,表示使用在什么地方。可用的ElementType参数包括    
  //ElementType.CONSTRUCTOR: 构造器声明   public enum ElementType {
  //ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
  //ElementType.LOCAL_VARIABLE: 局部变量声明
  //ElementType.METHOD: 方法声明
  //ElementType.PACKAGE: 包声明
  //ElementType.PARAMETER: 参数声明
  //ElementType.TYPE: 类、接口(包括注解类型)或enum声明       
   //ElementType.ANNOTATION_TYPE  Annotation type declaration

   @interface是一个关键字,在设计注解的时候必须把一个类型定义为@interface,而不能用class或interface关键字,@Retention用来声明注解的保留策略,有CLASS、RUNTIME和SOURCE这三种属性,分别表示注解保存在字节码.class文件、JVM运行时刻和源代码.java文件中。只有当声明为RUNTIME时,才能通过反射机制获取到注解信息。@Target用来声明注解可以添加哪些元素类型;

    SOURCE代表的是这个Annotation类型的信息只会保留在程序源码里,源码如果经过了编译之后,Annotation的数据就会消失,并不会保留在编译好的.class文件里面。 
    ClASS的意思是这个Annotation类型的信息保留在程序源码里,同时也会保留在编译好的.class文件里面,在执行的时候,并不会把这一些信息加载到虚拟机(JVM)中去.注意一下,当你没有设定一个Annotation类型的Retention值时,系统默认值是CLASS. 
     RUNTIME,表示在源码、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的. 

   @Documented的目的就是让这一个Annotation类型的信息能够显示在javaAPI说明文档上;没有添加的话,使用javadoc生成API文档的时候就会找不到这一个类型生成的信息. 

   如果需要把Annotation的数据继承给子类,那么就会用到@Inherited这一个Annotation类型.

 我们在来看下@Override注解:

@Target(ElementType.METHOD)     
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
从上面可以得出:该注解只能在方法前使用,保存在源代码中,不会把@T...@R保存在.class文件中,但是通过反编译还是会打印出这两个注解?

使用@Override的时候只需要一个声明即可,而没有传递参数,称为标记注解(marker annotation ),代表某种配置语义。

使用@SuppressWarnings("xxxtype"),里面传递了一个参数;其实里面是一个名值对语句:value="xxxtype",省略了value=这个字符串;

 

所有的Annotation会自动继承java.lang.annotation这一个接口,所以不能再去继承别的类或是接口. ;只能用public或默认(default)这两个访问权限声明这个注解类;方法的返回值只能是基本类型(与注解传来的参数相接)byte,short,char,int,long,float,double,boolean和String,Enum,Class,annotations等数据类型,以及这一些类型的数组;如果只有一个成员变量,最好把变量名称设为"value",后加括号 String value(),这样注解时可以省略value这个字段,形式上是一个无参方法;多个成员变量后面都要加(),像int x();

自己设计一个Description注解类:

@Target(ElementType.TYPE)  //使用在类前,注意后面没有;分号
@Retention(RetentionPolicy.RUNTIME)  //保证在jvm中
public @interface Description {
	String info();
	String str();
}

使用Description注解:

@Description(info="skx",str="good name!!")
public class UseDescription {
}

查看注解: 

Class c=UseDescription.class;
//获取UseDescription里面的@Description注解
Description obj=(Description) c.getAnnotation(Description.class);
//获取Description.info方法的返回值
System.out.println(obj.info());  //skx
 注解处理过程: 本来深入想研究下import com.sun.mirror.apt.AnnotationProcessorFactory;被告知@Deprecated,若有朋友知道注解过程详解的,望不舍赐教;

猜你喜欢

转载自nickfover.iteye.com/blog/2121974