一、泛型的反射
泛型的基础知识见:http://www.flyne.org/article/248
首先必须清楚以下概念:
- ArrayList<E>称为泛型类型,其中的E称为类型变量
- ArrayList<Integer>称为参数化的类型(parameterized type),其中的Integer称为实际类型参数。
- ArrayList称为原始类型(raw type)
泛型的反射就是为了取得参数化类型(parameterized type)中的实际类型参数(actual type argument)。
1、核心API
Type是所有类型的公共父接口,Since 1.5。所有类型指的有:
- 原始类型:raw type,对应Class
- 基本类型:primitive type,对应Class
- 参数化类型:parameterized type,对应ParameterizedType,如List<String>、Map<key,value>
- 泛型数组:对应GenericArrayType,如Class<String>[],【还没用过……】
- TypeVariable、WildcardType:略。
扩展:为什么要设计Type接口?(Type接口的几个子接口的作用)
① 没有泛型的时候,只有原始类型,此时所有的类型都可以通过字节码文件类Class进行抽象。
② JDK1.5中引入泛型后,扩充了数据类型,增加了参数化类型、泛型数组类型……
③ 由于存在类型擦除,编译后,与泛型有关的类型(参数化类型、泛型数组……)将全部被打回原形,因此并不存在和自身类型一致的字节码文件。为了通过反射操作这些类型以满足实际开发的需要,Java新增了ParameterizedType……几种类型来代表与泛型有关的类型。
④ 当然,为了更好的发挥面向对象的特性,最终引入了Type接口“统一”了与泛型有关的类型和原始类型。但Type接口并不提供任何方法。
补充阅读材料:http://blog.csdn.net/kobejayandy/article/details/11709043
2、泛型反射的应用:
泛型的反射紧紧抓住一条(两个步骤):① 通过Class搞到同泛型有关的类型【一般都是Type类型,需强转成四种类型】,② 再通过四个子接口中的API进行泛型反射。因此泛型反射时一定要注意Class类中返回值为Type、Type[]的getXxx()方法。
1)DAO层设计中
详见《DAO层设计(泛型反射)》一文。
public class BaseDaoImpl<T> implements BaseDao<T> { private Class clazz; public BaseDaoImpl() { // 利用泛型的反射在实例化时搞到实体类的字节码 Type t = this.getClass().getGenericSuperclass(); ParameterizedType pt = (ParameterizedType) t; Type actualType = pt.getActualTypeArguments()[0]; clazz = (Class) actualType; } …… }
在DAO层的设计中,当BaseDaoImpl的子类被实例化时,BaseDaoImpl构造函数中拿到的this指向子类,获取父类的参数化类型,即BaseDaoImpl<Xxx>,再通过泛型反射拿到实体类的字节码。
2)待补充……
二、注解的反射
注解的基础知识见:http://www.flyne.org/article/243
注解的反射包括反射注解本身和反射注解的属性。要完成这一过程,需借助java.lang.reflect.AnnotatedElement接口中定义的方法。
1、AnnotatedElement接口
从字面意义上来看,该接口代表了被注解的程序元素,因为程序元素可以是类、方法、字段及包,所以可以推断,Class、Method、Field等类有实现该接口。(查阅API,也是这样)
AnnotatedElement接口中定义了如下方法:
- getAnnotation(Class<T> annotationClass):如果程序元素上存在指定类型的注解则返回该注解,否则返回null。
- getAnnotations():返回程序元素上存在的所有注解,包括通过继承得到的注解。见@Inherited
- getDeclaredAnnotations():返回程序元素上直接存在的注解。
- isAnnotationPresent(Class<T> annotationClass):是否存在指定类型的注解
2、元注解
注解的注解,即加在注解上的注解,为注解服务。
1) @Retention:加在自己定义的注解上指示注解能保留多久,默认为RetentionPolicy.CLASS
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention{ RetentionPolicy value(); }
注解中属性的类型必须是:基本类型、String、Class、注解、枚举及以上类型的一维数组。因此断定RetentionPolicy为枚举类型。有SOURCE、CLASS、RUNTIME等枚举值。如果需要自定义的注解能被反射到,需设置RetentionPolicy.RUNTIME。
2)@Target:限定注解适用的程序元素
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
不难推断ElementType也为枚举类型,取值有:ANNOTATION_TYPE、FILED、METHOD、TYPE。
3)@Documented:是否在生成的Java doc上显示注解
4)@Inherited:使用了注解的类,其子类是否可以继承父类使用的注解
3、模拟JUnit中的@Test
注解的灵魂不在于定义,而是对出现注解的地方进行反射处理。本节例子中模拟了Junit中的@Test注解(没有图形化,仅用控制台打印结果),完成的功能有:
- 执行有@MyTest注解的方法
- 如果有@MyTest(time=100)注解,则执行时间超过100毫秒,打印出“超时了”
先看@MyTest注解的定义和注解的使用:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyTest { long time() default -1; } public class MyTestDemo { @MyTest public void test1(){ System.out.println("被测试方法1"); } @MyTest(time=100) public void testUpdate(){ try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("被测试方法2"); } }
1)反射注解:执行有@MyTest注解的方法
public static void main(String[] args) throws Exception { Class clazz = MyTestDemo.class; Method[] ms = clazz.getMethods(); for(Method m : ms){ if(m.isAnnotationPresent(MyTest.class)){ m.invoke(clazz.newInstance(), null); } } }
2)反射注解的属性:根据time属性判断是否超时
public static void main(String[] args) throws Exception{ Class clazz = MyTestDemo.class; Method[] ms = clazz.getMethods(); for (Method m : ms) { if (m.isAnnotationPresent(MyTest.class)) { MyTest mt = m.getAnnotation(MyTest.class); if(mt.time() != -1){ long start = System.currentTimeMillis(); m.invoke(clazz.newInstance(), null); long end = System.currentTimeMillis(); if((end-start)>mt.time()){ System.out.println("超时了"); } }else{ m.invoke(clazz.newInstance(), null); } } } }
- 本文固定链接: http://www.flyne.org/article/611
- 转载请注明: 东风化宇 2014年09月02日 于 Flyne 发表