问题
什么是注解,它用来做什么事情,解决什么问题
注解就是一个标记,它通过元注解进行修饰,可以传入一些值和设定注解策略,不同策略的注解可以解决不同的事情:
- SOURCE策略的注解只在编写.java文件中有效,最常见的就是@Override注解,在开发者重写父类方法有误时,在编写阶段就能提示异常
- CLASS策略的注解在编译成.class文件中仍存在,但是JVM加载的时候就会被遗弃
- 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)
定义这个注解能保留多久,一共有三种策略:
-
SOURCE
注解只保留在源文件中,当java文件编译成class文件时,注解被遗弃,被编译器忽略
-
CLASS
注解保留到class文件,但JVM加载class文件的时候被遗弃,这是默认的生命周期
-
RUNTIME
注解不仅保存到class文件中,JVM加载class文件后仍然存在
问题
这三种策略的应用场景有哪些?
-
RESOURCE
- Lombok插件的@Data等注解用的就是Resource,在编译成class文件时,根据@Data等注解自动生成代码
- @Override标注后去重写父类方法,编译器会去检查该方法的正确性(即该方法必须在父类中存在),有错误时进行提示,如果不加并且重写错了重写方法,程序不会提示报错,导致潜在的bug
- @Deprecated:标记该类、方法或属性已被弃用,不建议使用,但是目前还可以使用,在IDEA中被该注解标记的对象会有一条横线作为提示
-
CLASS
- MapStruct映射工具用的@Mapper注解是CLASS,该注解会在.class文件中看到
-
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有三种配置时间的方式:
- cron
- fixedRate
- 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最麻烦也最灵活,一些逻辑可以自己写
参考资料:
@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中时才注入