自学内容网 自学内容网

Java基础之注解

概述

Annotation,注解是Java5的特性,是插入在代码中的一种元数据(metadata),为程序代码本身提供额外的信息。注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用反射机制进行处理。

一个最简单的注解定义:

public @interface MyAnnotation {
}

上面这个注解没有添加任何注解声明(如@Documented、@Retention、@Target),也没有定义属性。

设置默认值:允许通过default关键字为任何数据成员指定默认值,参考下面的@Autowired等注解。

定义注解需遵照的规则:

  • 注解声明以@interface开设,随后是注解的类名;
  • 为了创建注解的参数,需要使用参数的类型声明方法;
  • 方法声明不应包含任何参数;
  • 方法声明不应包含任何throws子句;
  • 方法的返回类型应该为:基本类型,字符串,类,枚举,上述类型的数组。

分类

可分为三类:

  • 标记注解:无任何元素的注解,Marker注解,标识注解
  • 单值注解:有一个元素的注解
  • 多值注解:有多个元素的注解。

从另一个角度分类,可分为:

  • 类注解
  • 方法注解
  • 属性注解
  • 变量注解
  • 等等

标记注解,如用于重写的@Override:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

单值注解,只接受单值类型,默认情况下,数据成员使用value指定,如Spring提供的@Value用于获取配置值:

public @interface Value {
String value();
}

使用此注解时:

@Value(value = "${http.port}")
private Integer port;

可省略value:

@Value("${http.port}")
private Integer port;

当然,数据成员也支持不使用value定义。如Spring提供的@Autowired用于注入Bean:

public @interface Autowired {
boolean required() default true;
}

多值注解,如JDK自带的@Deprecated,源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
// JDK9增加的属性,表示自哪个版本开始被废弃
String since() default "";
// JDK9增加的属性,表示是否会在未来版本里删除被@Deprecated标记的元素。默认值为false
// 注意:此字段为false时,IDEA提示为黄色,可通过编译;此字段为true时,IDEA提示为红色,编译失败。
boolean forRemoval() default false;
}

元注解

Meta-Annotations,注解的注解,可用于其他注解上的注解,必须是由JDK提供,就是为注解本身提供额外的信息,从而约束或增强注解的能力,包括:

  • @Documented:用于指定Javadoc生成API文档时显示该注解信息;被注解的元素将会作为Javadoc产生的文档中的内容。注解都默认不会成为成为文档中的内容;
  • @Inherited:用于指定被描述的注解可以被其所描述的类的子类继承;
  • @Target:用于约束被描述的注解的使用范围,当被描述的注解超出使用范围则编译失败;
  • @Retention:用于约束被描述的注解的作用范围,注解的作用范围有三个,参考下面的RetentionPolicy。

@Inherited

源码如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

用于指示类上的注解会被其子类继承,因此仅适用于类级注解,而不适用于方法、字段或参数等。当使用具有@Inherited的注解对类进行注解时,该类的任何子类都将继承该注解,除非它明确覆盖它。

// 注解默认不会被子类继承
@MyAnnotation
public class Parent{}

Son并没有继承注解MyAnnotation:

public class Son extends Parent{}

通过给Parent增加@Inherited,子类将会继承父类的@MyAnnoation注解。

绝大多数情况下,此注解几乎没啥用。

比如@SpringBootApplication有@Inherited注解。什么情况下,出于什么考虑需要继承SpringBootApplication呢?

另外,Spring Cache提供的几个注解,如@Caching、@Cacheable和@CacheEvict都使用@Inherited注解。

如果基于Spring Cache和Redis等组件,自研一套高度定制的缓存框架,可能会使用到@Inherited的继承功能:

@Cacheable("items")
class ParentService {
    public String getItem() {
        return "item from cache";
    }
}

ParentService是通用的父项目,ChildService可能是某个微服务模块:

class ChildService extends ParentService {
    // Inherits @Cacheable("items") behavior from ParentService
}

RetentionPolicy

源码如下:

public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}

包括:

  • SOURCE:作用范围为源码,就是仅存在于java文件中,执行javac命令时将会去除该注解;
  • CLASS:作用范围为二进制码,就是存在于class文件中,执行java命令时会去除该注解;
  • RUNTIME:作用范围为运行时,就是我们可以通过反射动态获取该注解。

ElementType

源码如下:

public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE,
MODULE,
RECORD_COMPONENT;
}

解读:
枚举类ElementType用于指示注解可以应用的目标(即注解可以放置的地方)。包括如下:

  • TYPE:可用于类、接口、
  • FIELD:用于类的成员变量;
  • METHOD:用于方法;
  • PARAMETER:用于方法或构造函数的参数;
  • CONSTRUCTOR:用于构造方法;
  • LOCAL_VARIABLE:用于局部变量;
  • ANNOTATION_TYPE:用于标注元注解;
  • PACKAGE:用于包;
  • TYPE_PARAMETER:用于泛型类型参数声明;
  • TYPE_USE:用于泛型类;
  • MODULE:用于模块,JDK9引入模块化后新增的类型;
  • RECORD_COMPONENT:用于record类型,JDK16引入record类这一新增的类型。

