项目中的自定义注解

1、实际开发场景

在项目的实际开发中,我们肯定会需要使用自定义注解来做到某些我们想要达到的效果

2、代码开发

2.1、自定义注解的使用

2.1.1、controller层

	//swagger中的注解,用于构建Api文档
    @ApiOperation(value = "新增用户")
    //自定义注解-日志
    @WebLog(info = "新增用户")
    //自定义注解-加解密
    @SecurityParameter
    @PostMapping("/addUser")
    public ResponseEntity<String> addUser(@RequestBody @Validated({User.AddAction.class}) User user) {
        .
        .
        .
        .
        .
        .
        return "新增成功";
    }

2.1.2、对象

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
//开启链式编程
@Accessors(chain = true)
//现实实体类型和数据库中的表实现映射
@TableName(value = "user")
public class User extends BasicEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    @NotNull(groups = {UpdateAction.class}, message = "userId不能为空")
    private Long id;

    @ApiModelProperty(value = "分组名称")
    @NotBlank(groups = {AddAction.class, UpdateAction.class}, message = "分组名称不能为空")
    private String roleName;

    @ApiModelProperty(value = "性别")
    @NotNull(groups = {AddAction.class, UpdateAction.class}, message = "性别不能为空")
    @EnumCheck(groups = {AddAction.class, UpdateAction.class}, clazz = Sex.class, message = "性别类型不合法")
    private Sex sex;

    @TableField(fill = FieldFill.INSERT)
    @TableLogic(value = "0", delval = "1")
    private Byte isDel;

    public interface AddAction {
    }

    public interface UpdateAction {
    }

}

2.2、自定义注解 - 校验字段的枚举值类型是否合法

2.2.1、注解

//将注解包含在javadoc中
@Documented
/**
* Constraint    详细信息
* @Null    被注释的元素必须为 null
* @NotNull    被注释的元素必须不为 null
* @AssertTrue    被注释的元素必须为 true
* @AssertFalse    被注释的元素必须为 false
* @Min(value)    被注释的元素必须是一个数字,其值必须大于等于指定的最小值
* @Max(value)    被注释的元素必须是一个数字,其值必须小于等于指定的最大值
* @DecimalMin(value)    被注释的元素必须是一个数字,其值必须大于等于指定的最小值
* @DecimalMax(value)    被注释的元素必须是一个数字,其值必须小于等于指定的最大值
* @Size(max, min)    被注释的元素的大小必须在指定的范围内
* @Digits (integer, fraction)    被注释的元素必须是一个数字,其值必须在可接受的范围内
* @Past    被注释的元素必须是一个过去的日期
* @Future    被注释的元素必须是一个将来的日期
* @Pattern(value)    被注释的元素必须符合指定的正则表达式
*/
//标明由哪个类执行校验逻辑
@Constraint(validatedBy = EnumConstraintValidator.class)
/**
* 接口、类、枚举、注解
* @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)
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
/**
* 注解仅存在于源码中,在class字节码文件中不包含
* @Retention(RetentionPolicy.SOURCE)
* 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
* @Retention(RetentionPolicy.CLASS)    
* 注解会在class字节码文件中存在,在运行时可以通过反射获取到
* @Retention(RetentionPolicy.RUNTIME)
*/
@Retention(RetentionPolicy.RUNTIME)
//可以在注解的地方重复标注注解
@Repeatable(EnumCheck.List.class)
public @interface EnumCheck {

    String message() default "{javax.validation.constraints.EnumCheck.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    /**
     * 枚举类
     */
    Class<? extends EnumValidator> clazz();

    /**
     * 调用的方法名称
     */
    String method() default "getValue";

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        EnumCheck[] value();
    }
}

2.2.2、校验类

//需要继承ConstraintValidator<注解名,校验的Object>
public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> {

    /**
     * 注解对象
     */
    private EnumCheck annotation;

    /**
     * 初始化方法
     *
     * @param constraintAnnotation 注解对象
     */
     //方法initialize()不需要一定有,使用可用来对注解定义的参数进行初始化给isValid()方法进行使用
    @Override
    public void initialize(EnumCheck constraintAnnotation) {
        this.annotation = constraintAnnotation;
    }

	//方法isValid()必须实现,是校验逻辑所在的位置
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (Objects.isNull(value)) {
            return true;
        }
        Object[] enumConstants = annotation.clazz().getEnumConstants();
        try {
            Method method = annotation.clazz().getMethod(annotation.method());
            for (Object enumConstant : enumConstants) {
                if (method.invoke(value).equals(method.invoke(enumConstant))) {
                    return true;
                }
            }
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            return false;
        }
        return false;
    }
}

