编者的话:注解是java的一个主要特性且每一个java开发者都应该知道如何使用它。html
咱们已经在Java Code Geeks提供了丰富的教程, 如Creating Your Own Java Annotations, Java Annotations Tutorial with Custom Annotation 和 Java Annotations: Explored & Explained.java
咱们也有些文章是关于注解在不一样类库中的应用,包括 Make your Spring Security @Secured annotations more DRY和 Java Annotations & A Real World Spring Example.程序员
如今,是时候汇总这些和注解相关的信息到一篇文章了,祝你们阅读愉快。spring
在这篇文章中咱们将阐述什么是Java注解,它们如何工做,怎么使用它们。数据库
咱们将揭开Java注解的面纱,包括内建注解或称元注解,还将讨论Java8中与之相关的的新特性。api
最后,咱们将实现自定义的注解,编写一个使用注解的处理程序(消费器),它经过java反射使用注解。数组
咱们还会列出一些基于注解,知名且被普遍应用的第三方类库如:Junit,JAXB,Spring,Hibernate。安全
在文章的最后,会有一个压缩文件包含了文章中的全部示例,实现这些例子使用的软件版本以下所示:oracle
注解早在J2SE1.5就被引入到Java中,主要提供一种机制,这种机制容许程序员在编写代码的同时能够直接编写元数据。框架
在引入注解以前,程序员们描述其代码的形式还没有标准化,每一个人的作法各异:transient关键字、注释、接口等。这显然不是一种优雅的方式,随之而来的一种崭新的记录元数据的形式——注解被引入到Java中。
其它因素也促成了这个决定:当时不一样类型的应用程序使用XML做为标准的代码配置机制,这其实并非最佳方式,由于代码和XML的解耦以及将来对这 种解耦应用的维护并不低廉。另外,因为非保留字的使用,例如“@deprecated”自从Java1.4便开始在Java文档中使用。我很是肯定这是一 个如今在注解中使用“@”缘由。
包含注解的设计和开发的Java规范主要有如下两篇:
解释何为注解的最佳方式就是元数据这个词:描述数据自身的数据。注解就是代码的元数据,他们包含了代码自身的信息。
注解能够被用在包,类,方法,变量,参数上。自Java8起,有一种注解几乎能够被放在代码的任何位置,叫作类型注解。咱们将会在后面谈到具体用法。
被注解的代码并不会直接被注解影响。这只会向第三系统提供关于本身的信息以用于不一样的需求。
注解会被编译至class文件中,并且会在运行时被处理程序提取出来用于业务逻辑。固然,建立在运行时不可用的注解也是可能的,甚至能够建立只在源文件中可用,在编译时不可用的注解。
理解注解的目的以及如何使用它都会带来困难,由于注解自己并不包含任何功能逻辑,它们也不会影响本身注解的代码,那么,它们到底为何而存在呢?
这个问题的解释就是我所称的注解消费器。它们是利用被注解代码并根据注解信息产生不一样行为的系统或者应用程序。
例如,在Java自带的内建注解(元注解)中,消费器是执行被注解代码的JVM。还有其余稍后谈到的其余例子,例如JUnit,消费器是读取,分析被注解代码的JUnit处理程序,它还能够决定测试单元和方法执行顺序。咱们会在JUnit章节更深刻。
消费器使用Java中的反射机制来读取和分析被注解的源代码。使用的主要的包有:java.lang, java.lang.reflect。咱们将会在本篇指南中介绍如何用反射从头开始建立一个自定义的消费器。
声明一个注解须要使用“@”做为前缀,这便向编译器说明,该元素为注解。例如:
@Annotation public void annotatedMehod() { ... }
上述的注解名称为Annotation,它正在注解annotatedMethod方法。编译器会处理它。注解能够以键值对的形式持有有不少元素,即注解的属性。
@Annotation( info = "I am an annotation", counter = "55" ) public void annotatedMehod() { ... }
若是注解只包含一个元素(或者只须要指定一个元素的值,其它则使用默认值),能够像这样声明:
@Annotation("I am an annotation") public void annotatedMehod() { ... }
就像咱们看到的同样,若是没有元素须要被指定,则不须要括号。多个注解可使用在同一代码上,例如类:
@ Annotation (info = "U a u O") @ Annotation2 class AnnotatedClass { ... }
一些java自己提供的开箱即用的注解,咱们称之为内建注解。也能够定义你本身的注解,称之为子定义注解。咱们会在下一章讨论。
注解基本上能够在Java程序的每个元素上使用:类,域,方法,包,变量,等等。
自Java8,诞生了经过类型注解的理念。在此以前,注解是限于在前面讨论的元素的声明上使用。今后,不管是类型仍是声明均可以使用注解,就像:
@MyAnnotation String str = "danibuiza";
咱们将会在Java8关联章节看到这种机制的更多细节。
注解能够知足许多要求,最广泛的是:
在这篇手册中咱们将展示几种注解可能的用法,包括流行的Java类库是如何使用它们的。
Java语言自带了一系列的注解。在本章中咱们将阐述最重要的一部分。这个清单只涉及了Java语言最核心的包,未包含标准JRE中全部包和库如JAXB或Servlet规范。
如下讨论到的注解中有一些被称之为Meta注解,它们的目的注解其余注解,而且包含关于其它注解的信息。
咱们会在稍后展开几个例子。
@SuppressWarnings( "unused") private String myNotUsedMethod(){ ... }
更多信息请参考:http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html
Java8带来了一些优点,一样注解框架的能力也获得了提高。在本章咱们将会阐述,并就java8带来的3个注解作专题说明和举例:
@Repeatable注解,关于类型注解的声明,函数式接口注解@FunctionalInterface(与Lambdas结合使用)。
看一个使用的例子。首先咱们创造一个能容纳重复的注解的容器:
/** * Container for the {@link CanBeRepeated} Annotation containing a list of values */ @Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface RepeatedValues { CanBeRepeated[] value(); }
接着,建立注解自己,而后标记@Repeatable
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) @Repeatable( RepeatedValues.class ) public @interface CanBeRepeated { String value(); }
最后,咱们能够这样重复地使用:
@CanBeRepeated( "the color is green" ) @CanBeRepeated( "the color is red" ) @CanBeRepeated( "the color is blue" ) public class RepeatableAnnotated { }
若是咱们尝试去掉@Repeatable
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE_USE ) public @interface CannotBeRepeated { String value(); } @CannotBeRepeated( "info" ) /* * if we try repeat the annotation we will get an error: Duplicate annotation of non-repeatable type * * @CannotBeRepeated. Only annotation types marked * * @Repeatable can be used multiple times at one target. */ // @CannotBeRepeated( "more info" ) public class RepeatableAnnotatedWrong { }
咱们会获得编译器的错误信息:
Duplicate annotation of non-repeatable type
@SuppressWarnings( "unused" ) public static void main( String[] args ) { // type def @TypeAnnotated String cannotBeEmpty = null; // type List<@TypeAnnotated String> myList = new ArrayList<String>(); // values String myString = new @TypeAnnotated String( "this is annotated in java 8" ); } // in method params public void methodAnnotated( @TypeAnnotated int parameter ) { System.out.println( "do nothing" ); }
// implementing its methods @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterface = new MyCustomInterface() { @Override public int doSomething( int param ) { return param * 10; } }; // using lambdas @SuppressWarnings( "unused" ) MyCustomInterface myFuncInterfaceLambdas = ( x ) -> ( x * 10 ); } @FunctionalInterface interface MyCustomInterface { /* * more abstract methods will cause the interface not to be a valid functional interface and * the compiler will thrown an error:Invalid '@FunctionalInterface' annotation; * FunctionalInterfaceAnnotation.MyCustomInterface is not a functional interface */ // boolean isFunctionalInterface(); int doSomething( int param ); }
@Documented @Retention(value=RUNTIME) @Target(value=TYPE) public @interface FunctionalInterface
正如咱们以前屡次说起的,能够定义和实现自定义注解。本章咱们即将探讨。
首先,定义一个注解:
public @interface CustomAnnotationClass
这样建立了一个新的注解类型名为 CustomAnnotationClass。关键字:@interface说明这是一个自定义注解的定义。
以后,你须要为此注解定义一对强制性的属性,保留策略和目标。还有一些其余属性能够定义,不过这两个是最基本和重要的。它们在第8章,描述注解的注解时讨论过,它们一样也是Java内建的注解。
因此,咱们为自定义的注解设置属性:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass implements CustomAnnotationMetho
在保留策略中 RUNTIME 告诉编译器这个注解应该被被JVM保留,而且能经过反射在运行时分析。经过 TYPE 咱们又设置该注解能够被使用到任何类的元素上。
以后,咱们定义两个注解的成员:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.TYPE ) public @interface CustomAnnotationClass { public String author() default "danibuiza"; public String date(); }
以上咱们仅定义了默认值为“danibuiza”的 author 属性和没有默认值的date属性。咱们应强调全部的方法声明都不能有参数和throw子句。这个返回值的类型被限制为以前提过的字符串,类,枚举,注解和存储这些类型的数组。
如今咱们能够像这样使用刚建立的自定义注解:
@CustomAnnotationClass( date = "2014-05-05" ) public class AnnotatedClass { ... }
在另外一种相似的用法中咱们能够建立一种注解方法的注解,使用Target METHOD:
@Retention( RetentionPolicy.RUNTIME ) @Target( ElementType.METHOD ) public @interface CustomAnnotationMethod { public String author() default "danibuiza"; public String date(); public String description(); }
这种注解可使用在方法声明上:
@CustomAnnotationMethod( date = "2014-06-05", description = "annotated method" ) public String annotatedMethod() { return "nothing niente"; } @CustomAnnotationMethod( author = "friend of mine", date = "2014-06-05", description = "annotated method" ) public String annotatedMethodFromAFriend() { return "nothing niente"; }
有不少其它属性能够用在自定义注解上,可是 目标 (Target)和 保留策略(Retention Policy)是最重要的两个。
Java反射API包含了许多方法来在运行时从类,方法或者其它元素获取注解。接口AnnotatedElement包含了大部分重要的方法,以下:
class 经过java.lang.Class被实现,java.lang.reflect.Method 和 java.lang.reflect.Field,因此能够基本上被和任何Java元素使用。
如今,咱们将看一个怎么读取注解的例子:
咱们写一个程序,从一个类和它的方法中读取全部的存在的注解:
public static void main( String[] args ) throws Exception { Class<AnnotatedClass> object = AnnotatedClass.class; // Retrieve all annotations from the class Annotation[] annotations = object.getAnnotations(); for( Annotation annotation : annotations ) { System.out.println( annotation ); } // Checks if an annotation is present if( object.isAnnotationPresent( CustomAnnotationClass.class ) ) { // Gets the desired annotation Annotation annotation = object.getAnnotation( CustomAnnotationClass.class ); System.out.println( annotation ); } // the same for all methods of the class for( Method method : object.getDeclaredMethods() ) { if( method.isAnnotationPresent( CustomAnnotationMethod.class ) ) { Annotation annotation = method.getAnnotation( CustomAnnotationMethod.class ); System.out.println( annotation ); } } }
输出以下:
@com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationClass(getInfo=Info, author=danibuiza, date=2014-05-05) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=friend of mine, date=2014-06-05, description=annotated method) @com.danibuiza.javacodegeeks.customannotations.CustomAnnotationMethod(author=danibuiza, date=2014-06-05, description=annotated method)
在这个程序中,咱们能够看到 getAnnotations()方法来获取全部某个对象(方法,类)上的全部注解的用法。展现了怎样使用isAnnotationPresent()方法和getAnnotation()方法检查是否存在特定的注解,和如何获取它。
注解在Java中可使用继承。这种继承和普通的面向对象继承几乎没有共同点。
若是一个注解在Java中被标识成继承,使用了保留注解@Inherited,说明它注解的这个类将自动地把这个注解传递到全部子类中而不用在子类 中声明。一般,一个类继承了父类,并不继承父类的注解。这彻底和使用注解的目的一致的:提供关于被注解的代码的信息而不修改它们的行为。
咱们经过一个例子更清楚地说明。首先,咱们定义一个自动继承的自定义注解。
@Inherited @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface InheritedAnnotation { }
有一个父类名为:AnnotatedSuperClass,已经被自定义的注解给注解上了:
@InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } }
一个子类继承父类:
@InheritedAnnotation public class AnnotatedSuperClass { public void oneMethod() { } }
子类 AnnotatedSubClass 展现了自动继承的注解 @InheritedAnnotation。咱们看到下面的代码经过 isAnnotationPresent() 方法测试出了当前注解。
pre>System.out.println( "is true: " + AnnotatedSuperClass.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedSubClass.class.isAnnotationPresent( InheritedAnnotation.class ) );</pre> <pre>
输出以下:
is true: true is true: true
咱们能够看到子类虽然并无声明注解,但仍是被自动地注解上了。
若是咱们尝试注解在一个接口中:
@InheritedAnnotation public interface AnnotatedInterface { public void oneMethod(); }
一个实现了该接口的类:
public class AnnotatedImplementedClass implements AnnotatedInterface { @Override public void oneMethod() { } }
通过 isAnnotationPresent() 方法测试:
System.out.println( "is true: " + AnnotatedInterface.class.isAnnotationPresent( InheritedAnnotation.class ) ); System.out.println( "is true: " + AnnotatedImplementedClass.class.isAnnotationPresent( InheritedAnnotation.class ) );
结果以下:
is true: true is true: false
这个结果说明继承注解和接口在一块儿使用时,接口中的注解在实现类中:仅仅被忽略。实现类并不继承接口的注解;接口继承仅仅适用于类继承。正如 AnnotatedSubClass。
@Inheriated注解仅在存在继承关系的类上产生效果,在接口和实现类上并不工做。这条一样也适用在方法,变量,包等等。只有类才和这个注解连用。
一条关于@Inheriated注解的很好的解释在Javadoc中:http://docs.oracle.com/javase/7/docs/api/java/lang/annotation/Inherited.html.
注解不能继承注解,若是你尝试这么作了,就会获得编译器抛出的错误:
Annotation type declaration cannot have explicit superinterfaces
在这一章咱们将展现知名类库是如何利用注解的。一些类库如:JAXB, Spring Framework, Findbugs, Log4j, Hibernate, Junit。它们使用注解来完成代码质量分析,单元测试,XML解析,依赖注入和许多其它的工做。
在这篇手册中咱们将讨论如下类库的部份内容:
这个框架用于完成Java中的单元测试。自JUnit4开始,注解被普遍应用,成为Junit的设计的主干之一。
基本上,JUnit处理程序经过反射读取类和测试套件,按照在方法上,类上的注解顺序地执行它们。固然还有一些用来修改测试执行的注解。其它注解都用来执行测试,阻止执行,改变执行顺序等等。
用到的注解至关多,可是咱们将会看到最重要的几个:
@Test public void testMe() { //test assertions assertEquals(1,1); }
@Before public void setUp() { // initializing variables count = 0; init(); }
@After public void destroy() { // closing input stream stream.close(); }
@Ignore @Test public void donotTestMe() { count = -22; System.out.println( "donotTestMe(): " + count ); }
还有一些测试套件和类库使用了注解,例如Mockito和JMock,使用注解来建立测试对象和预期值。
Hibernate多是一个用得最普遍的对象关系映射类库。它提供了对象模型和关系型数据库的映射框架。使用注解做为设计的一部分。
在本章咱们将讨论一两个由Hibernate提供的注解并解释它的处理程序如何处理它们。
下面的代码段使用了@Entity和@Table。这两个是用来向消费器(Hibernate处理程序)说明被注解的类是一个实体类,以及它映射的SQL表名。实际上,这个注解仅仅是指明了主表,还能够有说明字表的注解。
@Entity @Table( name = "hibernate_annotated" ) public class HibernateAnnotated
接下来的代码段展现了如何向Hibernate处理程序说明被标记的元素是表的主键,并映射名为“id”的列,而且主键是自动生成的。
@Id @GeneratedValue @Column( name = "id" ) private int id;
为了指定标准的SQL表列名,咱们能够写以下注解:
@Column( name = "description" ) private String description;
这说明被标记的元素映射的是这个类所对应的表中名为“description”的一列。
这些注解都来自 http://docs.oracle.com/javaee/6/api/javax/persistence/package-summary.html 企业级Java的包。它们几乎涵盖了全部 Hibernate 全部可用的注解。
Spring是个被普遍使用的Java企业级应用框架。其一项重要的特性就是在Java程序使用依赖注入。
Spring使用注解做为XML(Spring的早期版本使用的基于XML配置)的一种替代方式。如今二者都是可行的。你可使用XML文件或者注解配置你的项目。在我看来二者都各有优点。
咱们将在下例中展现两个可用注解:
@Component public class DependencyInjectionAnnotation { private String description; public String getDescription() { return description; } @Autowired public void setDescription( String description ) { this.description = description; } }
在次代码片断中咱们能够找到两个使用在了类和方法上的注解。
更多关于依赖注入和Spring框架的细节请参考:http://projects.spring.io/spring-framework/.
这是一个用来测量代码质量,并提供一系列可能提升它的工具。它会根据预约义(或者自定义)的违反规则来检查代码。Findbugs提供一系列注解来容许开发者来改变默认行为。
它主要使用反射读取代码(和包含的注解)并决定基于它们,应该采起什么行为。
一个列子是 edu.umd.cs.findbugs.annotations.SuppressFBWarnings 注解期待一种违反规定的键值并把它看成参数。这很是像 java.lang.SuppressWarnings。被用来向 Findbugs 说明当执行代码分析的时候,忽略指定的违反规则。
这是一个例子:
@SuppressFBWarnings( "HE_EQUALS_USE_HASHCODE" ) public class FindBugsAnnotated { @Override public boolean equals( Object arg0 ) { return super.equals( arg0 ); } }
这个类已经重现了 Object 的 equals 方法,可是并无重写 hashCode 方法。这一般会致使问题,由于 hashCode 和 equals 二者应该被同时重写,不然在使用这个对象做为 HashMap 的key值的时候会致使错误。因此,Findbugs会在违反规则报告中创造一个错误条目。
若是注解 @SuppressFBWarnings 设置了 HE_EQUALS_USE_HASHCODE 值之后,处理程序就不会在抛出这类型的错误了。
Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode() Bug: com.danibuiza.javacodegeeks.findbugsannotations.FindBugsAnnotated defines equals and uses Object.hashCode()
这个类重写了 equals 方法,可是没有重写 hashCode 方法,继承自 java.lang.Object 的 hashCode 方法(返回每一个对象被JVM赋予的特定值)。所以,这个类几乎违反了相同的对象必须 hashcode 相同的一致性。
若是你认为这个类的实例不会插入到哈希表,推荐的作法是像这样实现 hashCode 方法:
public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do } Rank: Troubling (14), confidence: High Pattern: HE_EQUALS_USE_HASHCODE Type: HE, Category: BAD_PRACTICE (Bad practice)
这个错误包含了该问题的一种解释而且提示如何处理它。在这种状况下,解决方案应该是实现 hashCode 方法。
想知道FindBugs在 SuppressFBWarnings 注解全部可用的违反规则设置,请参考:http://findbugs.sourceforge.net/bugDescriptions.html.
JAXB是一个用来相互转换和映射XML文件与Java对象的类库。实际上,这个类库与标准JRE一块儿提供,不须要任何额外的下载和配置。能够直接经过引入 java.xml.bind.annotation 包下的类直接使用。
JAXB使用注解来告知处理程序(或者是JVM)XML文件与代码的相互转化。例如,注解能够用来在代码上标记XML节点,XMl属性,值等等。咱们将看到一个例子:
首先,咱们声明一个类说明它应该是XML文件中的一个节点:
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; @XmlType( propOrder = { "brand", "model", "year", "km" } ) @XmlRootElement( name = "Car" ) class Car ...
使用注解@XmlType,@XmlRoootElement。它们用来告知 JAXB 处理程序 Car 这个类在转换后,将会转换成为XML中的一个节点。这个@XmlType说明了属性在XML中的顺序。JAXB将会基于这些注解执行合适的操做。
除了分离的 getter 和 setter 属性,不再须要向这个类中添加其它东西来完成转换。如今咱们须要一个消费器程序来执行转换成XML:
Car car = new Car(); car.setBrand( "Mercedes" ); car.setModel( "SLK" ); car.setYear( 2011 ); car.setKm( 15000 ); Car carVW = new Car(); carVW.setBrand( "VW" ); carVW.setModel( "Touran" ); carVW.setYear( 2005 ); carVW.setKm( 150000 ); /* init jaxb marshaler */ JAXBContext jaxbContext = JAXBContext.newInstance( Car.class ); Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); /* set this flag to true to format the output */ jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true ); /* marshaling of java objects in xml (output to standard output) */ jaxbMarshaller.marshal( car, System.out ); jaxbMarshaller.marshal( carVW, System.out );
程序产生的输入以下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 02 <Car> <brand>Mercedes</brand> <model>SLK</model> <year>2011</year> <km>15000</km> </Car> <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Car> <brand>VW</brand> <model>Touran</model> <year>2005</year> <km>150000</km> </Car>
更多关于JAXB XML和Java 的转换信息请参考:https://jaxb.java.net/
在这篇文档中,咱们解释了Java注解是Java1.5开始一个很是重要的特性。基本上,注解都是做为包含代码信息的元数据而被标记到代码中。它们不会改变或者影响代码的任何意义,而是被第三方称为消费器的程序经过反射的方式使用。
咱们列出了Java默认的内建注解,一些称为元注解例如:@Target或者 @Retention,又有@Override,@SuppressWarnings,还有一些Java8相关的注解,比 如:@Repeatable,@FunctionalInterface和类型注解。咱们还展示了一两个结合使用反射的例子,并描述了一些使用注解的类库 例如Spring, Junit,Hibernate。
注解是Java中一种分析元数据的强大机制,能够在不一样的程序中担任不一样的做用,例如校验,依赖注入,单元测试。
这是一个java注解的教程。
你能够从这里下载本教程全部代码: customAnnotations
这是一些很是有用的关于Java注解的资料:
官方Java注解地址:http://docs.oracle.com/javase/tutorial/java/annotations/
维基百科中关于Java注解的解释:http://en.wikipedia.org/wiki/Java_annotation
Java规范请求250:http://en.wikipedia.org/wiki/JSR_250
Oracle 注解白皮书:http://www.oracle.com/technetwork/articles/hunter-meta-096020.html