精华内容
下载资源
问答
  • 初学spring的时候使用注解总觉得使用注解很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解。问题来了,自定义注解到底是什么?肯定会有人和我一样有这个疑惑,我根据自己...

    初学spring的时候使用注解总觉得使用注解很神奇,加一个注解就能实现想要的功能,很好奇,也想自己根据需要写一些自己实现的自定义注解。问题来了,自定义注解到底是什么?肯定会有人和我一样有这个疑惑,我根据自己的理解总结一下。看完下面的几个使用自定义注解的实战demo,小伙伴大概就懂怎么用了。

    其实注解一点也不神奇,注解就是一种标志,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。就像下面这样,创建一个@interface的注解,然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface XinLinLog {
        String value() default"";
    }
    
    

    一、 java自定义注解的定义、描述

    注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。

    1. 创建一个注解的基本元素

    修饰符
    访问修饰符必须为public,不写默认为pubic;
    关键字
    关键字为@interface;
    注解名称
    注解名称为自定义注解的名称,例如上面的XinLinLog 就是注解名称
    注解类型元素
    注解类型元素是注解中内容,根据需要标志参数,例如上面的注解的value;

    2. 元注解(@Target、@Retention、@Inherited、@Documented)

    我们上面的创建的注解XinLinLog上面还有几个注解(@Target、@Retention、@Inherited、@Documented),这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)

    • @Target:用于描述注解的使用范围,该注解可以使用在什么地方
    Target类型描述
    ElementType.TYPE应用于类、接口(包括注解类型)、枚举
    ElementType.FIELD应用于属性(包括枚举中的常量)
    ElementType.METHOD应用于方法
    ElementType.PARAMETER应用于方法的形参
    ElementType.CONSTRUCTOR应用于构造函数
    ElementType.LOCAL_VARIABLE应用于局部变量
    ElementType.ANNOTATION_TYPE应用于注解类型
    ElementType.PACKAGE应用于包

    备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上,但是我们在这个注解标志在类上,就会报错

    • @Retention:表明该注解的生命周期
    生命周期类型描述
    RetentionPolicy.SOURCE编译时被丢弃,不包含在类文件中
    RetentionPolicy.CLASSJVM加载时被丢弃,包含在类文件中,默认值
    RetentionPolicy.RUNTIME由JVM 加载,包含在类文件中,在运行时可以被获取到
    • @Inherited:是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    • @Documented:表明该注解标记的元素可以被Javadoc 或类似的工具文档化

    二、自定义注解的使用DEMO

    上面总结的注解的定义,但是创建这样一个注解,仅仅是一个标志,装饰类、方法、属性的,并没有功能,要想实现功能,需要我们通过拦截器、AOP切面这些地方获取注解标志,然后实现我们的功能。

    java自定义注解的使用范围
    一般我们可以通过注解来实现一些重复的逻辑,就像封装了的一个方法,可以用在一些权限校验、字段校验、字段属性注入、保存日志、缓存

    1.权限校验注解(校验token)

    有些项目进入到接口后调用公用方法来校验token,这样看起来代码就有点不优雅,我们可以写自定义注解来进行校验token。
    例如有个项目,前端是把token放到json里面传到后端(也有一些项目放到请求头的header里面,方式一样),没用注解之前,我们可能是通过调用公共的方法去校验token,如validateToken(token),然后每个接口都有这一段代码,我们用注解的模式替换
    在这里插入图片描述

    1) 首先我们创建一个注解,标志那些类需要校验token
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AppAuthenticationValidate {
        //必填参数
        String[] requestParams() default {};
    }
    
    
    2) 然后再创建一个AOP切面类来拦截这个注解

    拦截使用这个注解的方法,同时获取注解上面的requestParams参数,校验json里面必填的属性是否存在

    @Aspect
    @Component
    @Slf4j
    public class AppAuthenticationValidateAspect {
    
        @Reference(check = false, timeout = 18000)
        private CommonUserService commonUserService;
    
        @Before("@annotation(cn.com.bluemoon.admin.web.common.aspect.AppAuthenticationValidate)")
        public void repeatSumbitIntercept( JoinPoint joinPoint) {
    
            //获取接口的参数
            Object[] o = joinPoint.getArgs();
            JSONObject jsonObject = null;
            String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
            String source = null;
    
            for(int i=0;i<parameterNames.length;i++){
                String paramName = parameterNames[i];
    
                if(paramName.equals("source")){
                    //获取token来源
                    source = (String)o[i];
                }
                if(paramName.equals("jsonObject")){
                    jsonObject = (JSONObject) o[i];
                }
            }
            if(jsonObject == null){
                throw new WebException(ResponseConstant.ILLEGAL_PARAM_CODE, ResponseConstant.ILLEGAL_PARAM_MSG);
            }
            String token = jsonObject.getString("token");
            if(StringUtils.isBlank(token)){
                throw new WebException(ResponseConstant.TOKEN_EXPIRED_CODE,"登录超时,请重新登录");
            }
    
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            AppAuthenticationValidate annotation = method.getAnnotation(AppAuthenticationValidate.class);
            String[] requestParams = annotation.requestParams();
            //校验必填参数
            ParamsValidateUtil.isNotBlank(jsonObject,requestParams);
            ResponseBean<String> response = null;
            if(StringUtils.isBlank(source)){
                response = this.commonUserService.checkAppToken(token);
            }else{
                response = this.commonUserService.checkAppTokenByAppType(token,source);
            }
    
            if (response.getIsSuccess() && ResponseConstant.REQUEST_SUCCESS_CODE == response.getResponseCode()) {
                String empCode = response.getData();
                log.info("---token ={}, empCode={}--", token, empCode);
                jsonObject.put(ProcessParamConstant.APP_EMP_CODE,empCode);
            } else {
                log.info("---token验证不通过,token ={}---", token);
                throw new WebException(ResponseConstant.TOKEN_EXPIRED_CODE, "登录超时,请重新登录");
            }
        }
    }
    
    3)把注解加在需要校验的接口方法上

    这个注解同时校验了必填字段,校验完token后同时会把token的用户信息加在json对象里面
    在这里插入图片描述

    备注:有些项目会把token放到请求头header中,处理方式类似

    2.角色校验注解(springsecurity中的角色校验)

    我们在使用springsecurity有一个注解@PreAuthorize可以作用在类或方法上,用来校验是否有权限访问,我们可以模仿这个注解,写一个我们自定义注解来实现同样的功能

    1)创建一个自定义注解
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface RoleAuthorize {
        String[] value() default {};
    }
    
    
    2)创建一个拦截器

    这个拦截器拦截所有访问路径的url,如果访问方法上带有我们创建的自定义注解RoleAuthorize ,则获取这个注解上限定的访问角色,方法没有注解再获取这个类是否有这个注解,如果这个类也没有注解,则这个类的访问没有角色限制,放行,如果有则校验当前用户的springsecurity是否有这个角色,有则放行,没有则抛出和springsecurity一样的异常AccessDeniedException,全局异常捕获这个异常,返回状态码403(表示没有权限访问)

    @Component
    public class RoleInterceptor extends HandlerInterceptorAdapter{
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                 Object handler) throws Exception {
            HandlerMethod handlerMethod = (HandlerMethod)handler;
    
            //在方法上寻找注解
            RoleAuthorize permission = handlerMethod.getMethodAnnotation(RoleAuthorize.class);
            if (permission == null) {
                //方法不存在则在类上寻找注解则在类上寻找注解
                permission = handlerMethod.getBeanType().getAnnotation(RoleAuthorize.class);
            }
    
            //如果没有添加权限注解则直接跳过允许访问
            if (permission == null) {
                return true;
            }
    
            //获取注解中的值
            String[] validateRoles = permission.value();
            //校验是否含有对应的角色
            for(String role : validateRoles){
                //从springsecurity的上下文获取用户角色是否存在当前的角色名称
                if(AuthUserUtils.hasRole("ROLE_"+role)){
                    return true;
                }
            }
            throw  new AccessDeniedException("没有权限访问当前接口");
        }
    
    }
    
    3)配置拦截器
    @Configuration
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    
        @Autowired
        private RoleInterceptor roleInterceptor;
    
        /**
         * 添加拦截器
         *
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    
            registry.addInterceptor(roleInterceptor).addPathPatterns("/**");
            super.addInterceptors(registry);
            
        }
    
    }
    

    备注:
    1.这里添加拦截器可以继承WebMvcConfigurerAdapter (已过时,在springboot2.0是继承 WebMvcConfigurationSupport或实现WebMvcConfigurer)
    2.WebMvcConfigurationSupport–>不需要返回逻辑视图,可以选择继承此类.WebMvcCofigurer–>返回逻辑视图,可以选择实现此方法,重写addInterceptor方法
    3.继承webmvcconfigurationsupport之后就没有springmvc的自动配置了 建议实现WebMvcConfigurer

    4)把注解加到接口的类或方法上验证

    可以看到接口会返回无权限访问
    在这里插入图片描述
    在这里插入图片描述

    3.字段属性注入注解

    还有一种场景,我们校验token通过后,还需要通过token去换取用户信息,如果通过接口方法里面去调token换用户信息,好像不太优雅,我们用自定义注解把用户信息直接注入我们接口上的属性参数里面

    1)创建自定义注解
    //作用在参数上
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LoginUserInfo {
    }
    
    
    2)需要写一个实现类实现HandlerMethodArgumentResolver接口

    Spring也向我们提供了多种解析器Resolver,HandlerMethodArgumentResolver是用来处理方法参数的解析器,包含以下2个方法:

    • supportsParameter(满足某种要求,返回true,方可进入resolveArgument做参数处理)
    • resolveArgument(解析返回对象注入我们该注解上的属性)
    public class UserInfoArgumentResolver implements HandlerMethodArgumentResolver {
    
        @Override
        public boolean supportsParameter(MethodParameter methodParameter) {
            return methodParameter.getParameterType().isAssignableFrom(LoginUser.class)
                    && methodParameter.hasParameterAnnotation(LoginUserInfo.class);
        }
    
        @Override
        public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
            //从我们的request域里获取用户信息对象,返回注入属性
            return  nativeWebRequest.getAttribute(Constants.USER_INFN, RequestAttributes.SCOPE_REQUEST);
        }
    
    
    }
    
    3)创建拦截器

    这个拦截器会通过请求头header拿到token,然后再通过token去从缓存或数据库中获取用户信息,最后把用户信息写进request里面

    @Component
    public class AuthenticationInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        UserService userService;
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
            StringBuffer requestURL = request.getRequestURL();
            System.out.println("前置拦截器1 preHandle: 请求的uri为:"+requestURL.toString());
            String token = request.getHeader("token");
    
            //根据token从缓存或数据库中获取用户信息
            LoginUser user = userService.findUserByToken(token);
    
            if(user == null){
                throw new WebException(ResultStatusCode.INVALID_TOKEN);
            }
            request.setAttribute(Constants.USER_INFN,user);
            return true;
        }
    }
    
    //配置拦截器
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Autowired
        AuthenticationInterceptor authenticationInterceptor;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 拦截指定路径
            registry.addInterceptor(authenticationInterceptor).addPathPatterns("/user/check/**");
    
        }
     }
    
    4)把注解加到接口的参数上

    在这里插入图片描述
    执行测试我们就可以知道只要token正确,loginUser就已经注入用户信息了

    4.对象的属性校验注解


    完整描述查看 springboot中参数验证自定义注解,@Valid总结

    validation-api包里面还有一个@Constraint注解,我们的自定义注解里面加上这个注解就能实现自定义验证

    1)创建一个自定义验证注解

    我们这个自定义注解实现一个简单的功能:
    根据type参数进行校验,校验为空、年龄范围、手机号码

    @Constraint(validatedBy = {CheckValidator.class})//指向实现验证的类

    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = {CheckValidator.class})//指向实现验证的类
    public @interface Check {
    
        CheckValidatorEnum type() default CheckValidatorEnum.Null;
    
        long min() default 1;
        long max() default 1;
    
        String message() default "参数异常";
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default { };
    }
    
    //校验枚举类
    @Getter
    public enum  CheckValidatorEnum {
        Null(1,"为空校验"),
        AGE(2,"年龄校验"),
        Phone(3,"手机校验");
    
        CheckValidatorEnum(int code,String desc){
            this.code = code;
            this.desc = desc;
        }
        private int code;
        private String desc;
    }
    
    2)实现验证的类

    我们这个实现自定义验证的类需要实现ConstrainValidator接口

    public class CheckValidator implements ConstraintValidator<Check,Object> {
        
        private Check check;
        private CheckValidatorEnum checkValidatorEnum;
    
        @Override
        public void initialize(Check CheckAnnotation){
            this.check = CheckAnnotation;
            this.checkValidatorEnum = CheckAnnotation.type();
        }
    
        @Override
        public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
            
            if(checkValidatorEnum.getCode()==CheckValidatorEnum.Null.getCode()){
                //非空校验
                if(o != null && !o.toString().equals("")){
                    return true;
                }
            }else if(checkValidatorEnum.getCode()==CheckValidatorEnum.AGE.getCode()){
                //年龄校验
                if(o.toString() == null){
                    return true;
                }
                long min = this.check.min();
                long max = this.check.max();
                Integer age = Integer.parseInt(o.toString());
                if(age>=min && age<=max){
                    return true;
                }
            }else if(checkValidatorEnum.getCode()==CheckValidatorEnum.Phone.getCode()){
                if(o == null){
                    return true;
                }
                //手机号码校验
                if(CommonUtil.isMobile(o.toString())){
                    return true;
                }
            }
            return false;
        }
    }
    
    
    3)使用@Valid注解加在类上校验

    如下 @Valid User user,同时在User里面的属性加入自定义注解

    public ResponseObject addUser(@Valid @RequestBody User user) throws IOException {}
    
    @Data
    public class User {
        private int id;
    
        @Check(type = CheckValidatorEnum.Null)
        private String name;
    
        @Check(type = CheckValidatorEnum.AGE, min = 18,max = 30,message = "年龄不在18-30范围内")
        private Integer age;
    
        @Check(type = CheckValidatorEnum.Phone,message = "手机号码不正确")
        private String tel;
        
        @Valid
        private UserCityInfo cityInfo;
    }
    
    //嵌套对象的校验
    @Data
    public class UserCityInfo {
    
        @Check(type = CheckValidatorEnum.Null,message = "城市编码不能为空")
        private Integer cityCode;
        @NotEmpty
        private String cityName;
    }
    

    备注:
    可能需要校验的对象的属性还是对象,然后我们需要校验对象里面的对象,这种嵌套校验,我们只需要在对象的属性对象上再加一个@Valid即可实现嵌套校验

    4)在全局异常里捕获校验异常
    @Slf4j
    @RestControllerAdvice
    public class GlobalExceptionHandle {
        //捕获@Valid校验不通过的异常
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseObject handleMethodArgumentNotValidException(MethodArgumentNotValidException  e) {
            log.error(e.getMessage(), e);
            BindingResult ex = e.getBindingResult();
            List<ObjectError> allErrors = ex.getAllErrors();
            ObjectError error = allErrors.get(0);
            String defaultMessage = error.getDefaultMessage();
    
            ResponseObject responseObject = new ResponseObject(ResultStatusCode.VALIDATE_FAIL.getStatuscode(),defaultMessage,null);
            return responseObject;
        }
    
        @ExceptionHandler(Exception.class)
        public ResponseObject handleException(Exception e) {
            log.error(e.getMessage(), e);
    
            ResponseObject responseObject = new ResponseObject(ResultStatusCode.SYSTEM_ERROR.getStatuscode(),ResultStatusCode.SYSTEM_ERROR.getStatusmsg(),null);
            return responseObject;
        }
    
    }
    
    
    

    然后我们就实现了在注解里校验,不需要在接口代码里再判断在这里插入图片描述

    5.接口的操作日志注解

    记录接口的操作日志注解

    1)创建自定义注解
    //记录接口的操作日志
    @Target({METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface OperationLog {
    
        String value() default"";
    
        OperationTypeEnum operationTypeEnum() default OperationTypeEnum.OTHER;
    
    }
    
    2)通过AOP切面实现注解功能
    @Aspect
    @Component
    public class LogAspect {
    
        @Autowired
        private ISysLogInfoService sysLogInfoService;
    
        //定义切点 @Pointcut
        //在注解的位置切入代码
        @Pointcut("@annotation(OperationLog)")
        public void logPointCut() {
        }
    
        //切面 配置通知
        @AfterReturning("logPointCut()")
        public void saveSysLog(JoinPoint joinPoint) {
            //保存日志
            SysLogInfo sysLog = new SysLogInfo();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            Operation operation = method.getAnnotation(Operation.class);
            if (operation != null) {
                String value = operation.value();
                //获取请求的类名
                String className = joinPoint.getTarget().getClass().getName();
                //获取请求的方法名
                String methodName = method.getName();
                String methodStr = (className + "." + methodName);
                // 构造参数组集合
                JSONObject allParams = new JSONObject();
    
                for (Object arg : joinPoint.getArgs()) {
                    if (arg instanceof JSONObject) {
                        allParams.putAll((JSONObject) arg);
                    }
                }
    
                //获取用户ip地址
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                String url = request.getRequestURI();
                String ip = IpAddressUtils.getIpAdrress(request);
    
              
                String params = "";
                //请求参数
                if (!allParams.isEmpty()) {
                    params = allParams.toJSONString();
                }
                if(params.length()> 1000){
                    params = params.substring(0,997).concat("...");
                }
                sysLog.setLogDesc(value);
                sysLog.setBizId(bizId);
                sysLog.setOpIp(ip);
                sysLog.setBizParam(params);
                sysLog.setOpMethod(methodStr);
                //获取登录用户信息
                AuthUser user = AuthUserUtils.getCurrentUser();
                sysLog.setBizType(operation.operationTypeEnum().getCode());
                sysLog.setOpUser(user.getName());
                sysLog.setOpUserNo(user.getEmpNo());
                sysLog.setOpTime(new Date());
                //保存日志
                sysLogInfoService.insertSelective(sysLog);
            }
        }
    
    }
    
    3)把注解加在接口方法上

    把注解加在接口的方法上就可以保存进入这个接口请求IP、类型、方法名、入参、用户信息

    6.缓存注解

    spring里面有一个@Cacheable注解,使用这个注解的方法,如果key在缓存中已有,则不再进入方法,直接从缓存获取数据,缓存没有值则进入并且把值放到缓存里面,我们写一个类似的,简单的注解

    备注:使用了这个注解,如果数据有修改,记得清除缓存

    1)创建自定义注解
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CustomCache {
        //缓存前缀
        String prefix() default "";
        //缓存key
        String key() default "";
    
    }
    
    2)然后再创建一个AOP切面类来实现这个注解
    @Aspect
    @Component
    public class CustomCacheAspect {
    
        private static HashMap<String,Object>  cacheMap = new HashMap<>();
    
        @Pointcut("@annotation(CustomCache)")
        public void cache() {
        }
    
        @Around("cache()")
        public Object printLog(ProceedingJoinPoint joinPoint){
    
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取操作
            CustomCache customCache = method.getAnnotation(CustomCache.class);
            String prefix = customCache.prefix();
            if(prefix == null || prefix.equals("")){
                //如果前缀为空,默认使用类型+方法名作为缓存的前缀
                //获取请求的类名
                String className = joinPoint.getTarget().getClass().getName();
                //获取请求的方法名
                String methodName = method.getName();
                prefix = className+"-"+methodName;
            }
            String key = customCache.key();
            if(key == null || key.equals("")){
                //获取接口的参数
                Object[] o = joinPoint.getArgs();
                //如果key为空,默认使用参数名称为id的值作为id
                String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
                for(int i=0;i<parameterNames.length;i++){
                    String paramName = parameterNames[i];
                    if(paramName.equals("id")){
                        key = o[i].toString();
                    }
                }
            }
            String cacheKey = prefix+key;
            Object result = cacheMap.get(cacheKey);
            if(result != null){
                //缓存不为空,直接返回缓存结果
                return result;
            }
            try {
                result = joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            cacheMap.put(cacheKey,result);
            return result;
        }
    }
    
    3)把注解加在查询用户信息的Service上
        @Override
        @CustomCache()
        public User findUser(Integer id) {
            return baseMapper.selectById(id);
        }
    

    测试可以看到只有首次才会进入这个方法查询用户信息,查询出用户信息后再调用这个方法只是从缓存中获取

    7.防刷新注解

    有一些场景,例如申请提交之后几秒内需要防止用户重复提交,我们后端通过注解实现这一功能

    1)创建自定义注解
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AvoidRepeatSubmit {
        /**
         * 指定时间内不可重复提交,单位:s
         *
         * @return
         */
        long timeout() default 3;
    }
    
    
    2)再创建一个AOP切面类来实现这个注解
    @Aspect
    @Component
    @Slf4j
    public class AvoidRepeatSubmitAspect {
        @Autowired
        private RedisRepository redisRepository;
    
        @Before("@annotation(cn.com.bluemoon.admin.web.common.aspect.AvoidRepeatSubmit)")
        public void repeatSumbitIntercept(JoinPoint joinPoint) {
            // ip + 类名 + 方法 + timeout
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            String ip = IpAddressUtils.getIpAdrress(request);
    
            String className = joinPoint.getTarget().getClass().getName();
    
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            String methodName = method.getName();
    
            // 获取配置的过期时间
            AvoidRepeatSubmit annotation = method.getAnnotation(AvoidRepeatSubmit.class);
            long timeout = annotation.timeout();
    
            StringBuilder builder = new StringBuilder();
            builder.append(ip).append(",").append(className).append(",").append(methodName).append(",").append(timeout).append("s");
            String key = builder.toString();
            log.info(" --- >> 防重提交:key -- {}", key);
    
            // 判断是否已经超过重复提交的限制时间
            String value = redisRepository.get(key);
            if (StringUtils.isNotBlank(value)) {
                String messge = MessageFormat.format("请勿在{0}s内重复提交", timeout);
                throw new WebException(messge);
            }
            this.redisRepository.setExpire(key, key, timeout);
        }
    }
    
    



    参考文章

    大白快跑8的《Java实现自定义注解》https://blog.csdn.net/zt15732625878/article/details/100061528

    木叶的《spring 自定义注解(annotation)与 aop获取注解》
    https://segmentfault.com/a/1190000013258647

    展开全文
  • Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义...

    前言

    Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义注解+AOP。

    什么是注解(Annotation)

    Java注解是什么,以下是引用自维基百科的内容

    Java注解又称Java标注,是JDK5.0版本开始支持加入源代码的特殊语法元数据。

    Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。

    注解体系图

    元注解:java.lang.annotation中提供了元注解,可以使用这些注解来定义自己的注解。主要使用的是Target和Retention注解

    在这里插入图片描述
    注解处理类:既然上面定义了注解,那得有办法拿到我们定义的注解啊。java.lang.reflect.AnnotationElement接口则提供了该功能。注解的处理是通过java反射来处理的。

    如下,反射相关的类Class, Method, Field都实现了AnnotationElement接口。

    在这里插入图片描述

    在这里插入图片描述
    因此,只要我们通过反射拿到Class, Method, Field类,就能够通过getAnnotation(Class)拿到我们想要的注解并取值。

    常用元注解

    Target:描述了注解修饰的对象范围,取值在java.lang.annotation.ElementType定义,常用的包括:

    • METHOD:用于描述方法
    • PACKAGE:用于描述包
    • PARAMETER:用于描述方法变量
    • TYPE:用于描述类、接口或enum类型

    Retention: 表示注解保留时间长短。取值在java.lang.annotation.RetentionPolicy中,取值为

    • SOURCE:在源文件中有效,编译过程中会被忽略
    • CLASS:随源文件一起编译在class文件中,运行时忽略
    • RUNTIME:在运行时有效

    只有定义为RetentionPolicy.RUNTIME时,我们才能通过注解反射获取到注解。

    所以,假设我们要自定义一个注解,它用在字段上,并且可以通过反射获取到,功能是用来描述字段的长度和作用。

    @Target(ElementType.FIELD)  //  注解用于字段上
    @Retention(RetentionPolicy.RUNTIME)  // 保留到运行时,可通过注解获取
    public @interface MyField {
        String description();
        int length();
    }
    

    示例-反射获取注解

    先定义一个注解:

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyField {
        String description();
        int length();
    }
    

    通过反射获取注解

    public class MyFieldTest {
    
        //使用我们的自定义注解
        @MyField(description = "用户名", length = 12)
        private String username;
    
        @Test
        public void testMyField(){
    
            // 获取类模板
            Class c = MyFieldTest.class;
    
            // 获取所有字段
            for(Field f : c.getDeclaredFields()){
                // 判断这个字段是否有MyField注解
                if(f.isAnnotationPresent(MyField.class)){
                    MyField annotation = f.getAnnotation(MyField.class);
                    System.out.println("字段:[" + f.getName() + "], 描述:[" + annotation.description() + "], 长度:[" + annotation.length() +"]");
                }
            }
    
        }
    }
    

    运行结果

    在这里插入图片描述

    应用场景一:自定义注解+拦截器 实现登录校验

    接下来,我们使用springboot拦截器实现这样一个功能,如果方法上加了@LoginRequired,则提示用户该接口需要登录才能访问,否则不需要登录。

    首先定义一个LoginRequired注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface LoginRequired {
        
    }
    

    然后写两个简单的接口,访问sourceA,sourceB资源

    @RestController
    public class IndexController {
    
        @GetMapping("/sourceA")
        public String sourceA(){
            return "你正在访问sourceA资源";
        }
    
        @GetMapping("/sourceB")
        public String sourceB(){
            return "你正在访问sourceB资源";
        }
    
    }
    

    没添加拦截器之前成功访问

    在这里插入图片描述

    实现spring的HandlerInterceptor 类先实现拦截器,但不拦截,只是简单打印日志,如下:

    public class SourceAccessInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("进入拦截器了");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    

    实现spring类WebMvcConfigurer,创建配置类把拦截器添加到拦截器链中

    @Configuration
    public class InterceptorTrainConfigurer implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new SourceAccessInterceptor()).addPathPatterns("/**");
        }
    }
    

    拦截成功如下

    在这里插入图片描述

    在sourceB方法上添加我们的登录注解@LoginRequired

    @RestController
    public class IndexController {
    
        @GetMapping("/sourceA")
        public String sourceA(){
            return "你正在访问sourceA资源";
        }
    
        @LoginRequired
        @GetMapping("/sourceB")
        public String sourceB(){
            return "你正在访问sourceB资源";
        }
    
    }
    

    简单实现登录拦截逻辑

    @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("进入拦截器了");
    
            // 反射获取方法上的LoginRequred注解
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            LoginRequired loginRequired = handlerMethod.getMethod().getAnnotation(LoginRequired.class);
            if(loginRequired == null){
                return true;
            }
    
            // 有LoginRequired注解说明需要登录,提示用户登录
            response.setContentType("application/json; charset=utf-8");
            response.getWriter().print("你访问的资源需要登录");
            return false;
        }
    

    运行成功,访问sourceB时需要登录了,访问sourceA则不用登录

    在这里插入图片描述
    在这里插入图片描述

    应用场景二:自定义注解+AOP 实现日志打印

    先导入切面需要的依赖包

    <dependency>
          <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    定义一个注解@MyLog

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyLog {
        
    }
    

    定义一个切面类,见如下代码注释理解:

    @Aspect // 1.表明这是一个切面类
    @Component
    public class MyLogAspect {
    
        // 2. PointCut表示这是一个切点,@annotation表示这个切点切到一个注解上,后面带该注解的全类名
        // 切面最主要的就是切点,所有的故事都围绕切点发生
        // logPointCut()代表切点名称
        @Pointcut("@annotation(me.zebin.demo.annotationdemo.aoplog.MyLog)")
        public void logPointCut(){};
    
        // 3. 环绕通知
        @Around("logPointCut()")
        public void logAround(ProceedingJoinPoint joinPoint){
            // 获取方法名称
            String methodName = joinPoint.getSignature().getName();
            // 获取入参
            Object[] param = joinPoint.getArgs();
    
            StringBuilder sb = new StringBuilder();
            for(Object o : param){
                sb.append(o + "; ");
            }
            System.out.println("进入[" + methodName + "]方法,参数为:" + sb.toString());
    
            // 继续执行方法
            try {
                joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            System.out.println(methodName + "方法执行结束");
    
        }
    }
    

    在步骤二中的IndexController写一个sourceC进行测试,加上我们的自定义注解:

        @MyLog
        @GetMapping("/sourceC/{source_name}")
        public String sourceC(@PathVariable("source_name") String sourceName){
            return "你正在访问sourceC资源";
        }
    

    启动springboot web项目,输入访问地址

    在这里插入图片描述

    写在最后

    自定义注解的用途非常广泛,这里只是列出来两种场景。

    如果想知道更多的应用场景,请关注我吧,下面将继续更新

    展开全文
  • 如何使用自定义注解

    千次阅读 2015-03-15 16:16:47
    Java注解目前广泛被应用。spring中的基于注解的依赖注入、Spring Web MVC中的Route、PlayFramework中的...现在来看看,如何自定义注解。 目标:通过注解方式,定义属性的默认值。例如: [java] view plaincopy

    Java注解目前广泛被应用。spring中的基于注解的依赖注入、Spring Web MVC中的Route、PlayFramework中的基于注解的Validation等。

    使用注解,可以在适当地方替代XML的繁琐。


    现在来看看,如何自定义注解。

    目标:通过注解方式,定义属性的默认值。例如:

    [java]  view plain copy
    1. public class DefaultUse {  
    2.       
    3.     @Default(value = "Hello Annotation")  
    4.     private String msg;  
    5.       
    6.     public void say(){  
    7.         System.out.println(msg);  
    8.     }  
    9. }  


    一、新建Annotation

    [java]  view plain copy
    1. import java.lang.annotation.ElementType;  
    2. import java.lang.annotation.Retention;  
    3. import java.lang.annotation.RetentionPolicy;  
    4. import java.lang.annotation.Target;  
    5.   
    6. /** 
    7.  * @Retention 指定注释的生存时期 
    8.  * CLASS:注释记录在类文件中,但在运行时 VM 不需要保留注释。 
    9.  * RUNTIME:注释记录在类文件中,在运行时 VM 将保留注释,因此可以使用反射机制读取注释内容。 
    10.  * SOURCE:编译器要丢弃的注释。 
    11.  */  
    12. @Retention(RetentionPolicy.RUNTIME)   
    13.   
    14. /** 
    15.  * @Target  
    16.  * 指示注释类型所适用的程序元素的种类,如果注释类型声明中不存在 Target 元注释, 
    17.  * 则声明的类型可以用在任一程序元素上。 
    18.  * ElementType.ANNOTATION_TYPE:注释类型声明 
    19.  * ElementType.CONSTRUCTOR:构造方法声明 
    20.  * ElementType.FILED:字段声明 
    21.  * ElementType.LOCAL_VARIABLE:局部变量声明 
    22.  * ElementType.METHOD:方法声明 
    23.  * ElementType.PACKAGE:包声明 
    24.  * ElementType.PARAMETER:参数声明 
    25.  * ElementType.TYPE:类、借口或枚举声明 
    26.  */  
    27. @Target(ElementType.FIELD)  
    28. public @interface Default {  
    29.     String value(); //默认值  
    30. }  

    二、实际类中使用

    [java]  view plain copy
    1. public class DefaultUse {  
    2.       
    3.     @Default(value = "Hello Annotation")  
    4.     private String msg;  
    5.       
    6.     public void setMsg(String msg) {  
    7.         this.msg = msg;  
    8.     }  
    9.   
    10.     public void say(){  
    11.         System.out.println(msg);  
    12.     }  
    13. }  

    三、注解解析过程

    [java]  view plain copy
    1. import java.beans.PropertyDescriptor;  
    2. import java.lang.reflect.Field;  
    3. import java.lang.reflect.Method;  
    4.   
    5. public class DefaultTest {  
    6.     public static void main(String args[]) throws Exception{  
    7.         DefaultUse use = new DefaultUse();  
    8.           
    9.         //Default注解的处理过程  
    10.         //这里使用反射机制完成默认值的设置  
    11.         Field[] fileds = use.getClass().getDeclaredFields();  
    12.           
    13.         for(Field filed : fileds){  
    14.             Default annotation = filed.getAnnotation(Default.class);  
    15.             if(annotation != null){  
    16.                 PropertyDescriptor pd = new PropertyDescriptor(filed.getName(), DefaultUse.class);  
    17.                 Method setterName = pd.getWriteMethod();  
    18.                   
    19.                 if(setterName!=null){  
    20.                     String value = annotation.value();  
    21.                     filed.setAccessible(true);  
    22.                     setterName.invoke(use, value);  
    23.                 }  
    24.             }  
    25.         }  
    26.           
    27.         use.say();  
    28.     }  
    29. }  
    展开全文
  • 自定义注解

    千次阅读 2020-09-01 16:31:58
    自定义注解 1.@Target ElementType.TYPE 类 接口(包括注解类型)枚举 ElementType.FIELD 属性(包括枚举中的常量) ElementType.METHOD 方法 ElementType.PARAMETER 形参 ElementType.CONSTRUCTOR 构造函数 ...

    自定义注解

    1.@Target

    ElementType.TYPE 类 接口(包括注解类型)枚举

    ElementType.FIELD 属性(包括枚举中的常量)

    ElementType.METHOD 方法

    ElementType.PARAMETER 形参

    ElementType.CONSTRUCTOR 构造函数

    ElementType.LOCAL_VARIABLE 局部变量

    ElementType.ANNOTATION_TYPE 注解类型

    ElementType.PACKAGE 包

    2.@Retention

    RetentionPolicy.SOURCE 编译时被丢弃,不包含在类文件中

    RetentionPolicy.CLASS JVM加载时被丢弃,包含在类文件中

    RetentionPolicy.RUNTIME JVM 加载,包含在类文件中,在运行时可以被获取到

    3.单机版配合反射

    4.web版配合spring-aop

    Aspect Pointcut

    5.应用场景

    对比同一个字段新旧值进行记录(包含字段汉字(比如姓名 年龄等注释))
    自定义注解+拦截器(HandlerInterceptor) 实现登录校验–>反射
    自定义注解+AOP 实现操作日志入库–>Aspect Pointcut
    展开全文
  • 使用自定义注解实现接口参数校验

    千次阅读 2019-01-21 00:10:59
    1.前言 在接口的开发中,我们有时会想让某个接口只可以被特定的人(来源)请求,那么就需要在服务端对...面对这种情况,我们可以选择自定义一个注解,由注解来告诉我们,这个接口允许的访问者是谁. 注:在本文的示例中,仅实...
  • Spring项目中自定义注解使用

    万次阅读 多人点赞 2019-05-24 23:18:38
    本篇博客将从一个普通的spring项目入手,教你如何在项目中应用自定义注解
  • Java使用自定义注解优雅地解决异常

    万次阅读 2020-08-29 23:04:05
    Java使用自定义注解优雅地解决异常 前言 我们在实际的开发的过程中是不是经常会遇到这样的情况:当调用服务出现错误的时候页面直接报500的错误,并且在页面显示一大串错误提示,很明显这种异常信息的展示对用户体验...
  • 自定义注解示例

    2020-04-05 23:46:29
    自定义注解在项目开发过程中非常有用,当框架提供的注解无法满足我们的业务逻辑需求时会需要我们自定义注解,了解自定义注解之前需要先了解元注解,即所谓注解的注解,本文不详聊元注解的概念,简单粗暴上示例代码...
  • 谈谈 Java 中自定义注解使用场景

    千次阅读 2020-09-03 09:33:00
    作者:快给我饭吃www.jianshu.com/p/a7bedc771204Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非...
  • 自定义注解在Spring中的应用

    千次阅读 2017-01-09 23:27:39
    Java注解作为程序元素(类、成员变量、成员方法等)的一种元数据信息,对程序本身的执行不会产生...将Java的自定义注解与Spring结合,在特定场景下实现注解的解析、处理,可以降低应用的耦合度,提高程序的可扩展性。
  • 深入Spring:自定义注解加载和使用 前言 在工作中经常使用Spring的相关框架,免不了去看一下Spring的实现方法,了解一下Spring内部的处理逻辑。特别是开发Web应用时,我们会频繁的定义*@Controller*,*@Service*等...
  • 在Spring中使用自定义注解的本质就是使用Spring 的 AOP编程。 首先创建一个注解@interface import org.springframework.data.mongodb.core.mapping.Document; import java.lang.annotation.ElementType; import ...
  • springboot 自定义注解(含源码)

    万次阅读 多人点赞 2019-03-04 14:40:17
    在springmvc框架广泛应用,可以注解的随处可见,近几年流行的springboot框架,更把注解用到了极致,这框架的基本消灭了大部分传统框架上xml配制后改为注解代替,既然注解这么使用这么多,那么如何自定义注解呢 ...
  • 自定义注解结合AOP之实战应用

    千次阅读 2020-12-19 20:07:41
    自定义注解结合AOP之实战应用背景介绍步骤流程1. 定义注解2.将注解应用于方法和参数3.定义切面4.测试结果致谢 背景介绍 最近在项目中写了一个公共的上传文件接口,项目中有多个业务场景会使用到上传文件,每个场景对...
  • JWT:Json Web Token是实现token的一种解决方案,它有三个部分组成,header(头)、payload(载体)、signature(签名)。 jwt的第一部分是header,header主要包含两个部分,alg指加密类型,可选值HS256,RSA,type为JWT...
  • 在我们做的系统中,有时需要记录操作日志,方便找到某个操作是谁进行的,这个可以用spring的aop来实现,本篇博客记录用自定义注解+aop应用于springboot项目中实现操作日志的记录 1、aop相关术语介绍 连接点...
  • SpringBoot自定义注解使用AOP实现请求参数解密以及响应数据加密 一、前言 本篇文章将依托与SpringBoot平台,自定义注解用来标识接口请求是否实现加密解密。使用AOP切面来具体操作解密加密,实现对源代码的低耦合,不...
  • Java自定义注解一般使用场景为:自定义注解+拦截器或者AOP,使用自定义注解来自己设计框架,使得代码看起来非常优雅。 本文将先从自定义注解的基础概念说起,然后开始实战,写小段代码实现自定义注解+拦截器,自定义...
  • 利用拦截器和自定义注解实现未登录拦截实现思路自定义注解拦截器代码实现拦截器加入配置其它微服务中引用使用该登录权限校验代码实现 实现思路 所有需要有登录权限的接口先校验是否已登录(登录成功会往redis缓存中...
  • 在上一篇博客已经介绍了...拦截所有添加了我们自定义注解的方法,并将userId和userMobile放入HttpServletRequest,之后通过对应的注解取值。 包格式 首先我们来先定义三个注解 根据需求...
  • SpringMVC Annotation自定义注解使用笔记

    千次阅读 2017-12-27 11:14:49
    step1:自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface ApiLog { String type(); String name(); } step2:定义Aspect // @Pointcut("execu
  • 自定义注解+Aop+Redis+SpringBoot应用于数据字典

    千次阅读 热门讨论 2019-11-28 11:40:06
    我们在项目的开发中,一般都会用到数据字典,但这有一点比较麻烦的是,数据库存的...其实还有更好的解决方案,那就是用自定义注解+Aop: 先来看表设计: t_annotation_data_dict表 t_annotation_data_item表 ...
  • Spring AOP与自定义注解Annotation的使用

    千次阅读 2019-06-03 21:29:19
    AOP,Spring框架的两大核心之一,又称面向切面编程,通过代理模式,对原有的类进行增强。在Spring框架中,AOP有两种动态代理方式,其一是...(笔者使用AOP的场景:保存所有用户对数据进行的增删改内容等,比如,张三...
  • 最近做的一个项目用的是前后端分离的开发模式,系统是要登录后才能进行操作的,所以需要进行身份token校验,校验通过后才能得到所请求的...于是就用了拦截器+自定义注解来实现,思路如下:用户登录成功后生成一个j...
  • Java自定义注解

    千次阅读 2012-03-03 02:04:39
    Java注解目前广泛被应用。spring中的基于注解的依赖注入、Spring Web MVC中的Route、...现在来看看,如何自定义注解。 目标:通过注解方式,定义属性的默认值。例如: public class DefaultUse { @Default(value =
  • java自定义注解以及原理

    千次阅读 2017-12-15 15:04:31
    1. 自定义注解应用举例 在springmvcconfig中定义前置通知,代码如下: import java.util.Arrays; import java.util.List; import com.puhui.flowplatform.manage.filter.RightFilter; import org.springframework....
  • 深入Spring:自定义注解加载和使用

    千次阅读 2016-05-10 19:52:25
    特别是开发Web应用时,我们会频繁的定义@Controller,@Service等JavaBean组件,通过注解,Spring自动扫描加载了这些组件,并提供相关的服务。 Spring是如何读取注解信息,并注入到bean容器中的,本文就是通过嵌入...
  • 今天主要说说如何通过自定义注解的方式,在 Spring Boot 中来实现 AOP 切面统一打印出入参日志。小伙伴们可以收藏一波。 废话不多说,进入正题 ! 目录 一、先看看切面日志输出效果 二、添加 AOP Maven ...
  • 使用Spring AOP和自定义注解记录日志

    千次阅读 2018-04-02 20:02:55
    什么是注解? 对于很多初次接触的开发者来说应该都有这个疑问?...为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontati...
  • java 自定义注解 spring aop 实现注解

    千次阅读 2017-01-13 11:46:36
    java自定义注解 1.几个常用的注解解释   @Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 62,830
精华内容 25,132
关键字:

web应用使用自定义注解