Java注解入门及常用注解概览

172 阅读11分钟

问题

什么是注解,它用来做什么事情,解决什么问题

注解就是一个标记,它通过元注解进行修饰,可以传入一些值和设定注解策略,不同策略的注解可以解决不同的事情:

  1. SOURCE策略的注解只在编写.java文件中有效,最常见的就是@Override注解,在开发者重写父类方法有误时,在编写阶段就能提示异常
  2. CLASS策略的注解在编译成.class文件中仍存在,但是JVM加载的时候就会被遗弃
  3. RUNTIME策略的注解在JVM运行时还会存在,也是我们最常使用的注解策略

元注解

@Target

说明注解的作用目标,默认为任何元素

为这个注解传值:

@Target(value = {ElementType.FIELD})
  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
  • ElementType.FIELD:允许作用在属性字段上
  • ElementType.METHOD:允许作用在方法上
  • ElementType.PARAMETER:允许作用在方法参数上
  • ElementType.CONSTRUCTOR:允许作用在构造器上
  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
  • ElementType.ANNOTATION_TYPE:允许作用在注解上
  • ElementType.PACKAGE:允许作用在包上

@Retension

使用:

@Retention(value = RetentionPolicy.RUNTIME)

定义这个注解能保留多久,一共有三种策略:

  1. SOURCE

    注解只保留在源文件中,当java文件编译成class文件时,注解被遗弃,被编译器忽略

  2. CLASS

    注解保留到class文件,但JVM加载class文件的时候被遗弃,这是默认的生命周期

  3. RUNTIME

    注解不仅保存到class文件中,JVM加载class文件后仍然存在

问题

这三种策略的应用场景有哪些?
  1. RESOURCE

    • Lombok插件的@Data等注解用的就是Resource,在编译成class文件时,根据@Data等注解自动生成代码
    • @Override标注后去重写父类方法,编译器会去检查该方法的正确性(即该方法必须在父类中存在),有错误时进行提示,如果不加并且重写错了重写方法,程序不会提示报错,导致潜在的bug
    • @Deprecated:标记该类、方法或属性已被弃用,不建议使用,但是目前还可以使用,在IDEA中被该注解标记的对象会有一条横线作为提示
  2. CLASS

    • MapStruct映射工具用的@Mapper注解是CLASS,该注解会在.class文件中看到
  3. RUNTIME

    RUNTIME是我们最常使用的注解,因为它可以可以保存在JVM中,所以我们可以通过反射获取,然后去做一些逻辑操作

RESOURCE和CLASS的区别是什么?

这两个策略都很相似,我并没有找到这两个策略各自的应用场景,虽然Lombok注解策略是RESOURCE,MapStruct用的是CLASS,但他们去做的事情其实是类似的,至于这个注解保留到.class文件中还是不保留有什么影响,我还没有搞懂。

所以我得出的结论是:

RESOURCE策略的典型案例是JDK自带的@Override,该注解的作用仅仅是在编译阶段进行提示,不需要写进.class,所以用RESOURCE;

CLASS策略虽然会保留到源文件中,但是不会被JVM加载到,应该是在源文件打包到JVM的过程中,存在某些注解需要被扫描出来去做某些事情,这些事情与开发者无关,与JVM有关。

顺带一提,CLASS策略是默认策略。

@Inherited

inherited是继承的意思,如果一个超类使用了这个注解,那么如果他的子类没有被任何注解应用的话,那么这个子类就会继承这个超类的注解

@Repeatable

repeatable是可重复的意思,可以对同一种注解重复进行使用,比如对一个用户使用多个相同注解来标识为多个角色

JAVA自带注解

常见的有以下几个:

  • @Override

    这个我们再熟悉不过了,该注解强制子类必须覆盖父类的方法,不然报错

  • @Deprecated

    标记某个元素已经过时,这个是给编译器看的,编译器会对该元素画一条横线提示开发者这个已过时

  • @SuppressWarnings

    告诉编译器不提示某个警告信息

自定义注解

学习了元注解后,我们就可以自定义注解来实现一些业务逻辑,通常情况下我们需要动态的用反射来获取注解,所以注解的Retension策略都是用的RUNTIME,至于@Target、@Inherited等就看场景区分了。

我们用【登录校验功能】来演示自定义注解的使用:

首先创建登录校验注解@LoginValid:

// 作用元素是属性
@Target(value = ElementType.FIELD)
// RUNTIME策略以便于我们用反射来获取
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LoginValid {
    // 声明这个注解的传值属性,可以给一个默认值
    String message() default "";
}

写一个登录请求参数类:

public class LoginModel {
    @LoginValid(message = "用户名不能为空")
    private String username;
​
    @LoginValid(message = "密码不能为空")
    private String password;
​
        // 省略了setter/getter代码
}

