@[TOC]
Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。 本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。
# 1 什么是注解(Annotation)
Java注解是什么,以下是引用自维基百科的内容
> java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。
# 2 元注解
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。
## 2.1 @Target
最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:
- 类或接口:ElementType.TYPE;
- 字段:ElementType.FIELD;
- 方法:ElementType.METHOD;
- 构造方法:ElementType.CONSTRUCTOR;
- 方法参数:ElementType.PARAMETER。
## 2.2 @Retention
另一个重要的元注解@Retention定义了Annotation的生命周期:
- 仅编译期:RetentionPolicy.SOURCE;
- 仅class文件:RetentionPolicy.CLASS;
- 运行期:RetentionPolicy.RUNTIME。
## 2.3 @Repeatable
使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。
## 2.4 @Inherited
使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:
```java
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
```
在使用的时候,如果一个类用到了@Report:
```java
@Report(type=1)
public class Person {
}
```
则它的子类默认也定义了该注解:
```java
public class Student extends Person {
}
```
# 示例1 反射获取注解
假设我们要自定义一个注解,并用在字段上,可以通过反射获取到,用来描述字段的长度和作用。
```java
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
String description();
int length();
}
```
通过反射获取注解
```java
public class MyFieldTest {
//使用我们的自定义注解
@MyField(description = "用户名", length = 12)
private String username;
@Test
public void testMyField(){
// 获取类模板
Class c = MyFieldTest.class;
// 获取所有字段
for(Field f : c.getDeclaredFields()){
// 判断这个字段是否有MyField注解
if(f.isAnnotationPresent(MyField.class)){
MyField annotation = f.getAnnotation(MyField.class);
System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
}
}
}
}
```
## 示例2:自定义注解+拦截器 实现登录校验
接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。 首先定义一个LoginRequired注解
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
```
然后写两个简单的接口,访问sourceA,sourceB资源
```java
@RestController
public class IndexController {
@GetMapping("/sourceA")
public String sourceA(){
return "你正在访问sourceA资源";
}
@GetMapping("/sourceB")
public String sourceB(){
return "你正在访问sourceB资源";
}
}
```
没添加拦截器之前成功访问
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222411395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxODk4NjQ3,size_16,color_FFFFFF,t_70)实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:
```java
public class SourceAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
```
实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中
```java
@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
}
}
```
拦截成功如下
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222559331.png)
在sourceB方法上添加我们的登录注解@LoginRequired
```java
@RestController
public class IndexController {
@GetMapping("/sourceA")
public String sourceA(){
return "你正在访问sourceA资源";
}
@LoginRequired
@GetMapping("/sourceB")
public String sourceB(){
return "你正在访问sourceB资源";
}
}
```
简单实现登录拦截逻辑
```java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器了");
// 反射获取方法上的LoginRequred注解
HandlerMethod handlerMethod = (HandlerMethod)handler;
LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
if(loginRequired == null){
return true;
}
// 有LoginRequired注解说明需要登录,提示用户登录
response.setContentType("application/json; charset=utf-8");
response.getWriter().print("你访问的资源需要登录");
return false;
}
```
运行成功,访问sourceB时需要登录了,访问sourceA则不用登录.
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20210414222753521.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxODk4NjQ3,size_16,color_FFFFFF,t_70)
Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。 本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。
1 什么是注解(Annotation)
Java注解是什么,以下是引用自维基百科的内容
java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。
2 元注解
有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。
2.1 @Target
最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置:
类或接口:ElementType.TYPE;
字段:ElementType.FIELD;
方法:ElementType.METHOD;
构造方法:ElementType.CONSTRUCTOR;
方法参数:ElementType.PARAMETER。
2.2 @Retention
另一个重要的元注解@Retention定义了Annotation的生命周期:
仅编译期:RetentionPolicy.SOURCE;
仅class文件:RetentionPolicy.CLASS;
运行期:RetentionPolicy.RUNTIME。
2.3 @Repeatable
使用@Repeatable这个元注解可以定义Annotation是否可重复。这个注解应用不是特别广泛。
2.4 @Inherited
使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
int type() default 0;
String level() default "info";
String value() default "";
}
在使用的时候,如果一个类用到了@Report:
@Report(type=1)
public class Person {
}
则它的子类默认也定义了该注解:
public class Student extends Person {
}
示例1 反射获取注解
假设我们要自定义一个注解,并用在字段上,可以通过反射获取到,用来描述字段的长度和作用。
@Target(ElementType.FIELD) // 注解用于字段上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时,可通过注解获取
public @interface MyField {
String description();
int length();
}
通过反射获取注解
public class MyFieldTest {
//使用我们的自定义注解
@MyField(description = "用户名", length = 12)
private String username;
@Test
public void testMyField(){
// 获取类模板
Class c = MyFieldTest.class;
// 获取所有字段
for(Field f : c.getDeclaredFields()){
// 判断这个字段是否有MyField注解
if(f.isAnnotationPresent(MyField.class)){
MyField annotation = f.getAnnotation(MyField.class);
System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
}
}
}
}
示例2:自定义注解+拦截器 实现登录校验
接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。 首先定义一个LoginRequired注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}
然后写两个简单的接口,访问sourceA,sourceB资源
@RestController
public class IndexController {
@GetMapping("/sourceA")
public String sourceA(){
return "你正在访问sourceA资源";
}
@GetMapping("/sourceB")
public String sourceB(){
return "你正在访问sourceB资源";
}
}
没添加拦截器之前成功访问
在这里插入图片描述实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:
public class SourceAccessInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器了");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中
@Configuration
public class InterceptorTrainConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
}
}
拦截成功如下
在这里插入图片描述
在sourceB方法上添加我们的登录注解@LoginRequired
@RestController
public class IndexController {
@GetMapping("/sourceA")
public String sourceA(){
return "你正在访问sourceA资源";
}
@LoginRequired
@GetMapping("/sourceB")
public String sourceB(){
return "你正在访问sourceB资源";
}
}
简单实现登录拦截逻辑
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进入拦截器了");
// 反射获取方法上的LoginRequred注解
HandlerMethod handlerMethod = (HandlerMethod)handler;
LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
if(loginRequired == null){
return true;
}
// 有LoginRequired注解说明需要登录,提示用户登录
response.setContentType("application/json; charset=utf-8");
response.getWriter().print("你访问的资源需要登录");
return false;
}
运行成功,访问sourceB时需要登录了,访问sourceA则不用登录.
在这里插入图片描述
发文助手
发文助手会检测您的文章标题、错别字、内容质量,助您提升文章质量。【创作规范】
暂无建议
Markdown 4395 字数 179 行数 当前行 1, 当前列 0HTML 3792 字数 134 段落