java自定义注解的两种方式

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 是传的注解参数
②写注解(与第一步无顺序要求)

@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的使用封装前文有讲,不再赘述。

扫描二维码关注公众号,回复: 10726744 查看本文章
发布了67 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/m0_37635053/article/details/105416102