最后是关键的登录逻辑代码:

public class LoginService {
    public static boolean login(LoginModel model) throws IllegalAccessException {
        // 获取该类下的所有字段
        Field[] fieldList = model.getClass().getDeclaredFields();
        // 遍历字段,判断是否有@LoginValid注解,有就判断该属性是否为空值
        for (Field field : fieldList) {
            // 允许属性访问权限
            field.setAccessible(true);
            LoginValid annotation = field.getAnnotation(LoginValid.class);
            if (annotation != null && field.get(model) == null) {
                // 登录校验不通过抛出异常
                throw new RuntimeException(annotation.message());
            }
        }
​
        return true;
    }
}

自定义注解的花样还有很多,传值类型、嵌套注解等,可以在有业务需要的时候再去研究使用

Spring的注解

@SpringBootApplication

是以下三个注解的复合注解,整合成一个方便使用:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • ComponentScan

@SpringBootConfiguration

等于@Configuration,标记为配置类,两者唯一的区别是@SpringBootConfiguration允许自动找到配置

@EnableAutoConfiguration

允许自动配置,Spring会去加载需要配置的Beans,默认扫描的是当前启用注解的Class对象所在的目录作为root目录,这个目录下的所有子目录都会被扫描

@ComponentScan

定义扫描的路径,并从中找出标识了需要装配的类,将其自动装配到Spring的Bean容器中

使用场景:如果代码包在使用了@SpringBootApplication注解的Class类所在包或下级包中,那么@SpringBootApplication中整合的@ComponentScan注解会自动扫描这些包,并注入到容器中;如果有一些代码包不在,那么就可以使用@ComponentScan注解手动添加指定

@Component

把普通的pojo实例化到Spring容器中,相当于配置文件中的:

@Controller

标识该类是一个Spring MVC控制器,负责处理HTTP请求

@Controller是@Component的派生,如果不使用SpringMVC,@Controller和@Comonent是没有区别的,但是使用了SpringMVC,@Controller就会有另外一个作用:Spring会过滤出添加了@Controller注解的Bean,将添加了@RequestMapping注解的方法解析出来,在后续HTTP请求到达的时候会从解析出来的方法中进行匹配

@RestController

使用@Controller返回JSON数据需要和@ResponseBody配合使用,@RestController可以替代@Controller + @ResponseBody,默认返回JSON格式的数据

@Service

标识该类是一个业务逻辑层组件,负责处理逻辑业务

@Repository

标识该类是一个数据访问层组件,负责处理数据层交互

该注解能将所标注的类中抛出的数据访问异常封装为Spring的数据访问异常类型,用于封装不同持久层框架抛出的异常,使得异常独立于底层框架

@Configuration

标识该类是一个配置类

可以替换XML配置文件

被注解的类中可以有一个或多个@Bean方法声明Bean

可以被AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext进行扫描,用于构建Bean定义以及初始化Spring容器

@Bean

用于注释方法,表示该方法返回的Bean会被放到Spring容器中,是一个方法级别的注解

主要用在@Configuration注解的类里,也可以用在@Component注解的类里,添加Bean的id为方法名

默认情况下Bean的名称和方法名称相同,为了避免重复可以使用name属性来指定

Bean的命名还支持别名

@Autowired

用来装配Bean,可以写在字段上或方法上

默认情况下要求依赖对象必须存在,如果要允许null值,可以设置该注解的required属性为false,如:@Autowired(required=false)

写在字段上虽然简单方便,但是在代码拓展中可能会违背单一职责原则,或者导致单元测试构建对象麻烦,所以建议使用构造器创建的方式来装配bean

@Autowired只根据type进行注入,不会去匹配name,如果涉及到type无法辨别注入对象时,需要依赖@Aualifier或@Primary注解一起来修饰

@Resource

@Resource是Java的注解,有两个重要属性:name和type,使用name属性则使用byName的自动注入策略,解析Bean的名字;使用type则使用byType的自动注入策略,解析Bean的类型。默认是按byName的方式注入。

@Primary

当有多个相同的Bean时,使用该注解来赋予Bean更高的优先级

使用场景:现有一个登录接口ILoginService,有以下两种实现类:

  • WeChartLoginService:实现了微信登录
  • UsernameLoginService:实现了用户名登录

因为他们都继承自ILoginService,所以他们注册的Bean是相同的,在启动时Spring不知道注入哪个实现类导致报错,所以可以使用@Primary注解修饰其中一个实现类,让其优先注入,或者使用@Qualifier

@Qualifier

通过@Qualifier注解我们可以指定特定的SpringBean名称进行装配,这样Spring就可以在多个相同类型的Bean中找到正确的那个,前提是我们要在@Bean注解中声明value属性以确定名称

