一、注解简介
注解也叫元数据,是JDK1.5版本开始引入的一个特性,用于对代码进行标记说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解修饰
1.1、注解的类型
1、JDK注解和框架注解:JDK本身提供了很多注解比如@Resource、@PostConstruct等;另外常用的框架也提供了很多注解,比如Spring的@Autowired,@Service等等,这些注解使用时会自动被JDK或框架进行识别解析;
2、元注解:元注解用于修饰注解的,如@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)
3、自定义注解:用户可以根据自行需求自定义注解
1.2、元注解
@Retention:定义注解的生命周期,默认是CLASS,取值范围如下:
SOURCE | 在编译阶段被抛弃,通常用于编译时使用,比如@Override注解 |
CLASS | 在编译阶段会被写入字节码,当类加载的时候会被丢弃 |
RUNTIME | 不会被丢弃,运行期间也可以使用,所以通过反射机制就可以读取该注解的信息,通常自定义注解都会采用RUNTIME类型 |
@Target:定义注解可以修饰的目标,默认是可以修饰任意目标,取值范围如下
TYPE | 用于描述类、接口或enum声明,如@Service、@Component等 |
FIELD | 用于描述属性,比如@JSONField等 |
METHOD | 用于描述方法,比如@Override |
PARAMETER | 用于描述方法参数,比如Mybatis框架中的@Param |
CONSTRUCTOR | 用于描述构造函数 |
LOCAL_VARIABLE | 用于描述局部变量 |
ANNOTATION_TYPE | 用于描述注解类型,比如@Target本身,@Retention,@Document注解等 |
PACKAGE | 用于描述包名 |
TYPE_PARAMETER | 用于描述参数类型 |
TYPE_USE | 表示该注解能使用在使用类型的任意语句中 |
@Inherited:定义注解是否继承给子类
当@Inherited注解修饰了一个注解,那么如果这个注解修饰了一个类,那么这个类的子类也会继承该注解
@Documented:定义注解是否将注解信息加到Java文档中
1.3、注解的组成
注解通常有几个部分组成,包括修饰该注解的元注解,注解名称和注解方法,当然也可以将注解仅当作标记作用,没有任何方法也行,比如@Override注解就没有任何方法,仅当作标记使用
二、注解的使用
通常我们Web服务提供接口需要用户登录之后才可以访问,此时如果每个接口都判断下用户是否登录就会冗余很多的代码,所以需要在执行接口方法之前有一层验证用户登录的逻辑,可以通过过滤器,拦截器等方式实现,此时就可以配合注解来实现,在需要进行登录验证的方法上添加一个自定义的注解,然后每个添加了注解的方法就需要验证登录,没有注解的方法就不需要登录,实现方式如下:
自定义注解@Logined
@Documented @Target(ElementType.METHOD) /** 修饰方法*/ @Retention(RetentionPolicy.RUNTIME)/** 生命周期为运行期间*/ public @interface Logined { /** 定义方法,如果没有登录的情况下是否直接抛异常*/ boolean exception() default false; }
定义了注解之后就可以直接使用,但是想要使注解的效果生效,就需要有一套获取注解并处理业务的逻辑,此时就离不开Java的反射机制,需要通过反射机制获取到修饰在方法、类、属性上的注解来进行判断是否加了注解。
另外在使用Spring框架时,可以配置AOP来配合使用自定义注解,比如以下案例就是用来处理@Logined注解的逻辑:
@Aspect @Component public class LoginAspect { @Around("@annotation(com.test.annotation.Logined)") public Object doBefore(ProceedingJoinPoint jp) throws Throwable { if (MessageConfig.LOCAL.get() == null) { System.out.println("请求用户为空,返回401:" + jp.getSignature().getName()); return Result.returnUnauthorized(); } return jp.proceed(); } }
三、注解的实现原理
注解本身没有任何逻辑,只能起到标记的作用,实现的逻辑完全取决于处理注解的逻辑,而处理注解就需要先找到注解,此时就离不开Java的反射机制,主要是通过Constructor、Class、Method、Field等反射相关类的getAnnotation(Class annotationClass)方法获取对应的注解,如果能获取到注解那么就表示被注解修饰了,案例如下:
1 /** 1.查找类上的注解 */ 2 Annotation classAnnotation = cla.getAnnotation(Logined.class); 3 if(classAnnotation != null){ 4 System.out.println("类被@Logined注解修饰"); 5 } 6 7 /** 2.查找方法上的注解 */ 8 Method[] methods = cla.getMethods(); 9 for (Method method : methods){ 10 if(method.getAnnotation(Logined.class) != null){ 11 System.out.println("方法:" + method.getName() + "被注解@Logined" + "修饰"); 12 } 13 } 14 15 /** 3.查找属性上的注解 */ 16 Field[] fields = cla.getFields(); 17 for (Field field : fields){ 18 if(field.getAnnotation(Logined.class) != null){ 19 System.out.println("属性:" + field + "被注解@Logined" + "修饰"); 20 } 21 } 22 23 /** 4.查找构造函数上的注解 */ 24 Constructor constructor = cla.getConstructor(String.class); 25 if(constructor.getAnnotation(Logined.class)!=null){ 26 System.out.println("构造器被@Logined注解修饰"); 27 }