在接口的开发中,我们有时会想让某个接口只可以被特定的人(来源)请求,那么就需要在服务端对请求参数做校验.
这种情况我们可以使用interceptor
来统一进行参数校验,但是如果很多个接口,有不同的的设定值,我们总不能写很多个interceptor
,然后按照patn逐一添加吧?
面对这种情况,我们可以选择自定义一个注解,由注解来告诉我们,这个接口允许的访问者是谁.
注:在本文的示例中,仅实现了对某一个字段的校验,安全性并不高,实际项目中,可以采用多字段加密的方式,来保证安全性,原理和文中是一样的.
Java Annotation是JDK5.0引入的一种注释机制。
Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。
通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。
Annotation可以像修饰符一样被使用,可以用于package、class、interface、constructor、method、member variable(成员变量)、parameter、local variable(局部变量)、annotation(注解),jdk 1.8之后,只要出现类型(包括类、接口、注解、枚举)的地方都可以使用注解了。
我们可以使用JDK以及其它框架提供的Annotation,也可以自定义Annotation。
元注解是什么呢?在我的理解里,元注解是java官方提供的,用于修饰其他注解的几个属性.
因为开放了自定义注解,所以所有的注解必须有章可循,他们的一些属性必须要被定义.比如:这个注解用在什么地方?类上还是方法上还是字段上?这个注解的生命周期是什么?是保留在源码里供人阅读就好,还是会生成在class文件中,对程序产生实际的作用?这些都需要被提前定义好,因此就有了:
这四个元注解@Target、@Retation、@Inherited、@Documented.
接下来对四个元注解逐一说明
用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
他的取值范围JDK定义了枚举类ElementType
,他的值共有以下几种:
注:在JDK1.8,新加了两种类型,
8. TYPE_PARAMETER:表示这个 Annotation 可以用在 Type 的声明式前,
9. TYPE_USE 表示这个 Annotation 可以用在所有使用 Type 的地方
表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
他的取值范围来自于枚举类RetentionPolicy
,取值共以下几种:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
常用的第三方框架实现了非常多的注解,比如Mybatis
的Param
,Spring
的Component
,Service
,fastjson
的JSONfield
等等.
具体的实现方法这里不多解释了,有兴趣的朋友可以取看一下fastjson
的源码,该项目相比spring
等框架,简单一些也更容易理解.
看到这种注解或简单或复杂的功能之后,我们是否也可以自己来动手实现一个呢?
首先我们来定义注解:
package com.huyan.demo.config;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* created by huyanshi on 2019/1/20
*/
@Target(ElementType.METHOD) // 该注解使用在方法上
@Retention(RetentionPolicy.RUNTIME) //运行时注解
@Documented
public @interface CheckSource {
//该注解的参数,是一个string数组
String[] sources() default {"all"};
}
我们需要的注解用于校验参数,因此它的使用范围是方法
,生命周期是运行时保留
.此外,注解有一个类型为string数组
的参数,用来表示当前方法允许的source列表.
其实一开始我在这里纠结了许久,因为我不能理解一个注解应该在哪里以什么方式调用
.
按照我的思路,每个注解应该有一个字段(或者类似的东西),来指示应该去哪里
调用这个注解的真正使用.
后来经过细细思考,发现这是不现实的,因为注解的作用完全没有规律可言,你可以实现任何你想要的功能,返回值可以使任意值,里面的逻辑也是任意的.
那么就意味着,你需要为你的注解负责,否则他没有任何作用.也就是说,你需要为自己的注解编写注解解析器
,来定义什么时候用到这个注解,用它干什么
?
@纯个人观点,慎看
经过在网上冲浪,我发现注解解析器的主要形式
有三种:
这种方式比较方便,可以直接拦截所有的请求,检查该请求进入的类及方法上有没有特定的注解,如果有怎么怎么操作一波.
但是局限性比较大,我们又不是只在controller里面会用到注解.
这种方式也比较方便,扩展性比较好,当你需要在新的地方用到该注解,新增一个切点就好.
这种是大部分人喜闻乐见的(其实最喜闻乐见的是每次用到就写一遍呗),但是如果不经常重构一下代码,你会发现导出充满了你对某一个注解的使用代码,那就很崩溃了,你需要尽量将其封装一下,放在统一的工具类,每次需要的时候调用即可.
@个人观点结束!
由于我们这次的需求是拦截不合法的请求,所以当然是第一种方式比较靠谱,因此我们写了一个拦截器:
package com.huyan.demo.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* created by huyanshi on 2019/1/20
*/
public class CheckSourceInterceptor extends HandlerInterceptorAdapter {
private static Logger LOG = LoggerFactory.getLogger(CheckSourceInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (!(handler instanceof HandlerMethod)) {
LOG.warn("UnSupport handler");
throw new IllegalArgumentException("Interceptor only supports HandlerMethod handler");
}
//拿到请求参数里面的source参数
String source = request.getParameter("source");
String errorMsg = null;
//如果source为空,返回错误
if (null == source || "".equals(source)) {
errorMsg = "No source in params";
}
if (errorMsg != null) {
response.setStatus(500);
LOG.info(errorMsg);
response.getWriter().write(errorMsg);
return false;
}
//拿到该方法上的注解对象
CheckSource checkSource = getCheckSource((HandlerMethod) handler);
//如果拿到的对象为空,说明没有此注解,直接放行
if (checkSource != null) {
//拿到注解对象的属性,即允许通行的source列表
String[] sources = checkSource.sources();
if (sources.length == 0 || sources[0].equals("all")) {
//列表为空或者为默认值,放行
return true;
}
//遍历列表,如果传入的参数在其中,则放行
for (String s : sources) {
if (s.equals(source)) {
return true;
}
}
//如果传入的source参数不在允许的参数列表中,则拦截请求,并返回错误信息
errorMsg = "source is not support";
response.getWriter().write(errorMsg);
return false;
}
return true;
}
/**
* 拿到该方法上的checksource注解对象
*/
private CheckSource getCheckSource(HandlerMethod handlerMethod) {
if (handlerMethod.getBeanType().isAnnotationPresent(CheckSource.class)) {
return handlerMethod.getBeanType().getAnnotation(CheckSource.class);
} else if (handlerMethod.getMethod().isAnnotationPresent(CheckSource.class)) {
return handlerMethod.getMethod().getAnnotation(CheckSource.class);
}
return null;
}
}
代码中添加了比较详细的注释,这里只写一下大概的思路:
通过拦截器的机制,拿到该方法上的CheckSource
对象,该对象可能为空,不为空的时候拿到它的sources
属性,之后依次遍历,判断传入的source是否在允许的列表中.
在这个拦截器中,我们定义了:
在我们配置的,使用这个拦截器的时候,进入controller层的某一个方法时.
拿传入的source
参数和这个注解的属性sources
列表一一匹配,有匹配上的则允许请求,无匹配值则返回错误信息.
status
接口package com.huyan.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* created by huyanshi on 2019/1/20
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
CheckSourceInterceptor checkSourceInterceptor = new CheckSourceInterceptor();
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(checkSourceInterceptor).addPathPatterns("/status");
}
}
package com.huyan.demo.controller;
import com.huyan.demo.config.CheckSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* created by pfliu on 2018/9/2
*/
@RestController
public class StatusController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@CheckSource(sources = {"huyan", "huihui"})
@GetMapping(value = "/status")
public Object status(@RequestParam("source") String source) {
return "哈哈哈";
}
}
好,编码全部完成了.
启动项目,看一下结果.
source
参数source
参数source
参数java的注解机制并不算太难理解,但是重点是,我们日常中很难想到去应用他,一来是因为我们对其不够熟悉,二来是我们的业务
,没有那么通用的逻辑.
注解机制被大量的使用在各种框架中,足以证明他是一种优秀的机制,值得我们去学习并努力的应用在自己的工作中.
https://josh-persistence.iteye.com/blog/2226493
https://www.ibm.com/developerworks/cn/java/j-lo-java8annotation/index.html
完。
以上皆为个人所思所得,如有错误欢迎评论区指正。
欢迎转载,烦请署名并保留原文链接。
联系邮箱:[email protected]
更多学习笔记见个人博客------>呼延十