2.2.3、枚举接口

public interface EnumValidator {

    Object getValue();

}

2.2.4、枚举对象

public enum Sex implements EnumValidator {

    MAN(0, "男"),
    WOMAN(1, "女");

    @EnumValue
    @JsonValue
    private Integer type;

    private String desc;

    Sex(Integer type, String desc) {
        this.type = type;
        this.desc = desc;
    }

    public Integer getType() {
        return type;
    }

    public String getDesc() {
        return desc;
    }

    public static Sex getByType(int type) {
        for (Sex sex : values()) {
            if (sex.type == type) {
                return sex;
            }
        }
        return null;
    }

    @Override
    public Object getValue() {
        return type;
    }

}

2.3、自定义注解 - 日志

2.3.1、注解

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

    /**
     * 日志方法描述信息
     */
    String info() default "";

}

2.3.2、切面

//@Aspect:作用是把当前类标识为一个切面供容器读取
@Aspect
@Component
@Slf4j
//切面加载顺序
@Order(1)
public class RequestAspect {

    private static ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();

	//@Pointcut:Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
    @Pointcut("@annotation(xxx.xxx.xxx.WebLog)")
    public void webLog() {
    }

	//@Before:标识一个前置增强方法,相当于BeforeAdvice的功能
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String ip = request.getRemoteAddr();
        String httpMethod = request.getMethod();
        String url = request.getRequestURL().toString();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        WebLog webLog = signature.getMethod().getAnnotation(WebLog.class);
        String apiInfo = webLog.info();
        log.info("~~~Start~~~收到来自 {} 的 {} 请求,URL: {},开始执行后台方法:{},全路径({}.{}),入参为:{}~~~",
                ip, httpMethod, url, apiInfo,
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(),
                JSONObject.toJSONString(getFieldsName(joinPoint)));
    }

	//@Around:环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        WebLog webLog = signature.getMethod().getAnnotation(WebLog.class);
        String apiInfo = webLog.info();
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        log.info("~~~End~~~方法 {}.{} ({}) 执行结束,耗时:{}秒,返回值为:{}~~~",
                proceedingJoinPoint.getSignature().getDeclaringTypeName(),
                proceedingJoinPoint.getSignature().getName(),
                apiInfo,
                (float) (System.currentTimeMillis() - startTime) / 1000,
                JSONObject.toJSONString(result));
        return result;
    }

    /**
     * 获取参数列表
     */
    private static Map<String, Object> getFieldsName(JoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = pnd.getParameterNames(method);
        if (parameterNames != null) {
            Map<String, Object> paramMap = new HashMap<>(parameterNames.length);
            for (int i = 0; i < parameterNames.length; i++) {
                paramMap.put(parameterNames[i], args[i]);
            }
            return paramMap;
        }
        return Collections.emptyMap();
    }

}

2.4、自定义注解 - 加解密

2.4.1、注解

/**
 * 请求数据解密
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Mapping
@Documented
public @interface SecurityParameter {

    /**
     * 入参是否解密,默认解密
     */
    boolean inDecode() default true;

    /**
     * 出参是否加密,默认加密
     */
    boolean outEncode() default true;
}

2.4.2、请求数据解密

/**
 * 请求数据解密
 */
 //范围 - 在括号内的包下生效
@ControllerAdvice(basePackages = "xxx.xxx.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {

    private static final Logger logger = LoggerFactory.getLogger(DecodeRequestBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        try {
            boolean encode = false;
            if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
                //获取注解配置的包含和去除字段
                SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
                //入参是否需要解密
                encode = serializedField.inDecode();
            }
            if (encode) {
                logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密");
                return new MyHttpInputMessage(inputMessage);
            } else {
                return inputMessage;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            return inputMessage;
        }
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return body;
    }

    class MyHttpInputMessage implements HttpInputMessage {
        private HttpHeaders headers;

        private InputStream body;

        public MyHttpInputMessage(HttpInputMessage inputMessage) throws Exception {
            this.headers = inputMessage.getHeaders();
            this.body = IOUtils.toInputStream(AesEncryptUtils.decryptAES(IOUtils.toString(inputMessage.getBody(), "UTF-8")));
        }

        @Override
        public InputStream getBody() throws IOException {
            return body;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }

    }

}

2.4.3、返回数据加密