@ResponseBody

将java对象转成JSON格式,用于将Controller方法返回给前端的对象转换成JSON格式,写入到Response的Body区中

@RequestBody

接收从前端传递给后端的JSON格式数据(即请求体)后,将该数据转换成Java对象

@PathVariable

用于接收请求路径中占位符的值,将URL中格式为{xxx}的占位符绑定到操作方法的入参中

@RequestParam

把请求参数绑定到控制器的方法参数上,请求参数的格式为:url?name=cc&age=20

@RequestMapping

将HTTP请求映射到指定的控制器方法上,可以声明该HTTP请求的接受类型,如GET、POST等

@GetMapping

@RequestMapping的延伸,声明该@RequestMapping接收GET类型的HTTP请求,好处是更加直观,下面两种写法是等效的:

@RequestMapping(value = "/index", method = RequestMethod.GET)
​
@GetMapping("/index")

@PostMapping

同上@GetMapping

@ModelAttribute

可以用在方法上、方法参数上

  • 用在方法上:被@ModelAttribute注解的方法会在Controller每个方法执行前执行一次
  • 用在方法参数上:可以将所有名称匹配的参数绑定并添加到模型对象上,比起@RequestParam只能绑定单个对象会方便一些

@Transactional

声明式事务管理注解,用在接口实现类或接口方法上,对public的方法起作用,且应该只被添加到public方法上

@Cacheable

用在方法上,表示该方法的返回结果是可以缓存的,就是说该方法的返回结果会被放在缓存中,便于以后使用相同参数调用该方法时,会先去缓存中找数据,而不会实际执行该方法

@CacheEvict

用来清除缓存,有以下属性:

  • value:缓存位置名称,不能为空
  • key:缓存的key,默认为空
  • condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
  • allEntires:true表示清除value中的全部缓存,默认为false

@CachePut

被注解的方法总是会执行,然后尝试将结果放入缓存。和@Cacheable不同的时,被@Cacheable注解修饰的方法,会先去缓存中查找数据,找不到才执行方法

简单说,@Cacheable适合查询数据方法,@CachePut适合更新数据方法

@Aspect

作用是把当前类标识为一个切面类,供容器读取

@Before

前置通知,在切面方法执行之前执行

@After

返回通知,在切面方法执行之后执行

声明一个切入点

@Around

环绕通知,围绕着方法执行

@AfterReturning

返回通知,在切面方法返回结果之后执行,将返回的结果传入进来

不能和@Around一起使用,否则会导致@AfterReturning获取不到值

@AfterThrowing

异常通知,在切面方法抛出异常后执行

@PointCut

切点表达式

@Value

注入配置文件中的属性值,语法是:

@Value(value = "${user.name}")

配置文件中的属性名用驼峰或者小写字母加中划线的组合,都可以被识别出来

可以设置默认值,在属性名后加冒号【:】,如:

@Value(value = "${user.name:chen}")

关于static变量,被static修饰的变量用@Value注入会失败,所以注入静态变量的话要把@Value用到该静态变量的setter方法上

除了用${}的写法,还可以用#{},该语法主要用于Spring的EL表达式

@ConfigurationProperties

和@Value一样注入配置文件中的属性值,但通常比@Value更好用

在SpringBoot中,推荐使用@ConfigurationProperties

  • 该注解支持属性文件和JavaBean的映射,而@Value支持SpEL表达式
  • 如果是多个属性映射,推荐使用@ConfigurationProperties,读取单个属性可以用@Value

@PropertySource

加载指定的属性文件(.properties、.yml)到Spring的Environment中,可以配置@Value和@ConfigurationProperties使用

使用场景:当所有属性值都配置在主配置文件中,主配置文件就会越来越大,此时可以使用@PropertySource注解,去加载指定的配置文件,结合@ConfigurationProperties,实现指定配置文件与JavaBean的注入操作

@PostConstruct

是Java自带的注解,在方法上使用,可以在依赖加载后,对象使用前执行,而且只执行一次

@PreDestroy

同@PostConstruct,在方法上使用,不同的是在对象销毁之前执行

@Profile

指定组件在哪个环境下才能被注册到容器中,不指定,任何环境都能注册这个组件

使用场景:Swagger接口文档只在开发环境中使用,生产环境中不需要,就可以用这个注解指定

  • 使用该注解修饰的Bean,只有这个环境被激活的时候才能被注册到容器中,默认是default环境
  • 用在配置类上,只有是指定的环境,整个配置类里面的所有配置才会开始生效

@Async

用于异步调用,在方法上使用该注解,Spring会从线程池中获取一个新的线程来执行方法,实现异步调用

@EnableAsync

表示开启对异步任务的支持,可以放在SpringBoot的启动类上,也可以放在自定义线程池的配置类上

