Java注解开发及ICOP平台中的应用
一.什么是Java注解
从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。
Annotation提供了一种为程序元素(包、类、构造器、方法、成员变量、参数、局域变量)设置元数据的方法。Annotation不能运行,它只有成员变量,没有方法。Annotation跟public、final等修饰符的地位一样,都是程序元素的一部分,Annotation不能作为一个程序元素使用。
二.注解的作用
1. 为编译器提供辅助信息 — Annotations可以为编译器提供而外信息,以便于检测错误,抑制警告等.(@Override)
2. 编译源代码时进行而外操作 — 软件工具可以通过处理Annotation信息来生成原代码,xml文件等等.(JPA工具利用entity类的@Table和@Column)
3. 运行时处理 — 有一些annotation甚至可以在程序运行时被检测,使用.(@EnableCache,@ Component,@Controller)
4. 总之,注解是一种元数据,起到了”描述,配置“的作用。
三. 注解的开发规范
3.1定义注解(Annotation)
定义新的Annotation类型使用@interface关键字(在原有interface关键字前增加@符号)。定义一个新的Annotation类型与定义一个接口很像,例如:
public @interface MyTag{
}
定义完该Annotation后,就可以在程序中使用该Annotation。使用Annotation,非常类似于public、final这样的修饰符,通常,会把Annotation另放一行,并且放在所有修饰符之前。例如:
@MyTag
public class MyClass{
....
}
3.2 定义成员变量
Annotation只有成员变量,没有方法。Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型,还可以通过default关键字定义注解成员变量的默认值,如果Annotation的成员变量已经指定了默认值,使用该Annotation时可以不为这些成员变量指定值,而是直接使用默认值。例如:
public @interface MyTag{
string name() default "张三";
int age() default 18;
}
3.2 元注解
在定义Annotation时,也可以使用JDK提供的元注解来修饰Annotation定义。JDK提供了以下4个元注解。
3.2.1 @Retention
@Retention用于指定Annotation可以保留多长时间。@Retention包含一个名为“value”的成员变量,该value成员变量是RetentionPolicy枚举类型。使用@Retention时,必须为其value指定值。value成员变量的值只能是如下3个:
1) RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器编译时,直接丢弃这种Annotation。(@Override注解,用于注释该方法为重写父类的方法)
2) RetentionPolicy.CLASS:编译器把Annotation记录在class文件中。当运行Java程序时,JVM中不再保留该Annotation。(用于注解处理器,根据注解生产java源文件及xml文件等。)
3) RetentionPolicy.RUNTIME:编译器把Annotation记录在class文件中。当运行Java程序时,JVM会保留该Annotation,程序可以通过反射获取该Annotation的信息。
3.2.2@Target
@Target指定Annotation用于修饰哪些程序元素。@Target也包含一个名为”value“的成员变量,该value成员变量类型为ElementType[ ],ElementType为枚举类型,值有如下几个:
1) ElementType[ ],ElementType为枚举类型,值有如下几个:
2) ElementType.TYPE:能修饰类、接口或枚举类型
3) ElementType.FIELD:能修饰成员变量
4) ElementType.METHOD:能修饰方法
5) ElementType.PARAMETER:能修饰参数
6) ElementType.CONSTRUCTOR:能修饰构造器
7) ElementType.LOCAL_VARIABLE:能修饰局部变量
8) ElementType.ANNOTATION_TYPE:能修饰注解
9) ElementType.PACKAGE:能修饰包
示例:
package com.cn;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ ElementType.FIELD, ElementType.METHOD })
public @interface FieldTag {
int type() default 1;
}
3.2.3 @Documented
如果定义注解A时,使用了@Documented修饰定义,则在用javadoc命令生成API文档后,所有使用注解A修饰的程序元素,将会包含注解A的说明。
@Documented
public @interface MyTag {
}
public class Test {
@ MyTag
public void info() {
}
}
3.2.4 @Inherited
@Inherited指定Annotation具有继承性。
package com.cn.annotation;
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;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyTag{
}
package com.cn.annotation;
@MyTag
public class Base {
}
package com.cn;
//SubClass只是继承了Base类
//并未直接使用@MyTag注解修饰
public class SubClass extends Base {
public static void main(String[] args) {
System.out.println(SubClass.class.isAnnotationPresent(MyTag.class));
}
}
//执行后控制台输出true
3.3 JDK中的基本注解
3.3.1 @Override
限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。
3.3.2 @Deprecated
用于表示某个程序元素(类、方法等)已过时。如果使用被@Deprecated修饰的类或方法等,编译器会发出警告。
3.3.3 @SuppressWarning
抑制编译器警告。指示被@SuppressWarning修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法.....)取消显示指定的编译器警告。例如,常见的@SuppressWarning(value="unchecked")
3.4 提取Annotation信息
当开发者使用了Annotation修饰了类、方法、Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的代码来提取并处理Annotation信息。这些处理提取和处理Annotation的代码统称为APT(AnnotationProcessing Tool)。
JDK主要提供了两个类,来完成Annotation的提取:
3.4.1 java.lang.annotation.Annotation
java.lang.annotation.Annotation接口:这个接口是所有Annotation类型的父接口(后面会分析Annotation的本质,Annotation本质是接口,而java.lang.annotation.Annotation接口是这些接口的父接口)。
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
java.lang.annotation.Annotation接口的主要方法是annotationType(),用于返回该注解的java.lang.Class。
3.4.2 java.lang.reflect.AnnotatedElement
ackage java.lang.reflect;
import java.lang.annotation.Annotation;
public interface AnnotatedElement {
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();
}
主要方法有:
· isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
· getAnnotation(Class<T> annotationClass):返回该程序元素上存在的指定类型的注解,如果该类型的注解不存在,则返回null
· Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
java.lang.reflect.AnnotatedElement接口是所有程序元素(例如java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Constructor等)的父接口。
· Annotation[] getDeclaredAnnotations ():返回该程序元素上存在的所有注解,该方法将忽略继承的注释
java.lang.reflect.AnnotatedElement接口是所有程序元素(例如java.lang.Class、java.lang.reflect.Method、java.lang.reflect.Constructor等)的父接口。
3.4.3 注解处理器
JDK6中提供的注解处理工具框架的主要类包javax.annotation.processing。处理器的核心接口为:javax.annotation.processing.Processor,还提供了一个此接口的实现类:javax.annotation.processing.AbstractProcessor。处理接口提供了一个核心处理方法process(),用于开发者实现自己的处理逻辑(用于处理注解)。
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
process()方法有一个boolean类型的返回值,若返回false,表示本轮注解未声明并且可能要求后续其它的Processor处理它们;若返回true,则代表这些注解已经声明并且不要求后续Processor来处理它们。
3.4.4 提取Annotation示例
/**
* 自定义注解--数据表
* @authorshl
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.TYPE })
public @interfaceTable{
String name();
}
/**
* 自定义注解--数据列
* @authorshl
*
*/
@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interfaceColumn {
Stringname() default"";
String type() default"varchar";
booleannullable()defaulttrue;
int length()default 10;
int precision()default 0;
int scale()default 0;
}
/**
* 根据注解生成table的描述信息
* @param entityClass entity Class
* @return
*/
private staticTableVO generateTableVO(Class<?> entityClass) {
String tableName = null;
Map<String, ColumnVO> columnMap =new HashMap<String,ColumnVO>();
TableVO tableVO = new TableVO();
//判断该类是否声明了table注解
if (entityClass.isAnnotationPresent(Table.class)) {
//获取table注解
Table tableAnnotation = (Table)entityClass.getAnnotation(Table.class);
tableName = tableAnnotation.name();
tableVO.setTablename(tableName);
Field[] fields = entityClass.getDeclaredFields();
for (Fieldfield: fields){
Column columnAnnotation= field.getAnnotation(Column.class);
ColumnVO columnVO =new ColumnVO();
columnVO.setName(columnAnnotation.name());
columnVO.setLength(columnAnnotation.length());
columnVO.setNullable(columnAnnotation.nullable());
columnVO.setPrecision(columnAnnotation.precision());
columnVO.setScale(columnAnnotation.scale());
columnVO.setType(columnAnnotation.type());
columnMap.put(field.getName(),columnVO);
}
tableVO.setColumnMap(columnMap);
}
returntableVO;
}
3.5 注解的本质
输出注解类为字节码文件:
javap -verbose -c Column.class > d.txt
Classfile/D:/eclipse_ownspace/ibop-springframework/target/classes/com/cn/annotation/Column.class
Last modified 2017-1-9; size 635 bytes
MD5checksum 9b1539aa17dc8983dc5d2de6ffa95e99
Compiled from "Column.java"
public interface com.cn.annotation.Columnextends java.lang.annotation.Annotation
SourceFile: "Column.java"
RuntimeVisibleAnnotations:
0: #25(#26=[e#27.#28,e#27.#29])
1: #30(#26=e#31.#32)
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1= Class #2 // com/cn/annotation/Column
#2= Utf8 com/cn/annotation/Column
#3= Class #4 // java/lang/Object
#4= Utf8 java/lang/Object
#5= Class #6 // java/lang/annotation/Annotation
#6= Utf8 java/lang/annotation/Annotation
#7= Utf8 name
#8= Utf8 ()Ljava/lang/String;
#9= Utf8 AnnotationDefault
#10= Utf8
#11= Utf8 type
#12= Utf8 varchar
#13= Utf8 nullable
#14= Utf8 ()Z
#15= Integer 1
#16= Utf8 length
#17= Utf8 ()I
#18= Integer 10
#19= Utf8 precision
#20= Integer 0
#21= Utf8 scale
#22= Utf8 SourceFile
#23= Utf8 Column.java
#24= Utf8 RuntimeVisibleAnnotations
#25= Utf8 Ljava/lang/annotation/Target;
#26= Utf8 value
#27= Utf8 Ljava/lang/annotation/ElementType;
#28= Utf8 METHOD
#29= Utf8 FIELD
#30= Utf8 Ljava/lang/annotation/Retention;
#31= Utf8 Ljava/lang/annotation/RetentionPolicy;
#32= Utf8 RUNTIME
{
publicabstract java.lang.String name();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#10
public abstract java.lang.String type();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#12
public abstract boolean nullable();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: Z#15
public abstract int length();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#18
public abstract int precision();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#20
public abstract int scale();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#20}
通过分析字节码可知:
· 注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。
· 注解的成员变量会被编译器编译为同名的抽象方法。
· 根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的AnnotationDefault属性位置,记录注解的成员变量默认值是多少。
四.注解在ICOP平台中的应用
4.1 高性能的基于注解的参照缓存服务实现
4.1.1 创建参照注解
package com.yyjz.icop.refer.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* 参照实体
*
*@author hupeng 2016年9月22日
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Refer {
Stringvalue() default "";
/**
* 参照注册节点对应的参照编码
*
* @return
*/
publicString referCode() default "";
/**
* 档案ID
*
* @return
*/
publicString id() default "id";
/**
* 档案CODE
*
* @return
*/
publicString code() default "code";
/**
* 档案名称
*
* @return
*/
publicString name() default "name";
publicString parentId() default "";
}
4.1.2 在实体类中声明@Refer注解
@Entity
@Table(name="bd_company")
@Refer(id="id",code="companyCode",name="companyName")
public classCompanyEntity extendsAbsIdEntity{
private static final long serialVersionUID= 1L;
@Column(name="company_code")
protected StringcompanyCode;
@Column(name="company_name")
protected StringcompanyName;
@Column(name="company_sh_name")
protected StringcompanyShName;
。。。。。。。省略部分代码。
}
4.1.3 在容器启动时,将声明@Rerfer注解的实体放入缓存
package com.yyjz.icop.refer.listenner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
importorg.springframework.context.event.ApplicationContextEvent;
importorg.springframework.stereotype.Component;
importorg.springframework.util.StringUtils;
importcom.yyjz.icop.database.entity.SuperEntity;
import com.yyjz.icop.database.repository.EntityNativeQuery;
importcom.yyjz.icop.exception.BusinessException;
importcom.yyjz.icop.refer.annotation.Refer;
importcom.yyjz.icop.refer.context.ContextUtils;
importcom.yyjz.icop.refer.utils.ReferCacheTool;
@Component
public class ReferListenner implementsApplicationListener<ApplicationContextEvent> {
@Autowired
privateEntityNativeQuery<? extends SuperEntity> query;
/**
* Y的时候初始化
*/
@Value("${refer.init:N}")
privateString referInit;
@Override
publicvoid onApplicationEvent(ApplicationContextEvent event) {
if(event.getApplicationContext().getParent() != null &&referInit.equals("Y")) {
Map<String,Object> beansWithAnnotationMap = ContextUtils.getApplicationContext()
.getBeansWithAnnotation(Refer.class);
for(String key : beansWithAnnotationMap.keySet()) {
System.out.println("beanName=" + key);
Objectbean = beansWithAnnotationMap.get(key);
Referrefer = bean.getClass().getAnnotation(Refer.class);
if(refer != null) {
List<String> filednames= new ArrayList<>();
filednames.add(refer.id());
filednames.add(refer.code());
filednames.add(refer.name());
if(!StringUtils.isEmpty(refer.parentId())){
filednames.add(refer.parentId());
}
List<SuperEntity>lst = null;
try{
lst= (List<SuperEntity>) query.query((Class<? extends SuperEntity>)bean.getClass(),
filednames,null,null);
}catch (BusinessException e) {
thrownew RuntimeException(e);
}
ReferCacheTool.putBatchReferCache(lst,bean.getClass());
}
}
}
}
}