反射

通过反射可获取类、方法等上的注解信息:

public class Test {
public static void main(String[] args){
MyAnnotation ma = Test.class.getAnnotation(MyAnnotation.class);
System.out.println(ma.value());
// 获取自身和从父类继承的注解
Annotation[] annotations = Test.class.getAnnotations();
// 仅获取自身的注解
Annotation[] annotations1 = Test.class.getDeclaredAnnotations();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MyAnnotation {
String value() default "hello annotation";
}
}

分析注解的解析原理,需要看最顶层接口,这个接口就是AnnotatedElement。其源码如下:

public interface AnnotatedElement {
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}

<T extends Annotation> T getAnnotation(Class<T> annotationClass);
Annotation[] getAnnotations();
Annotation[] getDeclaredAnnotations();

default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
T[] result = getDeclaredAnnotationsByType(annotationClass);
if (result.length == 0 && // Neither directly nor indirectly present
    this instanceof Class<?> cls && // the element is a class
    AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
    Class<?> superClass = cls.getSuperclass();
    if (superClass != null) {
        // Determine if the annotation is associated with the superclass
        result = superClass.getAnnotationsByType(annotationClass);
    }
}
return result;
}

default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}

default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
return AnnotationSupport.
getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
collect(Collectors.toMap(Annotation::annotationType, Function.identity(), 
((first,second) -> first), LinkedHashMap::new)), annotationClass);
}
}

接口AnnotatedElement提供的方法:

  • getAnnotations():返回该元素的所有注解,包括没有显式定义该元素上的注解;
  • getAnnotation(class):按照传入的参数获取指定类型的注解,返回null说明当前元素不带有此注解;
  • isAnnotationPresent(annotation):检查传入的注解是否存在于当前元素;
  • getDeclaredAnnotation(class):按照传入的参数获取
  • getDeclaredAnnotations()
  • getAnnotationsByType(class)
  • getDeclaredAnnotationsByType(class)

AnnotatedElement的实现类体系如下图:
在这里插入图片描述
Java反射API包含许多类和方法,用于在运行时从类,方法或其它元素获取注解信息。

异常

JDK为注解解析提供一个Error和2个Exception:

  • AnnotationFormatError
  • AnnotationTypeMismatchException
  • IncompleteAnnotationException

实战

自定义注解用于拦截未登录用户:

package com.johnny.interceptor;

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;

/**
 * 用于标示方法是否需要校验session
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface VerifySession {
boolean need() default true;
}

定义一个AoP拦截器,解析注解

package cn.johnny.interceptor;

import cn.johnny.service.UserService;
import jakarta.annotation.Resource;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;

@Component
public class SessionInterceptor implements MethodInterceptor {
@Resource
private UserService userService;

@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();
if (method.isAnnotationPresent(VerifySession.class) && method.getAnnotation(VerifySession.class).need()) {
userService.checkSession();
}
return methodInvocation.proceed();
}
}

UserService里的校验用户Session的方法checkSession():

public void checkSession() {
// 校验Session里的UserId, Status等信息
// 数据库校验用户是否存在
}

应用

注解在JDK和其他各种框架中,有非常广泛的应用。

JDK

JDK自带很多内置注解:

  • @Override:向编译器说明被注解的元素是重写父类里的某个元素。此注解并非强制性的,不过可以在重写错误时帮助编译器产生错误提醒;
  • @Resource:用于Bean注入,优于@Autowired;
  • @Deprecated:被标注的元素已不再推荐使用,可让编译器产生警告消息。可用于类、方法、变量、属性等之上,即@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})。一般情况下,优秀的框架源码里使用此注解时,会给出某元素被废弃的解释和原因,包括另一个可取代的API或策略;
  • @SuppressWarnings:阻止编译时的警告信息,需要接收一个String的数组作为参数。参考Java注解@SuppressWarnings
  • @FunctionalInterface:自JDK8引入,表示一个函数式接口元素;
  • @Repeatable:自JDK8引入
  • @Native:自JDK8引入
  • @SafeVarargs:自JDK7引入,断言方法或构造器的代码不会对参数进行不安全的操作。在Java后续版本中,使用这个注解时将会令编译器产生一个错误在编译期间防止潜在的不安全操作;
  • @Contended:自JDK8引入的内部使用的注解,用于内存对齐填充;

@FunctionalInterface

函数式接口是一种只有一个抽象方法(非默认)的接口。编译器会检查被注解元素,如果不符,就会产生错误。

JAXB

JDK提供用于XML文件和Java对象之间互相转换的模块。参考XML与Java

JUnit

参考单元测试理论储备及JUnit5实战

Spring

参考Spring系列之Spring(Boot/Cloud)常用注解

JPA/ORM

JPA,Spring Data JPA,Hibernate,或MyBatis,每个组件各自都提供若干个注解。
参考Spring系列之Spring Data JPA介绍

JSR 303 – Bean Validation

参考数据校验validation

参考


原文地址:https://blog.csdn.net/lonelymanontheway/article/details/142931028

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!