文章目录
1.概念
注解Annotation是java 1.5的新特性,是一种能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”。
2.作用
- 标记,用于告诉编译器一些信息
- 编译时动态处理,如动态生成代码
- 运行时动态处理,如得到注解信息
Java注解可以用在构建期。当构建我们的工程时,构建进程会编译源码、生成xml文件,打包编译后的代码和文件到jar包。构建过程一般由构建工具自动完成,常用的构建工具有ant、maven。构建工具在构建时会自动扫描我们的代码,当遇到构建期注解时,会根据注解的内容生成源码或者其它文件
3.注解的格式
- 一个java注解由一个@符后面跟一个字符串构成
@Entity
- java注解中一般包含一些元素,这些元素类似于属性或者参数,可以用来设置值
@Entity(userName = “zphuan”, userAge = “100”)
该注解中有两个元素:userName,userAge,分别赋予了对应的元素值。
4.注解的位置
1.注解的位置
注解可以用于描述一个类、接口、方法、方法参数、字段等。
@Author(
name = "zphuan",
date = "3/24/2017"
)
class MyClass() {
@Value
String name;
@Override
void mySuperMethod(@RequestParam String name) {
}
}
2.使用的细节
1.没有参数则不用写参数体,比如@Override
2.如果只有一个参数比如named value,则参数名可以省略,比如:
@SuppressWarnings("unchecked")
void myMethod() { ... }
3.也可以同时使用多个注解来标示,比如:
@Author(name = "Jane Doe")
@EBook
class MyClass { ... }
4.可以重复使用注解,不过只有在java SE 8 才支持。比如:
@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }
标明两个作者对该类的书写,一个管理员,一个开发者等。
- 注意:如果想自定义一个可以用于重复使用的注解(自定义注解在后面会涉及),记得加上@Repeatable,比如:
@Repeatable(Authors.class)
public @interface Author {
String name() default "";
String value() default "";
}
public @interface Authors {
Author[] value() default {};
}
5.Annotation 架构
Annotation 是一个接口,有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
6.Java内置注解
Java本身提供了三个内置注解,他们分别是:@Deprecated、@Override、@SuppressWarnings,包路径java.lang
1.@Deprecated
可以用来描述一个类、方法或者字段,表示java不赞成使用这些被描述的对象,如果我们使用了这些类、方法或者字段,编译器会给我们警告。
2.@Override
注解是一个编译时注解,它主要用在一个子类的方法中,当被注解的子类的方法在父类中找不到与之匹配的方法时,编译器会报错。
3.@SuppressWarnings
注解的作用是使编译器忽略掉编译器警告。比如,如果我们的一个方法调用了一个@Deprecated方法,或者做了一个不安全的类型转换,此时编译器会生成一个警告。如果我们不想看到这些警告,我们就可以使用@SuppressWarnings注解忽略掉这些警告.
7.元注解
元注解就是用来描述注解的注解,包路径java.lang.annotation
1.@Documented
作用是告诉JavaDoc工具,当前注解本身也要显示在Java Doc中。比如上面我写的自定义注解。
2.@Retention
用来定义当前注解的作用范围
package java.lang.annotation;
// 这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码
// 生命周期长度 SOURCE < CLASS < RUNTIME
public enum RetentionPolicy {
SOURCE, /* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃 */
CLASS, /* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入,在运行期有效,JVM在运行期通过反射获得注解信息 */
}
Annotation 的每一个实现类,都 和 1 个 RetentionPolicy 关联
@Retention(RetentionPolicy.RUNTIME)
3.@Target
用于指定注解作用于java的哪些元素,未标注则表示可修饰所有.有以下这些元素类型可供选择:
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 元注解类型,只能用来注解其它的注解,例如@Target和@Retention */
PACKAGE /* 包声明 */
}
Annotation 的每一个实现类,和 1~n 个 ElementType 关联。
// 作用范围:字段、枚举的常量、方法
@Target(value={ElementType.FIELD,ElementType.METHOD})
4.@Inherited
注解表示当前注解会被注解类的子类继承。比如有一个自定义注解:
@Inherited
public @interface InheritedAnnotation{
}
如果一个类使用了上面这个注解,那么Generation3List的子类也会继承这个注解。
@InheritedAnnotation
public class Generation3List {
}
8.自定义注解
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
String value() default "";
}
@interface关键字就代表这是一个注解类型,所以使用@interface关键字就可以创建注解了。
注解中的每个元素定义类似于接口中的方法定义。每个元素定义包含一个数据类型和名称,注解元素的数据类型可以是java基本数据类型、String、数组,但不能是复杂对象类型。
我们可以通过default关键字为某个元素设置默认值,当一个元素被设置默认值之后,这个元素便成了注解的可选元素。
@MyAnnotation1("xxx")
public class Generation3List{
}
9.Annotation解析
1. 运行时Annotation解析@Retention(RetentionPolicy.RUNTIME)
1.获取运行时注解
你需要通过反射来获取运行时注解,可以从 Package、Class、Field、Method…上面获取,基本方法都一样,几个常见的方法如下:
/**
* 获取指定类型的注解
*/
public <A extends Annotation> A getAnnotation(Class<A> annotationType);
/**
* 获取所有注解,如果有的话
*/
public Annotation[] getAnnotations();
/**
* 获取所有注解,忽略继承的注解
*/
public Annotation[] getDeclaredAnnotations();
/**
* 指定注解是否存在该元素上,如果有则返回true,否则false
*/
public boolean isAnnotationPresent(Class<? extends Annotation> annotationType);
/**
* 获取Method中参数的所有注解
*/
public Annotation[][] getParameterAnnotations();
要使用这些函数必须先通过反射获取到对应的元素:Class、Field、Method 等。
2.@Target(value = { ElementType.TYPE})
@InheritedAnnotation
@ClassPreamble(
author = "zphuan",
date = "3/17/2002",
currentRevision = 6,
lastModified = "4/12/2004",
lastModifiedBy = "Jane Doe",
// Note array notation
reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List {
}
import com.zphuan.MyAnnotation.ClassPreamble;
public class Main {
public static void main(String[] args) {
//通过反射获得Generation3List的注解信息
ClassPreamble preamble = (ClassPreamble) Generation3List.class.getAnnotation(ClassPreamble.class);
System.out.println("preamble author:"+preamble.author());
}
}
3.@Target(value = { ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface MethodAnnotation{
String author();
String date();
}
该注解作用与下面这个类中的test方法上:
public class Generation3List {
@MethodAnnotation(author = "zphuan", date = "3/26/2017")
public void test(){
}
}
现在我想要获取到该方法上的注解信息,可以这样反射获取:
try {
Class clazz = Class.forName("com.zphuan.Generation3List");
for(Method method : clazz.getMethods()){
MethodAnnotation methodAnnotation = method.getAnnotation(MethodAnnotation.class);
if(methodAnnotation!=null){
System.out.println("author:"+methodAnnotation.author());
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
2. 编译时Annotation解析@Retention(RetentionPolicy.CLASS)
1.创建一个注解类型为CLASS的Annotation,如下:
@Retention(RetentionPolicy.CLASS)
public @interface ClassAnnotation{
String author();
}
@ClassAnnotation(author = "zphuan")
public class Generation3List {
@MethodAnnotation(author = "zphuan", date = "3/26/2017")
public void test(){
}
}
2.@Retention(RetentionPolicy.CLASS)
,由编译器自动解析
编译器在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理
import java.util.HashMap;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.zphuan.MyAnnotation.ClassAnnotation;
/**
* 1.自定义类继承自 AbstractProcessor
* @SupportedAnnotationTypes的值为当前类支持的注解的完整类路径,支持通配符。表示这个 Processor 要处理的 Annotation 名字.
* @SupportedSourceVersion 标识该处理器支持的源码版本
*/
@SupportedAnnotationTypes({ "com.zphuan.MyAnnotation.ClassAnnotation" })
public class ClassProcessor extends AbstractProcessor {
private static HashMap<String, String> map;
/**
* 2.重写其中的 process 函数
* annotations 表示待处理的 Annotations
* env 表示当前或是之前的运行环境
* process 函数返回值表示这组annotations是否被这个Processor接受,如果接受后续子的rocessor不会再对这个Annotations进行处理
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
map = new HashMap<String, String>();
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
ClassAnnotation classAnnotation = element.getAnnotation(ClassAnnotation.class);
map.put(element.getEnclosingElement().toString(), classAnnotation.author());
}
}
return false;
}
}
10.Annotation 的作用
Annotation 是一个辅助类,它在 Junit、Struts、Spring 等工具框架中被广泛使用。
1.编译检查
Annotation 具有"让编译器进行编译检查的作用"。
例如,@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。
若某个方法被 @Override 的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标示,但父类中却没有"被 @Override 标注"的同名方法,则编译器会报错。
2.在反射中使用 Annotation
3.根据 Annotation 生成帮助文档
通过给 Annotation 注解加上 @Documented 标签,能使该 Annotation 标签出现在 javadoc 中。
4.自定义 Annotation 来实现一些功能
一般搭配AOP一起使用,完成业务功能
11.注解执行顺序
同一包下,按注解类的加载顺序(类名字符串排序),与注解使用时的顺序无关
当在不同aspect定义的两条Advice都需要在同一Join Point上运行时,除非专门指定顺序,否则它们的执行顺序是不确定的。
Spring AOP遵循与AspectJ相同的优先级规则来确定建议执行的顺序。
可以通过@Order控制优先级,数字越小优先级越高。在进来时,优先级最高的Advice首先运行,在出去时,优先级最高的Advice最后运行。
@Target({ElementType.Method})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation1{
}
@Target({ElementType.Method})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation2{
}
@Slf4j
@Aspect
@Order(1)
public class AopAspect1{
@Around("@annotation(com.dyn.util.annotation.TestAnnotation1)")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
log.info("TestAnnotation1被加载");
return pjp.proceed();
}
}
@Slf4j
@Aspect
@Order(2)
public class AopAspect2{
@Around("@annotation(com.dyn.util.annotation.TestAnnotation2)")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
log.info("TestAnnotation2被加载");
return pjp.proceed();
}
}
@RestController
public class Controller{
@TestAnnotation1
@TestAnnotation2
@TestAnnotation10
@TestAnnotation01
@TestAnnotation0
@RequestMapping(value="/test",method=RequestMethod.POST)
public void test(){
}
}
MyAnnotation0 > MyAnnotation01 > MyAnnotation1 > MyAnnotation10 > MyAnnotation2
12.Spring注解是如何生效的
现在大部分开发已经采用Spring Boot了,少了很多配置文件,方便了许多。以前在使用注解,比如@Autowired、@Resource 或者事务相关的一些注解时,我们会首先在配置文件里面加入这样的配置:
context:component-scan
context:annotation-config
tx:annotation-driven
这样就能告诉Spring容器在启动的时候,把相应的后处理器(BeanPostProcessor)初始化,交给Spring容器来管理,然后通过这些后处理器 去实现 各种注解(的功能),比如实现:@Autowired 默认 按类型注入属性 。具体来说,@Autowired 是Spring框架提供的注解,它是由AutowiredAnnotationBeanPostProcessor(实现了BeanPostProcessor接口) 后处理器实现的;@Resource 是JDK里面提供的注解,它由CommonAnnotationBeanPostProcessor实现注入的功能。
那使用了Spring Boot之后,几乎已经看不到上述配置了,简单来说:Spring Boot应用程序在启动的时候,默认加载了一批BeanPostProcessor,这样就可以实现这些注解的功能了。这是怎么做到的呢?
我们知道,Spring Boot应用程序会在Application启动类中标上 @SpringBootApplication 注解。这个注解包含了三个子注解:
- SpringBootConfiguration 将Application启动类作为Bean类注入到Spring容器中
- EnableAutoConfiguration 实现自动配置功能
- ComponentScan 实现自动扫描的功能,具体来说:就是告诉Spring容器注册"一些"Bean后处理器,从而能够支持各种注解。
比如说,要想让@Autowired生效,Spring容器必须加载了AutowiredAnnotationBeanPostProcessor 后处理器,看这个类的源码注释:
A default AutowiredAnnotationBeanPostProcessor will be registered by the “context:annotation-config” and “context:component-scan” XML tags.
因此,只要我们在XML配置文件里面配置了 context:component-scan
,AutowiredAnnotationBeanPostProcessor 就能被注册到Spring容器中。而使用Spring Boot之后,由@SpringBootApplication注解下面的@ComponentScan 完成了同样的功能。这就是不需要我们自己在XML配置文件里面写一行 context:component-scan
配置的原因。
类似地:CommonAnnotationBeanPostProcessor 实现了 @Resource 注解的功能,看看它的类源码注释可知:它负责实现@PostConstruct、@PreDestroy、@Resource等注解,配置了 @ComponentScan 就能将它注册到Spring容器中。
最后再来说说开启事务的注解@EnableTransactionManagement:
当我们在Application启动类 上标注 @EnableTransactionManagement 时,就表示开启事务,它等价于XML配置方式 tx:annotation-driven
。