精华内容
下载资源
问答
  • web应用使用自定义注解
    2021-12-13 15:29:04

    (自定义注解用途很多,这里针对业务使用的是拦截器和方法参数解析器来袭实现用自定义注解为属性赋值的作用。)
    注解可以理解为是一种标记,在类方法包参数上的一些关键节点打上标记,等到运行到该位置,检测该标记执行特殊操作(三步走战略)
    第一步,定义注解——相当于定义标记;
    第二步,配置注解——把标记打在需要用到的程序代码中;
    第三步,解析注解——在编译期或运行时检测到标记,并进行特殊操作。

    ***使用自定义注解首先需要了解三个注解:@Target、@Documented、@Retention***
    

    @Target:注解的作用目标
    @Target(ElementType.TYPE)——接口、类、枚举、注解
    @Target(ElementType.FIELD)——字段、枚举的常量
    @Target(ElementType.METHOD)——方法
    @Target(ElementType.PARAMETER)——方法参数
    @Target(ElementType.CONSTRUCTOR) ——构造函数
    @Target(ElementType.LOCAL_VARIABLE)——局部变量
    @Target(ElementType.ANNOTATION_TYPE)——注解
    @Target(ElementType.PACKAGE)——包

    @Retention:注解的保留位置
    RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。
    RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。
    RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。

    @Document:说明该注解将被包含在javadoc中
    @Inherited:说明子类可以继承父类中的该注解
    在了解过后首先需要明确自定义注解的适用范围,作用来挑选不同的类别,

    package com.example.demo.config;
    
    import java.lang.annotation.*;
    
    /*
     * @author GG
     * @description 测试自定义注解
     */
    @Target(ElementType.PARAMETER)
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CurrentUser {
    }
    

    接下来需要考虑怎么实现注解的功能。

    package com.example.demo.config;
    /*
     * @author GG
     * @description
     */
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    @Configuration
    public class AuthInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String user = request.getHeader("user");
            request.setAttribute("user",user);
            return HandlerInterceptor.super.preHandle(request, response, handler);
        }
    }
    
    
    package com.example.demo.config;
    /*
     * @author GG
     * @description
     * @date 2021/12/13 15:00
     */
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.MethodParameter;
    import org.springframework.web.bind.support.WebDataBinderFactory;
    import org.springframework.web.context.request.NativeWebRequest;
    import org.springframework.web.method.support.ModelAndViewContainer;
    
    import javax.servlet.http.HttpServletRequest;
    @Configuration
    public class HandlerMethodArgumentResolver implements org.springframework.web.method.support.HandlerMethodArgumentResolver {
    
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterAnnotation(CurrentUser.class)!=null && parameter.getParameterType() == String.class;
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
            HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
            return request.getAttribute("user");
        }
    }
    
    
    package com.example.demo.Controller;/*
     * @author GG
     * @description
     */
    
    import com.example.demo.config.CurrentUser;
    import com.example.demo.pojo.AreaTree;
    import com.example.demo.service.AreaService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class AreaController {
        @Autowired
        private AreaService areaService;
        @PostMapping("/test")
        public List<AreaTree> getTree(@CurrentUser String user){
            System.out.println(user);
            return areaService.getaTree();
        }
    }
    
    

    一个简单的自定义注解就写好了,这个里面主要有三步:
    1.根据范围,挑选合适的注解。写出自己锁需要的注解
    2.挑选一种方法,我现在能想到的只有AOP和拦截器+方法参数解析器实现功能
    3.给需要赋值的位置加上自己的自定义注解

    更多相关内容
  • 在JAX-RS中,提供了NameBinding机制,简单理解NameBinding,就是把指定过滤器/拦截器通过自定义的名称注解绑定在某些匹配的资源方法上。Jersey, RESTeasy等框架都有相应的实现。 该代码利用Springboot模拟实现了...
  • 初学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.动态切换数据源注解

    原理:Spring提供了AbstractRoutingDataSource用于动态路由数据源,继承AbstractRoutingDataSource类并覆写其protected abstract Object determineCurrentLookupKey()即可

    完整描述查看:springboot动态多数据源配置和使用(二)

    1)创建自定义注解
    /**
     * 自定义的多数据源注解
     *
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface DataSource {
        String value() default "";
    }
    
    
    2)再创建一个AOP切面类来实现这个注解

    下面的代码很简单,可以看出,这个切面类先判断注解是在方法上还是类上,如果方法上有注解优先使用方法上的,获取注解的value属性的值,把这个值作为数据源的key。通过 DynamicContextHolder.push(value)来设置数据源的key(这里改变后, determineCurrentLookupKey()重写的方法返回的key也就改变了,从而切换了数据源)

    /**
     * 多数据源,切面处理类
     *
     */
    @Aspect
    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class DataSourceAspect {
        protected Logger logger = LoggerFactory.getLogger(getClass());
    
        @Pointcut("@annotation(io.renren.datasource.annotation.DataSource) " +
                "|| @within(io.renren.datasource.annotation.DataSource)")
        public void dataSourcePointCut() {
    
        }
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Class targetClass = point.getTarget().getClass();
            Method method = signature.getMethod();
    
            DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
            DataSource methodDataSource = method.getAnnotation(DataSource.class);
            if(targetDataSource != null || methodDataSource != null){
                String value;
                if(methodDataSource != null){
                    value = methodDataSource.value();
                }else {
                    value = targetDataSource.value();
                }
    
                DynamicContextHolder.push(value);
                logger.debug("set datasource is {}", value);
            }
    
            try {
                return point.proceed();
            } finally {
                DynamicContextHolder.poll();
                logger.debug("clean datasource");
            }
        }
    }
    
    
    3)把这个注解加在需要使用多数据源的service方法或类上

    从下面的三个方法逐个调用测试,可以看到操作了三个不同的数据源的数据

    @Service
    //@DataSource("slave1")
    public class DynamicDataSourceTestService {
        @Autowired
        private SysUserDao sysUserDao;
    
        @Transactional
        public void updateUser(Long id){
            SysUserEntity user = new SysUserEntity();
            user.setUserId(id);
            user.setMobile("13500000000");
            sysUserDao.updateById(user);
        }
    
        @Transactional
        @DataSource("slave1")
        public void updateUserBySlave1(Long id){
            SysUserEntity user = new SysUserEntity();
            user.setUserId(id);
            user.setMobile("13500000001");
            sysUserDao.updateById(user);
        }
    
        @DataSource("slave2")
        @Transactional
        public void updateUserBySlave2(Long id){
            SysUserEntity user = new SysUserEntity();
            user.setUserId(id);
            user.setMobile("13500000002");
            sysUserDao.updateById(user);
        }
    }
    
    



    参考文章

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

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

    展开全文
  • 自定义注解做点什么 前言 你不一定听过注解,但你一定对@Override不陌生。 当我们重写父类方法的时候我们就看到了@Override。我们知道它表示父类方法被子类重写了。 现在告诉你,@Override就是一个注解。 也许你...

    用自定义注解做点什么

    前言


    你不一定听过注解,但你一定对@Override不陌生。

    当我们重写父类方法的时候我们就看到了@Override。我们知道它表示父类方法被子类重写了。

    现在告诉你,@Override就是一个注解。

    也许你会疑惑注解是什么?
    注解(annotation)是JDK5之后引进的新特性,是一种特殊的注释,之所以说它特殊是因为不同于普通注释(comment)能存在于源码,而且还能存在编译期跟运行期,会最终编译成一个.class文件,所以注解能有比普通注释更多的功能。

    接下来,先入个门,然后通过实战来证明注解有多“猛”。

    PS : 如果已经了解的小伙伴可自行跳到 自定义注解实战。

    自定义注解入门


    我们对于注解的认识大多数来源于标准注解(也称为内建注解)。

    标准注解表示的意义
    @Override用于标识该方法继承自超类 当父类的方法被删除或修改了,编译器会提示错误信息
    @Deprecated表示该类或者该方法已经不推荐使用 如果用户还是要使用,会生成编译的警告
    @SuppressWarnings用于忽略的编译器警告信息

    Java不仅仅提供我们原有的注解使用,它还允许我们自定义注解。比如你可以像这样:

    public @interface DoSomething {
        public String name() default "write";
    }
    

    这是最简单的注解声明。
    尽管看上去像是接口的写法,但完全不是一回事。这一点要注意。
    而使用注解也很简单,可以像这样:

    @DoSomething(name = "walidake")//可以显式传值进来,此时name=walidake
    public class UseAnnotation {
    
    }
    
    @DoSomething//如果不传值,则默认name=我们定义的默认值,即我们上面定义的"write"
    public class UseAnnotation {
    
    }
    

    需要注意的是当注解有value()方法时,不需要指明具体名称

    public @interface DoSomething {
        public String value();
        public String name() default "write";
    }
    
    @DoSomething("walidake")
    public class UseAnnotation {
    
    }
    

    然而“最简单的自定义注解”并没有特别的意义。所以,这时候我们需要引入一个元注解的概念。

    我们需要知道这些概念:
    “普通注解”只能用来注解“代码”,而**“元注解”只能用来注解 “普通注解”**。
    自定义注解是“普通注解”

    JDK5时支持的元注解有@Documented @Retention @Target @Inherited,接下来分别介绍它们修饰注解的效果。

    @Documented
    @interface DocumentedAnnotation{
    
    }
    
    @interface UnDocumentedAnnotation{
    
    }
    
    @DocumentedAnnotation
    @UnDocumentedAnnotation
    public class UseDocumentedAnnotation{
    
    }
    

    打开小黑窗,运行javadoc UseDocumentedAnnotation.java

    运行结果:

    img

    结论:可以看到,被@Documented修饰的注解会生成到javadoc中,如@DocumentedAnnotation。
    而不被@Documented修饰的注解(@UnDocumentedAnnotation)不会生成到javadoc中。

    注解的级别
    @Retention可以设置注解的级别,分为三种,都有其特定的功能。
    这个元注解是我们关注的重点,后面实战我们会用到。

    注解级别存在范围主要用途
    SOURCE 源码级别注解只存在源码中功能是与编译器交互,用于代码检测。 如@Override,@SuppressWarings。 额外效率损耗发生在编译时
    CLASS 字节码级别注解存在源码与字节码文件中主要用于编译时生成额外的文件,如XML,Java文件等,但运行时无法获得。 这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件
    RUNTIME 运行时级别注解存在源码,字节码与Java虚拟机中主要用于运行时反射获取相关信息

    限制注解使用的范围
    注解默认可以修饰各种元素,而使用@Target可以限制注解的使用范围。

    例如,可以限定注解只能修饰方法。

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override {
    
    }
    

    上面的代码将注解的使用范围限制在了方法上,而不能用来修饰类。

    试着用@Override修饰类会得到“The annotation @Override is disallowed for this location”的错误。

    @Target支持的范围(参见ElementType):

    1) 类,接口,注解;
    2) 属性域;
    3) 方法;
    4) 参数;
    5) 构造函数;
    6) 局部变量;
    7) 注解类型;
    8) 包
    

    注解的继承
    @Inherited可以让注解类似被“继承”一样。
    通过使用@Inherited,可以让子类对象使用getAnnotations()获取父类@Inherited修饰的注解。

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @interface Inheritable{
    
    }
    
    @interface UnInheritable{
    
    }
    
    public class UseInheritedAnnotation{
        @UnInheritable
        @Inheritable
        public static class Super{
        
        }
    
        public static class Sub extends  Super {
        
        }
    
        public static void main(String... args){
        
            Super instance=new Sub();
            //result : [@com.walidake.annotation.util.Inheritable()]
            System.out.println(Arrays.toString(instance.getClass().getAnnotations()));
        }
    }
    

    我们干脆用@Documented查看类结构。发现:

    img

    img

    这是不是恰恰证明了这种是伪继承的说法,而不是真正的继承。

    自定义注解实战


    引言
    Java Web开发中,对框架的理解和掌握是必须的。而在使用大多数框架的过程中,一般有两种方式的配置,一种是基于xml的配置方式,一种是基于注解的方式。然而,越来越多的程序员(我)在开发过程中享受到注解带来的简便,并义无反顾地投身其中。

    ORM框架,像Hibernate,Mybatis就提供了基于注解的配置方式。我们接下来就使用自定义注解实现袖珍版的Mybatis,袖珍版的Hibernate。

    这很重要
    说明:实战的代码会被文章末尾附上。而实际上在之前做袖珍版框架的时候并没有想到会拿来做自定义注解的Demo。因此给出的代码涉及了其他的一些技术,例如数据库连接池,动态代理等等,比较杂。
    在这个篇幅我们只讨论关于自定义注解的问题,至于其他的技术后面会开多几篇博文阐述。(当然这么多前辈面前不敢造次,有个讨论学习的氛围是很好的~)

    那么在自定义注解框架前,我们需要花点时间浏览以下几个和Annotation相关的方法。

    方法名用法
    Annotation getAnnotation(Class annotationType)获取注解在其上的annotationType
    Annotation[] getAnnotations()获取所有注解
    isAnnotationPresent(Class annotationType)判断当前元素是否被annotationType注解
    Annotation[] getDeclareAnnotations()与getAnnotations() 类似,但是不包括父类中被Inherited修饰的注解

    Mybatis 自定义注解

    本节目标:自定义注解实现Mybatis插入数据操作。
    本节要求:细心观察使用自定义注解的步骤。

    Step 1 : 声明自定义注解。

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Insert {
        public String value();
    }
    

    Step 2 : 在规定的注解使用范围内使用我们的注解

    public interface UserMapper {
    
        @Insert("insert into user (name,password) values (?,?)")
        public void addUser(String name,String password);
    
    }
    
    

    Step 3 : 通过method.getAnnotation(Insert.class).value()使用反射解析自定义注解,得到其中的sql语句

    //检查是否被@Insert注解修饰
    if (method.isAnnotationPresent(Insert.class)) {
        //检查sql语句是否合法
        //method.getAnnotation(Insert.class).value()取得@Insert注解value中的Sql语句
        sql = checkSql(method.getAnnotation(Insert.class).value(),
            Insert.class.getSimpleName());
        //具体的插入数据库操作
        insert(sql, parameters);
    }
    

    Step 4 : 根据实际场景调用Step 3的方法

    UserMapper mapper = MethodProxyFactory.getBean(UserMapper.class);
    mapper.addUser("walidake","665908");
    
    

    运行结果:

    img

    以上节选自annotation中Mybatis部分。具体CRUD操作请看源码。

    总结一下从上面学到的东西:
    1.声明自定义注解,并限制适用范围(因为默认是通用)
    2.规定范围内使用注解
    3.isAnnotationPresent(Insert.class)检查注解,getAnnotation(Insert.class).value()取得注解内容
    4.根据实际场景应用

    Hibernate 自定义注解

    本节目标:自定义注解使实体自动建表(即生成建表SQL语句)
    本节要求:动手操作,把未给全的代码补齐。
    本节规划:仿照Hibernate,我们大概会需要@Table,@Column,还有id,我们这里暂且声明为@PrimaryKey

    仿照自定义Mybatis注解的步骤:

    /**
     * 可根据需要自行定制功能
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Table {
        String name() default "";
    
    }
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Column {
        // 列名 默认为""
        String name() default "";
    
        // 长度 默认为255
        int length() default 255;
    
        // 是否为varchar 默认为true
        boolean varchar() default true;
    
        // 是否为空 默认可为空
        boolean isNull() default true;
    }
    
    /**
     * 有需要可以拆分成更小粒度
     * @author walidake
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface PrimaryKey {
        String name() default "";
    }
    
    

    完成Step 1,接下来是Step 2。

    @Table
    public class Person {
        @PrimaryKey
        private int id;
    
        @Column(isNull = false, length = 20)
        private String username;
        ...
    }
    

    Step 3,新建一个叫做SqlUtil的类,使用Class(实体类).isAnnotationPresent(Table.class)取到@Table注解的内容。

    而我们如何取到@Column和@PrimaryKey的内容?
    使用反射,我们可以很容易做到。

    // 反射取得所有Field
    Field[] fields = clazz.getDeclaredFields();
    ...
    ...
    // 获取注解对象
    column = fields[i].getAnnotation(Column.class);
    // 设置访问私有变量
    fields[i].setAccessible(true);
    // 取得@Column的内容
    columnName = "".equals(column.name()) ? fields[i].getName(): column.name();
    
    

    反射的内容后面再写。(感觉每一篇都给自己挖了很多坑后面去填)

    Step 4套入使用场景

    String createSql = SqlUtil.createTable(clazz);
    ...
    connection.createStatement().execute(createSql);
    

    运行结果:

    img

    运行结果正确!

    自此我们完成了实战模块的内容。当然关于Hibernate的CRUD也可以用同样的方法做到,更进一步还可以把二级缓存整合进来,实现自己的一个微型框架。尽管现有的框架已经很成熟了,但自己实现一遍还是能收获很多东西。

    可以看出来,注解简化了我们的配置。每次使用注解只需要@注解名就可以了,就跟吃春药一样“爽”。不过由于使用了反射,后劲太“猛”,jvm无法对代码优化,影响了性能。这一点最后也会提及。

    另外提一点,之前想格式化hibernate生成的SQL,做大量搜索后被告知“Hibernate 使用的是开源的语法解析工具 Antlr,需要进行 SQL 语法解析,将 SQL 语句整理成语法树”。也算一个坑吧~
    不过后来找到一个除了建表SQL以外的格式化工具类,觉得还不错就也分享了。可以在源码中找到。

    最后说点什么
    可以发现我们使用运行时注解来搭建我们的袖珍版ORM框架,因为运行时注解来搭建框架相对容易而且适用性也比较广,搭建的框架使用起来也比较简单。但在此基础上因为需要用到反射,其效率性能相对不高。因此,多数Web应用使用运行时注解,而像Android等对效率性能要求较高的平台一般使用源码级别注解来搭建。下一节我们讨论怎么玩一玩源码级注解。

    展开全文
  • 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项目,输入访问地址

    在这里插入图片描述

    写在最后

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

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

    展开全文
  • 表单重复提交是在多用户的 Web 应用中最常见且带来麻烦最多的一个问题。有很多的应用场景都会遇到表单重复提交问题,比如由于用户误操作,多次点击...今天就和大家分享一下如何利用自定义注解来实现防止表单重复提交。
  • SpringBoot自定义starter加强版——扫描自定义注解
  • Java使用自定义注解优雅地解决异常

    万次阅读 2020-08-29 23:04:05
    Java使用自定义注解优雅地解决异常 前言 我们在实际的开发的过程中是不是经常会遇到这样的情况:当调用服务出现错误的时候页面直接报500的错误,并且在页面显示一大串错误提示,很明显这种异常信息的展示对用户体验...
  • 刚才我们使用自定义注解实现了在方法调用前输出一句日志,但是我们并不知道这是哪个方法、哪个类输出的,如果有两个方法都加上了这个注解,且value的值都一样,那我们该怎么区分这两个方法呢?比如现在我们给 ...
  • Spring项目中自定义注解使用

    万次阅读 多人点赞 2019-05-24 23:18:38
    本篇博客将从一个普通的spring项目入手,教你如何在项目中应用自定义注解
  • 特别是开发Web应用时,我们会频繁的定义@Controller,@Service等JavaBean组件,通过注解,Spring自动扫描加载了这些组件,并提供相关的服务。Spring是如何读取注解信息,并注入到bean容器中的,本文就是通过嵌入...
  • Java实现自定义注解

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

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

    千次阅读 2019-06-03 21:29:19
    AOP,Spring框架的两大核心之一,又称面向切面编程,通过代理模式,对原有的类进行增强。在Spring框架中,AOP有两种动态代理方式,其一是...(笔者使用AOP的场景:保存所有用户对数据进行的增删改内容等,比如,张三...
  • JWT:Json Web Token是实现token的一种解决方案,它有三个部分组成,header(头)、payload(载体)、signature(签名)。 jwt的第一部分是header,header主要包含两个部分,alg指加密类型,可选值HS256,RSA,type为JWT...
  • springboot自定义注解作用在类上demo

    千次阅读 2021-09-24 19:35:25
    然后写了一个aop,自定义注解,注解作用在方法上可以实现逻辑,结果放在类上就不可以,都没有进入切面了,最后在仔细学习发现问题,话不多说上代码 pom//这里包有点多,其实好多都可以不要,偷懒了 <properties&...
  • 自定义注解校验Token

    千次阅读 2018-08-24 11:35:26
    方法二: 自定义注解校验token 1.创建自定义注解 package com .huizhongcf .cloud .api .interceptor ; import java .lang .annotation .Documented ; import java .lang .annotation .ElementType ; ...
  • 使用自定义注解实现接口参数校验

    千次阅读 2019-01-21 00:10:59
    1.前言 在接口的开发中,我们有时会想让某个接口只可以被特定的人(来源)请求,那么就需要在服务端对...面对这种情况,我们可以选择自定义一个注解,由注解来告诉我们,这个接口允许的访问者是谁. 注:在本文的示例中,仅实...
  • SpringBoot自定义注解使用AOP实现请求参数解密以及响应数据加密 一、前言 本篇文章将依托与SpringBoot平台,自定义注解用来标识接口请求是否实现加密解密。使用AOP切面来具体操作解密加密,实现对源代码的低耦合,不...
  • 一,了解自定义注解 1.1 自定义注解的定义、描述 注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译...
  • 自定义注解实现RBAC权限校验,不再说你不会了

    千次阅读 多人点赞 2022-01-22 14:06:23
    拦截器+自定义注解实现RBAC权限校验,你绝对看得懂
  • 自定义注解在Spring中的应用

    千次阅读 2017-01-09 23:27:39
    Java注解作为程序元素(类、成员变量、成员方法等)的一种元数据信息,对程序本身的执行不会产生...将Java的自定义注解与Spring结合,在特定场景下实现注解的解析、处理,可以降低应用的耦合度,提高程序的可扩展性。
  • springboot 自定义注解(含源码)

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

    千次阅读 2019-07-22 16:30:21
    自定义注解: package com.example.demo.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang....
  • 第12章 Sb starter解析【没有什么事情是starter不能解决的】 ...使用starter也非常简单,只需要引入之后就会进行自动装装载。这章就会揭开starter的神秘面纱,同时带小伙伴写一个属于自己的starter。...
  • 使用拦截器(HandlerInterceptor)+自定义注解,实现对系统管理员操作进行记录。 项目使用SpringMVC+MyBatis开发,Spring版本为:4.1.9.RELEASE。 关于拦截器没有太详细的介绍,本文仅对使用拦截器做了一定的说明。
  • 自定义注解不起作用

    千次阅读 2019-12-14 15:57:53
    一个吭哧了半天的问题,我写了自定义...看到一个博客自定义注解需要的两个jar web和aop 所以检查我自己的这两个jar包 1.web 修改前 <dependency> <groupId>org.springframework</groupId>...
  • 比如要实现这个自定义注解实现RequestMapping注解的功能。只需要很少的代码就能完成这个功能。 0,第一步自然是定义一个注解 @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy....
  • 目录 外部配置加载 环境准备 命令行参数 spring.config.location @ PropertySource ...Spring Boot 支持多种外部配置方式,如下所示,从上往下加载优先级... 使用“spring.config.location”改变默认的配置文件位...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 74,539
精华内容 29,815
热门标签
关键字:

web应用使用自定义注解