/**
 * 返回数据加密
 */
@ControllerAdvice(basePackages = "xxx.xxx.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {

    private final static Logger logger = LoggerFactory.getLogger(EncodeResponseBodyAdvice.class);

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        boolean encode = false;
        if (methodParameter.getMethod().isAnnotationPresent(SecurityParameter.class)) {
            //获取注解配置的包含和去除字段
            SecurityParameter serializedField = methodParameter.getMethodAnnotation(SecurityParameter.class);
            //出参是否需要加密
            encode = serializedField.outEncode();
        }
        if (encode) {
            logger.info("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行加密");
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                String result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body);
                return AesEncryptUtils.encryptAES(result);
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("对方法method :【" + methodParameter.getMethod().getName() + "】返回数据进行解密出现异常:" + e.getMessage());
            }
        }
        return body;
    }

}

2.4.4、前后端数据传输加密工具类

/**
 * 前后端数据传输加密工具类
 *
 * @author monkey
 */
@Slf4j
public class AesEncryptUtils {

    /*
     * 加密用的Key 可以用26个字母和数字组成 例如使用AES-128-CBC加密模式,key需要为16位。
     */
    private static final String KEY = "xxxxxxxxxxxxxxxx";
    private static final String IV = "xxxxxxxxxxxxxxxx";
    //参数分别代表 算法名称/加密模式/数据填充方式
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * AES算法加密明文
     *
     * @param data 明文
     * @return 密文
     */
    public static String encryptAES(String data) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            SecretKeySpec keyspec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
            // CBC模式,需要一个向量iv,可增加加密算法的强度
            IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes("UTF-8"));
            cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
            byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"));
            // BASE64做转码。
            return AesEncryptUtils.encode(encrypted).trim();
        } catch (Exception e) {
            log.error("{} 加密失败", data, e);
            return null;
        }
    }

    /**
     * AES算法解密密文
     *
     * @param data 密文
     * @return 明文
     */
    public static String decryptAES(String data) {
        try {
            if (StringUtils.isEmpty(data)) {
                return null;
            }
            //先用base64解密
            byte[] encrypted = AesEncryptUtils.decode(data);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            SecretKeySpec keyspec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");
            IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes("UTF-8"));
            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
            byte[] original = cipher.doFinal(encrypted);
            String originalString = new String(original);
            return originalString.trim();
        } catch (Exception e) {
            log.error("{} 解密失败", data, e);
            return data;
        }
    }

    /**
     * 编码
     *
     * @param byteArray
     * @return
     */
    public static String encode(byte[] byteArray) {
        return new String(new Base64().encode(byteArray));
    }

    /**
     * 解码
     *
     * @param base64EncodedString
     * @return
     */
    public static byte[] decode(String base64EncodedString) {
        return new Base64().decode(base64EncodedString);
    }

    /**
     * 用于对GET请求中的查询字符串进行解密
     *
     * @param params GET请求中的查询字符串
     */
    public static Map<String, Object> decryptQueryParams(Map<String, String> params) {
        Map<String, Object> doAfter = new HashMap<>();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (!StringUtils.isEmpty(entry.getValue())) {
                doAfter.put(entry.getKey(), decryptAES(entry.getValue()));
            }
        }
        return doAfter;
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
常用的自定义注解有以下几种: 1. @Autowired:用于自动装配依赖对象,可以在Spring容器自动查找匹配的Bean,并将其注入到目标对象。 2. @RequestMapping:用于映射HTTP请求的URL路径到具体的处理方法上,可以指定请求的方法、路径、参数等。 3. @Component:用于将一个类标识为Spring容器的组件,可以通过@ComponentScan注解扫描并注册到容器。 4. @Transactional:用于标识一个方法或类需要进行事务管理,可以控制事务的提交、回滚等行为。 5. @Validated:用于对方法参数进行校验,可以指定参数的验证规则,如非空、长度范围等。 6. @Aspect:用于定义切面,可以在方法执行前、后或异常时执行一些额外的逻辑,如日志记录、性能监控等。 这些是常用的自定义注解,可以根据具体的需求自定义更多的注解来实现特定的功能。 #### 引用[.reference_title] - *1* [Spring Boot的自定义注解](https://blog.csdn.net/qq_44717657/article/details/130869793)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [springboot项目定义注解的使用总结、java自定义注解实战(常用注解DEMO)](https://blog.csdn.net/qq_21187515/article/details/109643130)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [自定义注解](https://blog.csdn.net/u014365523/article/details/126730735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值