java自定义注解的两种方式
自定义注解是公共代码剥离的高度实现,这里提供两种方法自定义注解。拦截器法和切面法。
一、拦截器法
①在controller引入自定义的注解(简单示意)
@RequestMapping("/validate")
@Validation(namelength = 3,age = 18,gender="女")
public String test(@RequestParam Map<String,Object> map){
System.out.println("骑上我心爱的小摩托,再也不会堵车!");
return "ok";
}
这里的@validation是自定义注解
②写自定义注解(与第一步无顺序要求)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Validation {
int namelength();
int age();
String gender();
}
这里的参数就是调用注解时的传参数,可以不写参数,那样的话直接判断请求参数,写了的话方便控制,比如对请求参数的长度要求啊……
③在拦截器里校验参数
@Component
public class MyValidInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
Validation validation = hm.getMethodAnnotation(Validation.class);
if (null == validation) {
return true;
}
//获取自定义注解上的传的参数(没传就不用获取)
int namelength = validation.namelength();
int ageLength = validation.age();
String annogender = validation.gender();
//如果是@RequestParam接收的参数,集合形式的用这种办法获取
Map<String, Object> map = getParameterMap(request);
//如果是@RequestBody接收参数的,对象形式的用这种办法获取,因为要用流的形式(getHeader()或getInputStream())去获取请求参数,而控制层的@RequestBody也是根据流获取对象,而request的流只能被调一次,所以这里接收对象不适合用拦截器
// User u = getRequestBodyParam(request,User.class);
// String name = u.getName();
// int age = u.getAge();
// String gender = u.getGender();
//
String name = null;
if (null != map.get("name")) {
name = (String) map.get("name");
}
;
Integer age = null;
if (null != map.get("age")) {
age = Integer.parseInt((String) map.get("age"));
}
String gender = null;
if (null != map.get("gender")) {
gender = (String) map.get("gender");
}
//这里就方便对请求参数做限制,不同的地方调自定义注解可以传不同的注解参数,那就是实现不同接口对请求参数要求不一致
int paramlength = name.length();
String str = "";
if (namelength > paramlength) {
str = "名字长度不能小于:" + namelength;
render(response, str);
return false;
}
if (age < ageLength) {
str = "年龄不能小于:" + ageLength;
render(response, str);
return false;
}
if (!annogender.equalsIgnoreCase(gender)) {
str = "你的性别不是:" + annogender;
render(response, str);
return false;
}
return true;
}
return true;
}
/**
* 从request中获取@RequestBody传过来的参数
*
* @param request
* @param clazz
* @param <T>
* @return
*/
public <T> T getRequestBodyParam(HttpServletRequest request, Class<T> clazz) {
String body = null;
try {
body =
request.
getReader().
lines().
collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
e.printStackTrace();
}
T myParam = JSON.parseObject(body, clazz);
return myParam;
}
/**
* 从request中获取@RequestParam传过来的参数
*
* @return
* @RequestParam 获取的参数
* 获取所有请求参数,
* 封装为map对象
*/
public Map<String, Object> getParameterMap(HttpServletRequest request) {
if (request == null) {
return null;
}
Enumeration<String> enumeration = request.getParameterNames();
Map<String, Object> parameterMap = new HashMap<String, Object>();
StringBuilder stringBuilder = new StringBuilder();
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getParameter(key);
String keyValue = key + " : " + value + " ; ";
stringBuilder.append(keyValue);
parameterMap.put(key, value);
}
return parameterMap;
}
private void render(HttpServletResponse response, String str) throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
out.write(str.getBytes("UTF-8"));
out.flush();
out.close();
}
}
④注册拦截器到WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
//关键,将拦截器作为bean写入配置中,否则自定义拦截器中无法注入spring管理的bean
@Bean
public MyValidInterceptor myInterceptor(){
return new MyValidInterceptor();
}
// 添加自定义拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor());
}
}
这里就实现了自定义拦截器对参数的校验,而且限制条件可以自己根据不同的需求传不同的注解参数
二、切面法
利用切面做自定义注解(推荐)
①在控制层调用(这里做一个接口限流的通用注解)
@RestController
@RequestMapping("/aspect")
public class AnnotationAspectController {
@RequestMapping("/annotation")
@TimesValidate(seconds = 60,maxCount = 5)
public void test(){
System.out.println("人间值得");
}
}
@TimesValidate(seconds = 60,maxCount = 5)是自定义注解,
seconds 、maxCount 是传的注解参数
②写注解(与第一步无顺序要求)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimesValidate {
int seconds();
int maxCount();
}
③写切面类(这里用到了redis存取数据)
@Component
@Aspect
public class TimesValidateAspect {
@Autowired
protected RedisService redisService;
private static final String SAVESECOND = "TIMES_5SECOND";
@Pointcut("@annotation(com.sinux.icoding.selfannotation.annotation.TimesValidate)")
public void pointcut() {
}
/**
*环绕:在目标类执行前后都会执行这个方法,执行前,
* @around会执行到proceedingJoinPoint.proceed();
*然后线程阻塞
*在目标方法执行完毕、后又接着proceedingJoinPoint.proceed()往下执行。
*他应该保持线程安全
* 注意他应该在不满足条件 return false之后,因为在他之前的话,
* 那不管满足条件与否都会执行目标方法就是去了作用
**/
@Around("pointcut()")
public Object validateTimes(ProceedingJoinPoint joinPoint){
//取参数
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//获取request和response
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
TimesValidate timesValidate = method.getAnnotation(TimesValidate.class);
int seconds = timesValidate.seconds();
int maxcount = timesValidate.maxCount();
boolean flag = checkAccessTimes(maxcount,seconds);
String str = seconds+"秒内,访问接口次数不能超过"+maxcount+"次!";
if(!flag){
render(response,str);
return false;
}
/**
*这里需要注意这个joinPoint.proceed();的位置,他要写在校验不通过的后面,因为执行目标方法前会先执行到这里阻塞,目标方法执行完后继续从这里执行。在目标方法执行前就要给他拦住,所以这个joinPoint.proceed()节点要在拦截之后。
**/
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
public boolean checkAccessTimes(int maxcount,int seconds) {
Integer data = redisService.getValue(SAVESECOND,Integer.class);
Integer times = null;
if (null != data ) {
times = data;
}
if (null == times) {
redisService.setCacheValueForTimes(SAVESECOND,1,seconds,TimeUnit.SECONDS);
long m = redisService.getValue(SAVESECOND,Integer.class);
System.out.println(seconds+"秒内第"+m+"次访问接口!");
} else if (times < maxcount) {
long t = redisService.testInckey(SAVESECOND);
System.out.println(seconds+"秒内第"+t+"次访问接口!");
} else {
return false;
}
return true;
}
private void render(HttpServletResponse response, String str) {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = null;
try {
out = response.getOutputStream();
out.write(str.getBytes("UTF-8"));
out.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这样就实现了目标接口60秒内登陆不超过5次,的实现,在项目中可以获取ip作为redis的key,这样就能限制恶意刷接口第行为
这里关于redis的使用封装前文有讲,不再赘述。