Annotation 简介
注解(Annotation)是 JDK 1.5的时候引入的,又称 Java 标注。表现形式为 @xxx 。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
内置注解
Java 内置了一套注解,一共是十个(说七个的是生活在古代吧,都 9012 年了,截止到2019-11-08)。分别在 Java.lang 中和 Java.lang.annotation 中,作用在代码和其他注解的注解(元注解)。
如下列表
注解 | 使用说明 |
---|---|
@Override | 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。 |
@Deprecated | 标记过时已被弃用的方法。如果使用该方法,会报编译警告。具体表现为方法名中间有一道横线(删除线)。 |
@SuppressWarnings | 指示编译器去忽略注解中声明的警告。如 @SuppressWarnings(“all”),all 代表忽略所有。 |
@Retention | 标识这个注解怎么保存,是只在代码中,还是在运行时可以通过反射访问。 |
@Documented | 标记这些注解是否包含在用户文档中。 |
@Target | 标记这个注解应该是哪种 Java 成员。 |
@Inherited | 标记这个注解是继承于哪个注解类(默认没有继承于任何子类)。 |
@SafeVarargs | 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 |
@FunctionalInterface | 标识一个匿名函数或函数式接口。 |
@Repeatable | 标识某注解可以在同一个声明上使用多次。 |
那么注解到底是什么东西呢?底层源码是怎么样的?我们一起来看一下
随便找一个注解,比如 Spring 的 @SpringBootApplication
CTRL+B(博主用的IDEA,使用Eclipse系列的童鞋请按下CTRL后鼠标点击注解) 查看一下源码:
揭开注解神秘的面纱,实际上就是一个 interface ,只是在定义的时候加了个 @ 而已,那么我们是否可以自己写一个注解呢?
答案是肯定的,比如业务中有很多接口需要前端传递 ID,后端根据ID查询数据返回给前端,那么前端就有可能传递 空(null)或空字符串 “” ,拿着一个 NULL 去查库肯定是不行的,轻则查不出数据,重则就是NPE。甚至抛个异常出来给你看看都有可能。
为了避免这种情况肯定需要写 if 语句,if 语句都是重复性的毫无技术含量的代码,而且每个接口都写的话的确挺烦的。
接下来我就写一个 可以判断接口入参是否为空的情况,只需要在接口参数列表中 用我们自己定义的注解即可。再也不用写很多很烦的 if 语句了。
编写自定义接口参数检查注解
在此案例中我采用的是 Spring 的框架。新建一个项目,maven依赖项如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
</dependencies>
新建一个 接口 ,然后在 interface 前面加上 @
/**
* @Author: 马旭辉
* @Date: 2019/11/8 18:44
* @Describe: 被注解的参数不能为空
*/
// 表明此注解是参数级别的,可以作用到方法参数中
@Target({ElementType.PARAMETER})
// 注解保留到运行阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamNotNull {
}
这样自定义注解就写完了,但是我们没有实现任何的功能,目前仅仅是一个空壳。
编写一个 Spring 的参数校验拦截器 继承 HandlerInterceptorAdapter
类
/**
* @Author: 马旭辉
* @Date: 2019/11/8 18:49
* @Describe: 参数检查注解拦截器
*/
public class CheckParamsInterceptor extends HandlerInterceptorAdapter {
private static final Log LOG = LogFactory.getLog(CheckParamsInterceptor.class);
/**
* 获取方法的参数名
* @param method 方法
* @return list
*/
private List<String> getParamsName(HandlerMethod method) {
Parameter[] parameters = method.getMethod().getParameters();
List<String> list = new ArrayList<>(2);
for (Parameter parameter : parameters) {
// 判断参数是否被加了 @ParamNotNull 注解
if (parameter.isAnnotationPresent(ParamNotNull.class)) {
list.add(parameter.getName());
}
}
return list;
}
}
然后再重写 preHandle 方法:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
LOG.warn("UnSupport handler");
return true;
}
List<String> list = getParamsName((HandlerMethod) handler);
for (String s : list) {
String parameter = request.getParameter(s);
if (parameter == null || "".equals(parameter.trim())) {
// 自定义返回JSON数据
JSONObject jsonObject = new JSONObject();
Map<String, Object> errorMap = new HashMap<>(2);
errorMap.put("code", 40404);
errorMap.put("message", "缺少必要的 [" + s + "] 值");
jsonObject.put("status", 1);
jsonObject.put("data", "null");
jsonObject.put("error", errorMap);
response.setHeader("Content-type", "application/json;charset=UTF-8");
// 跨域
response.setHeader("Access-Control-Allow-Origin", "*");
try {
response.getWriter().write(jsonObject.toString());
return false;
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
最后再配置一下
Spring Boot无需写 XML 可使用纯 Java 式的配置,新建一个类 WebMvcConfig
继承 WebMvcConfigurer
类。
/**
* @Author: 马旭辉
* @Date: 2019/11/8 20:40
* @Describe: mvc配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
CheckParamsInterceptor checkParamsInterceptor = new CheckParamsInterceptor();
/**
* 增加校验拦截器
*
* @param registry 注册
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 可指定具体路径
registry.addInterceptor(checkParamsInterceptor).addPathPatterns("/**");
}
}
测试自定义注解
编写一个接口,测试一下注解的作用,如下代码:使用我们自定义的注解 @ParamNotNull 注解 userId 参数, 如果不传递 userId 或者传递为空字符串/空格,肯定是不合格的,不会返回 “Hello B3log!”,而是返回我们拦截器中定义的 Json 。
/**
* @Author: 马旭辉
* @Date: 2019/11/8 19:39
* @Describe: 测试Api
*/
@RestController
@RequestMapping("/api/test/")
public class DemoApi {
@GetMapping("getUserName")
public String getUserName(@ParamNotNull String userId) {
return "Hello B3log!";
}
}
访问 :http://localhost:8080/api/test/getUserName
不带任何参数,返回结果如下:
当然如果我们传递了一个正确的 userId 那么就可以正常进入我们的业务逻辑,返回 “Hello B3log!”。
由此可见,使用自定义注解可以帮我们省去大量的重复代码工作,提高工作效率,不必再进行很多重复性的工作。祝大家工作顺利!
关注微信公众号"程序员小辉"