目录:
- 概念
- 字段脱敏注解Demo
参考:
概念
注解是元数据的一种形式,可以添加到Java源代码中。 类,方法,变量,参数和包都可以被注释。 注解对其注释的代码的操作没有直接影响。
通过自定义注解,可以对类或方法等进行打标,实现自定义的逻辑。
自定义注解使用@interface
关键字
public @interface FieldSensitive {
}
还有一些注解,用来对注解使用做一些限制,如下。
@Retention
@Retention作用是什么
Retention的翻译过来就是”保留”的意思。也就意味着它的作用是,用来定义注解的生命周期的,并且在使用时需要指定RetentionPolicy
,RetentionPolicy
有三种策略,分别是:
- SOURCE - 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
- CLASS - 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
- RUNTIME - 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。
选择合适的生命周期
首先要明确生命周期 RUNTIME > CLASS > SOURCE 。一般如果需要在运行时去动态获取注解信息,只能使用RUNTIME。如果要在编译时进行一些预处理操作,比如生成一些辅助代码就用CLASS。如果只是做一些检查性的操作,比如 @Override和@SuppressWarnings,则可选用 SOURCE。
我们实际开发中的自定义注解几乎都是使用的RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
}
@Target
@Target
注解是限定自定义注解可以使用在哪些地方。这就和参数校验一样,约定好规则,防止乱用而导致问题的出现。针对上述的需求可以限定它只能用方法上。根据不同的场景,还可以使用在更多的地方。比如说属性、包、构造器上等等。
- TYPE - 类,接口(包括注解类型)或枚举
- FIELD - 字段(包括枚举常量)
- METHOD - 方法
- PARAMETER - 参数
- CONSTRUCTOR - 构造函数
- LOCAL_VARIABLE - 局部变量
- ANNOTATION_TYPE -注解类型
- PACKAGE - 包
- TYPE_PARAMETER - 类型参数
- TYPE_USE - 使用类型
上面两个是比较常用的元注解,Java一共提供了4个元注解。你可能会问元注解是什么?元注解的作用就是负责注解其他注解。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
}
@Documented
@Documented
的作用是对自定义注解进行标注,如果使用@Documented
标注了,在生成javadoc的时候就会把@Documented
注解给显示出来。没什么实际作用,了解一下就好了。
@Inherited
被@Inherited
修饰的注解,被用在父类上时其子类也拥有该注解。 简单的说就是,当在父类使用了被@Inherited
修饰的注解@InheritedTest
时,继承它的子类也拥有@InheritedTest
注解。
字段脱敏注解Demo
实现一个自定义注解,用来将日志中的敏感字段进行脱敏输出。
依赖
<!-- 集成web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.30</version>
</dependency>
枚举类
public enum SensitiveEnum {
/** 自定义(此项需设置脱敏的范围)*/
CUSTOMER("customer"),
/** 姓名 */
NAME("name"),
/** 身份证号 */
ID_NO("idNo"),
/** 手机号 */
PHONE("phone"),
/** 邮箱 */
EMAIL("email");
private String name;
SensitiveEnum(String name){
this.name = name;
}
}
自定义注解
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldSensitive {
/**敏感字段脱敏格式,默认为自定义脱敏格式*/
SensitiveEnum type() default SensitiveEnum.CUSTOMER;
/**前缀不需要脱敏的长度*/
int prefixNoMaskLen() default 1;
/**后缀不需要脱敏的长度*/
int suffixNoMaskLen() default 1;
/**使用的脱敏占用符号*/
String symbol() default "*";
}
拦截AOP
我这里由于直接修改结果返回值,所以使用了**@Around**注解
@Aspect
@Component
public class SensitiveAop {
/**
* 定义切入点
*/
// @Pointcut("execution(* com.qian..*.*(..))")
// public void pointcut(){
//
// }
/**
* 定义advisor
*/
@Around("execution(* com.qian.annotationDemo.controller..*.*(..))")
public Object aroundAop(ProceedingJoinPoint joinPoint) throws Throwable {
// String methodName = joinPoint.getSignature().getName();
// if(!methodName.equals("test")){
// return data;
// }
Object data = joinPoint.proceed();
if (data == null) {
return "";
}
User user;
if(data instanceof User) {
user = (User) data;
}if(data instanceof String){
user = JSON.parseObject((String) data, User.class);
}else {
return "";
}
Class<?> objClass = user.getClass();
Field[] fields = objClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
FieldSensitive annotation = field.getAnnotation(FieldSensitive.class);
if (annotation != null) {
Object value = field.get(user);
if (ObjectUtils.isNotEmpty(value) && StringUtils.isNotBlank((String)value)) {
SensitiveEnum sensitiveEnum = annotation.type();
int prefixNoMaskLen = annotation.prefixNoMaskLen();
int suffixNoMaskLen = annotation.suffixNoMaskLen();
String symbol = annotation.symbol();
switch (sensitiveEnum) {
case CUSTOMER:
value = SensitiveUtil.desValue((String)value, prefixNoMaskLen, suffixNoMaskLen, symbol);
break;
case ID_NO:
value = SensitiveUtil.hideIdNo((String)value);
break;
case NAME:
value = SensitiveUtil.hideName((String)value);
break;
case PHONE:
value = SensitiveUtil.hidePhone((String)value);
break;
default:
throw new IllegalArgumentException("unknown privacy type enum " + sensitiveEnum);
}
field.set(user, value);
field.setAccessible(false);
}
}
}
return JSON.toJSONString(user);
}
}
实体类
public class User {
public User() {
}
/**
* 全参构造函数
* @param userId
* @param name
* @param phone
*/
public User(String userId, String name, String phone) {
this.userId = userId;
this.name = name;
this.phone = phone;
}
/**
* userId
*/
private String userId;
/**
* 姓名
*/
@FieldSensitive(type = SensitiveEnum.NAME)
private String name;
/**
* 电话
*/
@FieldSensitive(type = SensitiveEnum.PHONE)
private String phone;
}
Controller
@RestController
public class SensitiveController {
/**
* 测试注解是否生效
* @return
*/
@RequestMapping("/annotation")
public String test(){
User user = new User("1", "千小虎", "12388889999");
return JSON.toJSONString(user);
}
}
运行结果