手把手教你在 Java 中自定义 Annotation

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后鼠标点击注解) 查看一下源码:

image.png

揭开注解神秘的面纱,实际上就是一个 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
不带任何参数,返回结果如下:

image.png

当然如果我们传递了一个正确的 userId 那么就可以正常进入我们的业务逻辑,返回 “Hello B3log!”。

image.png

由此可见,使用自定义注解可以帮我们省去大量的重复代码工作,提高工作效率,不必再进行很多重复性的工作。祝大家工作顺利!

关注微信公众号"程序员小辉"
微信图片20190813101011.jpg

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值