【Java】Annotation

1.概念

注解Annotation是java 1.5的新特性,是一种能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解,可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”。

2.作用

  1. 标记,用于告诉编译器一些信息
  2. 编译时动态处理,如动态生成代码
  3. 运行时动态处理,如得到注解信息

Java注解可以用在构建期。当构建我们的工程时,构建进程会编译源码、生成xml文件,打包编译后的代码和文件到jar包。构建过程一般由构建工具自动完成,常用的构建工具有ant、maven。构建工具在构建时会自动扫描我们的代码,当遇到构建期注解时,会根据注解的内容生成源码或者其它文件

3.注解的格式

  1. 一个java注解由一个@符后面跟一个字符串构成
@Entity
  1. 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();
}

img

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值