一、注解的介绍
在Java中,注解(Annotation)是JDK5.0引入的一个重要特性。注解提供了一种元数据机制,可以用于描述和定义程序中的元素(类、方法、成员变量等)。注解是一种能被添加到java源代码中的元数据,方法、类、参数和包都可以用注解来修饰。注解可以看作是一种特殊的标记,可以用在方法、类、参数和包上,程序在编译或者运行时可以检测到这些标记而进行一些特殊的处理。
二、创建一个注解的基本元素
修饰符
访问修饰符必须为public,不写默认为pubic;
关键字
关键字为@interface;
注解名称
注解名称为自定义注解的名称
注解类型元素
注解类型元素是注解中内容,根据需要标志参数
三、SpringBoot自定义注解
3.1 创建注解
自定义注解需要使用@interface
关键字进行定义,并且需要指定该注解的作用目标(如类、方法、字段等)和需要包含的元数据信息(如属性及其默认值等)。
3.1.1 元注解(@Target、@Retention、@Inherited、@Documented)
@Target、@Retention、@Inherited、@Documented,这四个注解就是元注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的元注解类型,它们被用来提供对其它 注解类型作标志操作(可以理解为最小的注解,基础注解)
- @Target:用于描述注解的使用范围,该注解可以使用在什么地方
备注:例如@Target(ElementType.METHOD),标志的注解使用在方法上。如果我们将这个注解标志在类上,就会报错。
- @Retention:表明该注解的生命周期
生命周期类型 | 描述 |
---|---|
RetentionPolicy.SOURCE | 编译时被丢弃,不包含在类文件中 |
RetentionPolicy.CLASS | JVM加载时被丢弃,包含在类文件中,默认值 |
RetentionPolicy.RUNTIME | 由JVM 加载,包含在类文件中,在运行时可以被获取到 |
@Inherited:是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
@Documented:表明该注解标记的元素可以被Javadoc 或类似的工具文档化
SpringBoot自定义注解 只需要关注@Target、@Retention这两个元注解即可
这是一个自定义注解的示例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE) // 指定这个注解可以用在类上
@Retention(RetentionPolicy.RUNTIME) // 指定这个注解在运行时可用
public @interface MyCustomAnnotation {String value() default ""; // 这是一个默认属性
}
上面的代码定义了一个名为MyCustomAnnotation
的自定义注解。这个注解可以用在类上,并且在运行时可用。这个注解有一个属性value
,它的默认值是空字符串。
3.2 使用注解
在需要使用该注解的类、方法或字段上添加该注解即可。
@MyCustomAnnotation("This is a custom annotation example")
public class MyClass {// ...
}
在这个例子中,我们在MyClass
类上使用了MyCustomAnnotation
注解,并设置了value
属性的值为"This is a custom annotation example"。
3.3 处理注解
可以使用Spring Boot的AOP功能或其他方式编写拦截器来处理带有自定义注解的类、方法或字段。在拦截器中,可以读取自定义注解的元数据信息,并执行相应的逻辑(如打印注解的值或执行其他操作)。
例如,你可以使用AOP的@Before
注解来指定在处理带有自定义注解的类之前执行的逻辑:
@Aspect
@Component
public class MyCustomAnnotationAspect {@Before("@annotation(MyCustomAnnotation)")public void handleMyCustomAnnotation(MyCustomAnnotation annotation) {System.out.println("Handling custom annotation: " + annotation.value());}
}
在这个例子中,我们在一个AOP切面中定义了一个方法,这个方法将在处理带有@MyCustomAnnotation
注解的类之前执行。在方法中,我们可以读取MyCustomAnnotation
注解的元数据信息(即value
属性的值),并执行相应的逻辑。
四、自定义注解的使用DEMO
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{@Overridepublic 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 {@Autowiredprivate RoleInterceptor roleInterceptor;/*** 添加拦截器** @param registry*/@Overridepublic 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.数据脱敏
1)自定义Jackson注解
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {//脱敏策略SensitiveStrategy strategy();
}
2) 指定脱敏策略,这个规则根据业务具体需求去制定,下面只做演示
import java.util.function.Function;/*** 脱敏策略,枚举类,针对不同的数据定制特定的策略*/
public enum SensitiveStrategy {/*** 用户名*/USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),/*** 身份证*/ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),/*** 手机号*/PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),/*** 地址*/ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));private final Function<String, String> desensitizer;SensitiveStrategy(Function<String, String> desensitizer) {this.desensitizer = desensitizer;}public Function<String, String> desensitizer() {return desensitizer;}
}
3) 定制JSON序列化实现
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;/*** 序列化注解自定义实现* JsonSerializer<String>:指定String 类型,serialize()方法用于将修改后的数据载入*/
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private SensitiveStrategy strategy;@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throwsIOException {gen.writeString(strategy.desensitizer().apply(value));}/*** 获取属性上的注解属性*/@Overridepublic JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throwsJsonMappingException {Sensitive annotation = property.getAnnotation(Sensitive.class);if (Objects.nonNull(annotation)&&Objects.equals(String.class,property.getType().getRawClass())) {this.strategy = annotation.strategy();return this;}return prov.findValueSerializer(property.getType(), property);}
}
4) 新增User类,并对需要脱敏的字段添加注解,并指定脱敏策略
import com.badao.demo.sensitive.Sensitive;
import com.badao.demo.sensitive.SensitiveStrategy;
import lombok.Data;
import java.io.Serializable;@Data
public class User implements Serializable {private static final long serialVersionUID = -5514139686858156155L;private Integer id;private Integer userId;@Sensitive(strategy = SensitiveStrategy.USERNAME)private String name;private Integer age;}
5) 编写controller进行测试
@RequestMapping("user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("save")public String save() {User user = new User();user.setUserId(new Random().nextInt( 1000 ) + 1);user.setName("badao"+user.getUserId());user.setAge(new Random().nextInt( 80 ) + 1);userService.insert(user);return "save success";}@RequestMapping("select")public User select() {List<User> all = userService.findAll();return all.size()>0?all.get(0):new User();}
}
6) 测试效果
其他更多的可参考:springboot项目中自定义注解的使用总结、java自定义注解实战(常用注解DEMO)_自定义方法注解demo-CSDN博客