What:Annotation干嘛的
JDK5开始,java增加了对元数据(MetaData)的支持,怎么支持?答:通过Annotation(注解)来实现。Annotation提供了为程序元素设置元数据的方法。元数据:描述数据的数据。
Annotation可以为哪些程序元素设置元数据呢? Annotation提供了一种为程序元素设置元数据的方法,包括修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。元数据的信息被存储在Annotation的“name=value”对中。
Annotation怎么实现设置元数据?程序如何读取这些元数据?答:元数据的信息被存储在Annotation的“name=value”对中。Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据。
Annotation不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一的执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套工具对Annotation中的信息进行访问和处理。jdk7之前访问和处理Annotation的工具统称APT(Annotation Processing Tool)(jdk7后就被废除了),jdk7及之后采用了JSR 269 API。相关原因官方说明 、 原因
注解的使用案例
@Entity
public class Book {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
}
Why:为什么要提供元数据支持
通过Annotation设置的元数据在什么时候被读取?读取能干嘛?答:Annotation就像代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取。读取到了程序元素的元数据,就可以执行相应的处理。通过注解,程序开发人员可以在不改变原有逻辑的情况下,在源代码文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过解析这些注解获取到这些补充信息,从而进行验证或者进行部署等。
比如:上面代码,读取到 id变量上面有@GeneratedValue(strategy=GenerationType.AUTO)注解,并且注解提供了strategy=GenerationType.AUTO这样的元数据信息,那么程序就会为id设置一个自增的值。读取到Book类上面有一个@Entity注解,程序就会认为这是一个持久化类,就会做一些持久化的处理。
不使用Annotation怎么为程序元素提供元数据
看来元数据在编程中还是能起到很大的作用的,如果没有元数据还真的不好办,比如上面代码中id成员变量的元数据是“strategy=GenerationType.AUTO即采用自增策略”,如果没有这个元数据支持,程序中怎么才能为id赋一个自增的值呢?忧愁。
提供元数据只有通过Annotation才可以吗?答:不是,通过配置文件也可以。比如还是上面代码id这个变量,我现在想为它添加描述数据即元数据,内容是:采用自增策略。这个信息通过Annotation来实现就是上面代码的样子。通过配置文件实现的话,比如采用xml格式配置文件。那么我可以在文件中配置
知道元数据在编程中的重要性和提供元数据的方法Annotation了,那么就来学习Annotation吧。
Java提供了5个基本的Annotation的用法,在使用Annotation时要在其前面增加@符号。
@Override :限定重写父类方法
@Deprecated:表示已过时
@SuppressWarnings:抑制编译警告
@SafeVarargs (java7新增):去除“堆污染”警告
@Functionlnterface (java8新增):修饰函数式接口
@Override :用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。写在子类的方法上,在编译期,编译器检查这个方法,保证父类包含被该方法重写的方法,否则编译出错。该注解只能修饰方法,在编译期被读取。
@Deprecated:用于表示某个程序元素(类、方法等)已过时。编译时读取,编译器编译到过时元素会给出警告。
@SuppressWarnings:抑制编译警告,被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译警告。
比如:取消如果程序使用没有泛型限制的集合会引起编译器警告,为了避免这种警告使用该注解。
@SuppressWarnings(value="unchecked")
public class SuppressWarningTest{
public static void main(String[] args)
{
List myList = new ArrayList();
}
}
@SuppressWarnings("deprecation") //取消过时警告
public HibernateTemplate getHt() {
return ht;
}
List l2 = new ArrayList();
List ls = l2;
3中方式去掉这个警告
@Functionlnterface (java8新增):修饰函数式接口
使用该注解修饰的接口必须是函数式接口,不然编译会出错。那么什么是函数式接口?答:如果接口中只有一个抽象方法(可以包含多个默认方法或static方法),就是函数式接口。
如:
@Functionlnterface
public interface FunInterface{
static void foo(){
System.out.println("foo类方法");
}
default void bar(){
System.out.println("bar默认方法");
}
void test();//只定义一个抽象方法,默认public
}
//定义下面的Testable Annotation保留到运行时,也可以使用value=RetentionPolicy.RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}
用于指定被修饰的Annotation能用于修饰哪些程序单元,只能修饰Annotation定义。它包含一个名为value的成员变量,取值如下:
@Target(ElementType.FIELD)
public @interface ActionListenerFor{}
//定义一个简单的注解Test
public @interface Test{}
默认情况下,Annotation可以修饰任何程序元素:类、接口、方法等。
@Test
public class MyClass{
}
//定义带成员变量注解MyTag
@Rentention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTag{
//定义两个成员变量,以方法的形式定义
String name();
int age() default 32;
}
//使用
public class Test{
@MyTag(name="liang")
public void info(){}
}
使用Annotation修饰了类、方法、成员变量等程序元素之后,这些Annotation不会自己生效,必须由开发者通过API来提取并处理Annotation信息。
Annotation接口是所有注解的父接口。
思路:通过反射获取Annotation,将Annotation转换成具体的注解类,在调用注解类定义的方法获取元数据信息。
获取Annotation
AnnotatedElement接口(java.lang.reflect反射包中)代表程序中可以接受注解的程序元素。即所有可以接受注解的程序元素都会实现该接口。而该接口就提供了获取Annotation的方法,它的所有实现类也便拥有了这些方法。常见的实现类:
Class:类定义。
Constructor:构造器定义
Field:类的成员变量定义
Method:类的方法定义。
Package:类的包定义。
由此可见,AnnotatedElement接口的实现类都是一些反射技术设计到的类,所以访问Annotation信息也是通过反射技术来实现的。
java.lang.reflect包下还包含实现反射功能的工具类,java5开始,java.lang.reflect包提供的反射API增加了读取允许Annotation的能力。但是,只有定义Annotation时使用了@Rentention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载.class文件时读取保存在class文件中的Annotation*。
AnnotatedElement接口获取Annotation信息的方法:
Annotation[] getAnnotations():返回此元素上存在的所有注解。
Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。
boolean isAnnotationPresent (Class< ? extends Annotation> annotationClass):如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
java8新增了重复注解功能,所以下面两个方法在java8之后才有:
案例
需求:获取Test类的info方法上的所有注解,并打印出来,如果包含MyTag注解,则再输出MyTag注解的元数据。
实现:正如我们所知,仅在程序中使用注解是不起任何作用的,必须使用注解处理工具来处理程序中的注解。下面就写一个注解处理类。处理注解的思路如下:通过反射获取Test的类描述类Class,然后在获取其info方法描述类Method,因为Method实现了AnnotatedElement接口,所以调用getAnnotations方法获取所有注解,在遍历打印。
MyTag注解处理器
public class MyTagAnnotationProcessor {
public static void process(String className) throws ClassNotFoundException{
try {
Class clazz =Class.forName(className);
Annotation[] aArray= clazz.getMethod("info").getAnnotations();
for( Annotation an :aArray){
System.out.println(an);//打印注解
if( an instanceof MyTag){
MyTag tag = (MyTag) an;
System.out.println("tag.name():"+tag.name());
System.out.println("tag.age():"+tag.age());
}
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
场景测试
public static void main(String[] args) {
try {
MyTagAnnotationProcessor.process("annotation.Test");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
测试结果
@annotation.MyTag(age=25, name=liang)
tag.name():liang
tag.age():25
//代码错误,不可以使用相同注解在一个程序元素上。
@MyTag(name="liang")
@MyTag(name="huan")
public void info(){
}
操作步骤2步:1编写需要重复的注解@MyTag,上面定义过了。2.编写”容器“注解DupMyTag 。
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface DupMyTag {
//成员变量为MyTag数组类型
MyTag[] value();
}
//代码正确,换个思路实现,在同一个程序元素上使用了多个相同的注解MyTag
@DupMyTag ({ @MyTag(name="liang"),@MyTag(name="huan",age=18)})
public void info(){
}
打印注解输出内容如下:
@annotation.DupMyTag(value=[@annotation.MyTag(age=25, name=liang), @annotation.MyTag(age=18, name=huan)])
**结论:通过新定义一个容器注解,来实现使用多个相同注解的目的,只是书写形式不能达到期待效果而已,要想书写形式能达到期待效果需要使用java8之后的@Repeatable元注解。
**
注:”容器“注解的保留期Retention必须比它所包含注解的保留期更长,否则编译报错
java8之后新增了@Repeatable元注解,用来开发重复注解,其有一个必填Class类型变量value。
同样,还是需要新定义一个注解@DupMyTag。和上面定义的一样。不一样的是@Repeatable元注解需要加在@MyTag上,value值设置为DupMyTag.class,开发便完成。
操作步骤2步:1编写需要重复的注解@MyTag,如下。2.编写”容器“注解DupMyTag ,上面定义过了
//定义带成员变量注解MyTag
@Repeatable(DupMyTag.class)
@Rentention(RetentionPolicy.RUNTIME)
@Method(ElementType.METHOD)
public @interface MyTag{
//定义两个成员变量,以方法的形式定义
String name();
int age() default 32;
}
@MyTag(name="liang")
@MyTag(name="huan",age =18)
public void info(){
}
//两种形式都可以
@DupMyTag ({ @MyTag(name="liang"),@MyTag(name="huan",age=18)})
public void info(){
}
原理:系统依然还是将两个MyTag注解作为DupMyTag的value成员变量的数组元素,只是书写形式多了一种而已
获取注解方法
上面代码通过getDeclaredAnnotationsByType(MyTag.class)和getDeclaredAnnotation(DupMyTag.class)两个方法都能获取到值,只是结果不一样如下:
@annotation.MyTag(age=25, name=liang)
@annotation.MyTag(age=18, name=huan)
@annotation.DupMyTag(value=[@annotation.MyTag(age=25, name=liang), @annotation.MyTag(age=18, name=huan)])
目的:以前的注解只能用在包、类、构造器、方法、成员变量、参数、局部变量。如果想在:创建对象(通过new创建)、类型转换、使用implements实现接口、使用throws声明抛出异常的位置使用注解就不行了。而Type Annotation注解就为了这个而来。
抽象表述: java为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值。@Target(TYPE_USE)修饰的注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方。*
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
String value() default "";
}
//implements实现接口中使用Type Annotation
public class Test implements @NotNull(value="Serializable") Serializable{
//泛型中使用Type Annotation 、 抛出异常中使用Type Annotation
public void foo(List<@NotNull String> list) throws @NotNull(value="ClassNotFoundException") ClassNotFoundException {
//创建对象中使用Type Annotation
Object obj =new @NotNull String("annotation.Test");
//强制类型转换中使用Type Annotation
String str = (@NotNull String) obj;
}
}
编写处理注解的处理器。
java8提供AnnotatedType接口,该接口用来代表被注解修饰的类型。该接口继承AnnotatedElement接口。同时多了一个public Type getType()方法,用于返回注解修饰的类型。
以下处理器只处理了类实现接口处的注解和throws声明抛出异常处的注解。
/*
类说明 NotNull注解处理器,只处理了implements实现接口出注解、throws声明抛出异常出的注解。
*/
public class NotNullAnnotationProcessor {
public static void process(String className) throws ClassNotFoundException{
try {
Class clazz =Class.forName(className);
//获取类继承的、带注解的接口
AnnotatedType[] aInterfaces =clazz.getAnnotatedInterfaces();
print(aInterfaces);
Method method = clazz.getMethod("foo");
//获取方法上抛出的带注解的异常
AnnotatedType[] aExceptions =method.getAnnotatedExceptionTypes();
print(aExceptions);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
/**
* 打印带注解类型
* @param array
*/
public static void print(AnnotatedType[] array){
for( AnnotatedType at : array){
Type type =at.getType();//获取基础类型
Annotation[] ans =at.getAnnotations();//获取注解
//打印类型
System.out.println(type);
//打印注解
for( Annotation an : ans){
System.out.println(an);
}
System.out.println("------------");
}
}
}
打印结果
interface java.io.Serializable
@annotation.NotNull(value=Serializable)
------------
class java.lang.ClassNotFoundException
@annotation.NotNull(value=ClassNotFoundException)
------------
JSR269描述
使用apt实现编译时处理Annotation
运行环境jdk1.8
Java提供的javac.exe工具有一个-processor选项,该选项可指定一个Annotation处理器,如果在编译java源文件的时候通过该选项指定了Annotation处理器,那么这个Annotation处理器,将会在编译时提取并处理Java源文件中的Annotation。
每个Annotation处理器都需要实现javax.annotation.processing包下的Processor接口。不过实现该接口必须实现它里面所有方法,因此通常采用继承AbstractProcessor的方式来实现Annotation处理器,一个Annotation处理器可以处理一种或多种Annotation类型。
之前的错误认识:之前以为-processor选项需要指定注解处理器是一个*.java文件,其实是一个.class文件,既然是.class文件,那么肯定是编译过后的,所以需要单独写一个处理器程序annotation-processor,打成一个jar包,然后在使用注解的程序annotation中加入注解处理器依赖包annotation-processor.jar,在编译的时候指定处理器类即可。下面我会分别演示通过javac 命令和maven命令如何进行操作。
下面的项目会使用maven来构建,如果不是使用maven也可以,因为我也会演示如何通过javac 命令来执行注解处理器。
9.6.1 注解处理器程序annotation-processor
修饰id注解
package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修饰id注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Id {
String column(); //该id属性对应表中的列名
String type(); //id属性类型
String generator(); //使用的策略
}
修饰属性注解
package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修饰属性注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Property {
String column(); //该属性对应表中的列名
String type(); //id属性类型
}
修饰实体类注解
package com.zlcook.processor.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//修饰实体类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Persistent {
String table(); //数据库中表名
}
package com.zlcook.processor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import com.zlcook.processor.annotation.Id;
import com.zlcook.processor.annotation.Persistent;
import com.zlcook.processor.annotation.Property;
/**
* 类说明:hiberante注解处理器,用于根据实体bean的注解生成*.hbm.xml文件,处理时期在编译阶段。
*/
public class HibernateAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
// TODO Auto-generated method stub
super.init(processingEnv);
System.out.println("HibernateAnnotationProcessor注解处理器初始化完成..............");
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//定义一个文件输出流,用于生成额外的文件
PrintStream ps = null;
try{
//遍历每个被@Persistent修饰的class文件,使用RoundEnvironment来获取Annotation信息
for( Element t : roundEnv.getElementsAnnotatedWith(Persistent.class)){
//获取正在处理的类名
Name clazzName = t.getSimpleName();
//获取类定义前的@Persistent Annotation
Persistent per = t.getAnnotation(Persistent.class);
//创建文件输出流
String fileName =clazzName+".hbm.xml";
ps = new PrintStream(new FileOutputStream(fileName));
// 执行输出
ps.println("");
ps.println("");
ps.println("");
ps.print(" ");
//获取@Persistent修改类的各个属性字段。t.getEnclosedElements()获取该Elemet里定义的所有程序单元
for(Element ele : t.getEnclosedElements()){
//只处理成员变量上的Annotation,ele.getKind()返回所代表的的程序单元
if( ele.getKind() == ElementKind.FIELD){
//被id注解修饰的字段
Id idAno= ele.getAnnotation(Id.class);
if( idAno != null){
String column =idAno.column();
String type =idAno.type();
String generator = idAno.generator();
// 执行输出
ps.println(" ");
ps.println(" ");
ps.println(" ");
}
//被Property注解修饰的字段
Property p = ele.getAnnotation(Property.class);
if( p !=null){
// 执行输出
ps.println(" ");
}
}
}// end for
ps.println(" ");
ps.println(" ");
}// end for
}catch(Exception e){
e.printStackTrace();
}finally {
if(ps!=null){
try{
ps.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
return true;
}
/**
* 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称
* @return 注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合
*/
@Override
public Set getSupportedAnnotationTypes() {
Set annotataions = new LinkedHashSet();
annotataions.add(Id.class.getCanonicalName());
annotataions.add(Property.class.getCanonicalName());
annotataions.add(Persistent.class.getCanonicalName());
return annotataions;
}
/**
* 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6
* @return 使用的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
注解程序写完打包成jar文件。
打包成jar文件为使用注解处理器的程序提供依赖。
使用maven构建直接使用mvn install,这样就将项目打包成jar依赖到本地仓库中了。
使用java命令打包成jar文件:先用javac编译成.class文件,在使用jar命令打包成jar文件。
使用java命令打包成jar文件
源文件位置:E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java,编译后.class文件存放到classes文件夹下,使用javac命令编译源代码需要指定.java文件,为了避免在命令行中敲太多代码,所以将要编译的源代码文件都列在了sources.list文件中。
源代码文件及编译后文件存放位置
source.list文件内容
E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java>javac -encoding UTF-8
-d classes @sources.list
E:\EclipseWorkspace\Cnu\annotation-processor\src\main\java\classes>jar -cvf annotation-processor.jar com
9.6.2 注解使用程序annotation
com.zlcook.processor
annotation-processor
0.0.5-SNAPSHOT
provided
没有使用maven构建,只要保证运行项目时annotation-processor.jar在classpath路径中就行。根据你是用的开发工具而定,使用eclipse则将jar添加到编译路径中。
编写项目annotation
为了演示自定义注解和注解处理的作用:在编译时根据注解生成*.hbm.xml文件,所以写一个类Person就可以了。代码如下:
package com.zlcook.annotation.bean;
import com.zlcook.processor.annotation.Id;
import com.zlcook.processor.annotation.Persistent;
import com.zlcook.processor.annotation.Property;
/**
* @author 周亮
* @version 创建时间:2017年2月19日 下午10:05:05
* 类说明:使用注解完成映射的实体类
*/
@Persistent(table="person_inf")
public class Person {
@Id(column = "person_id", type = "integer", generator = "identity")
private int id;
@Property(column = "person_name", type = "string")
private String name;
@Property(column = "person_age", type = "integer")
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
9.6.3 运行效果演示
下面就使用javac命令和maven命令编译annotation项目,来演示HibernateAnnotationProcessor处理器的效果。看能不能在编译期生成Person.hbm.xml文件。
javac编译
将annotation-processor.jar拷贝到annotaion的源代码位置,当然你也可以拷贝到其它地方,主要为了引用方便。再新建一个存放编译文件的文件夹classes。如下:
编译器文件情况
在该目录下执行javac 命令
javac命令中指定UTF-8编码、编译后文件存放位置、编译过程中依赖的文件、注解处理器类、需要编译的源文件
E:\EclipseWorkspace\Cnu\annotation\src\main\java>javac -encoding UTF-8 -d classes -classpath annotation-processor.jar -processor com.zlcook.processor.HibernateAnnotationProcessor com/zlcook/annotation/bean/Person.java
执行后效果
当前目录下出现了一个Person.hbm.xml文件
Paste_Image.png
Maven编译
使用maven编译,唯一需要动的的就是指明编译过程中需要的注解处理程序HibernateAnnotationProcessor。为此需要设置maven-compiler-plugin插件中compiler目标的参数。
在pom.xml中设置如下:
maven-compiler-plugin
3.1
default-compile
compile
compile
1.8
com.zlcook.processor.HibernateAnnotationProcessor
mvn clean compile
执行完成后在项目根目录下就出现了Person.hbm.xml文件。