最简单的使用:

在SpringBoot启动类上添加@EnableAsync,然后在Service层方法上对于需要使用异步调用的方法加上@Async,那么当Controller层调用该方法的时候,就会在主线程外自动新建线程执行该方法

@Scheduled

标记该方法是一个计划任务,控制某个任务在指定的时间执行,或者每隔一段时间执行,需要配合@EnableScheduling使用,@Scheduled有三种配置时间的方式:

  1. cron
  2. fixedRate
  3. fixedDelay

@EnableScheduling

在配置类上使用,表示开启计划的支持

@Enable*

一般用于开启某个功能,类似一个开关,只有加了这个注解,才能使用某个功能

在自定义@EnableXXX注解时通常结合@Import来使用

@Import

该注解作用是导入一些特定的配置类,这些特定类包括下面三种:

  • @Configuration注解的类

    如@SchedulingConfiguration,他注册了一个ScheduledAnnotationBeanPostProcessor的Bean,这个Processor会在Bean初始化后,容器接管前实现自己的逻辑,所以在Bean初始化后,AnnotatedElementUtils.getMergedRepeatableAnnotations会去拿到当前Bean有@Scheduled和@Schedules注解的方法,有的话将其注册到内部的ScheduledTaskRegistrar变量中,开启定时任务并执行

    所以,这种方法适用于初始化时便获取到所有想要的信息

  • 实现ImportSelector接口的类

    @EnableAsync类,通过导入AsyncConfigurationSelector类来开启异步支持,和@Configuration类型不同,selectImports接口可以根据条件指定需要导入的类

  • 实现ImportBeanDefinitionRegistrar接口的类

    @AspectJAutoProxyRegistrar,查看源码可以看到,比起上面两种方式,该方式更加精细,需要自己去实现Bean的注册逻辑

优缺点,以上三种的优缺点如下:

  • @Configuration需要手动判断如何导入
  • SelectorImports封装较好,可根据选择导入,尤其当选择的条件是AdviceMode,可以选择AdviceModeSelector,几行代码就搞定
  • ImportBeanDefinitionRegistrar最麻烦也最灵活,一些逻辑可以自己写

参考资料:

www.cnblogs.com/xfeiyun/p/1…

www.jianshu.com/p/ac22c39a8…

@RunWith

@RunWith就算一个运行器

@RunWith(JUnit4.class)就是用JUnit4来运行

@RunWith(SpringJUnit4ClassRunner.class)让测试运行于Spring测试环境

@RunWith(Suite.class)的话就是一套测试集合

@RunWith(SpringRunner.class)的SpringRunner继承了SpringJUnit4ClassRunner,没有任何拓展功能,只是名字简短而已

@ActiveProfiles

是一个类注解,在Spring单元测试加载ApplicationContext时指定Profiles

有以下参数:

  • profiles:指定配置文件
  • resolver:指定ActiveProfilesResolver,通过代码指定配置文件
  • value:profiles的别名
  • inheritProfiles:配置文件是否继承父类,默认为true

@ContextConfiguration

通常和@RunWith注解结合使用用来测试

当我们想要在某个测试类中使用@Autowired注解来注入Bean时,只需要给这个测试类添加@ContextConfiguration注解来标注我们想要获取的Bean

@EnableWebMvc

使用该注解后,配置一个继承于WebMvcConfigurerAdapter的配置类即可配置好SpringWebMvc

@ExceptionHandler

异常拦截器,统一拦截异常

用在方法上,指定要拦截的异常,当指定异常出现时执行,通常结合@ControllerAdvice使用

@ControllerAdvice

可以实现全局异常处理,只需要定义类,并添加该注解即可,结合@ExceptionHandler注解来指定异常的处理类型

@RestControllerAdvice

等于@ControllerAdvice + @ResponseBody,处理异常后返回JSON数据到前端

@Conditional

根据条件装配,就是要满足指定的条件后才进行组件注入

@ConditionalOnBean

表示当容器中存在某组件时才注入

@ConditionalOnMissingBean

表示当容器中不存在某组件时才注入

@ConditionalOnClass

表示当容器中存在该类时注入

@ConditionalOnMissingClass

表示当容器中不存在该类时注入

@ConditionalOnExpression

如果有更复杂的多个配置属性一起判断,就用该注解写表达式,如:

@ConditionOnExpression("${mybean.enabled:true} and ${otherbean.enabled:true}")

@ConditionalOnJava

只有运行指定版本的Java才会注入

@ConditionalOnJndi

只有指定的资源通过JNDI加载后才注入

@ConditionalOnWebApplication

只有在运行Web应用时才注入

@ConditionalOnNotWebApplication

只有在非Web环境下才注入

@ConditionalOnResource

当指定的资源存在于classpath中时才注入