自定义注解-(属性、类 方法)
大家对注解应该已经不会陌生了,但是往往在开发中已有的注解满足不了我们的业务需求时,就需要我们自定义注解来完成我们的工作;
从注解使用位置可以分为 属性、方法和类 下面就分别从这两个方面提供一些例子,来分析一下如何自定义注解:
一、属性注解
一般在实体类中使用的字段注解有:@NotNull 、 @Range(min = 20 , max = 99) 等都可以对实体类的字段值进行验证,举个例子:如果我们有需求传入的字段信息需要是一个集合中的元素,这时就没有注解可以满足了、这里我们需要自定义注解;
1、定义一个 @
CheckField 注解,通过 @
interface声明一个注解:
package com.dongl.utils.annotation; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author D-L * @date 2020-08-24 * * @Constraint 通过使用validatedBy来指定与注解关联的验证器 * * @Retention 用来说明该注解类的生命周期。 * * RetentionPolicy.SOURCE: 注解只保留在源文件中 * RetentionPolicy.CLASS : 注解保留在class文件中,在加载到JVM虚拟机时丢弃 * RetentionPolicy.RUNTIME: 注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解。 */ @Target({ ElementType.FIELD}) //只允许用在类的字段上 @Retention(RetentionPolicy.RUNTIME) //注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解 @Constraint(validatedBy = VerificationField.class) public @interface CheckField { /** * 合法的参数值 * */ String[] fieldValues(); /** * 提示信息 * */ String message() default "参数不为指定值"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2、参数验证逻辑类:
package com.dongl.utils.annotation; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.Arrays; import java.util.List; /** * @author D-L * @date 2020-08-24 * * 需要实现 ConstraintValidator 泛型接口 * * 第一个泛型参数类型 CheckField:注解,第二个泛型参数 Object:校验字段类型。 * 需要实现 initialize 和 isValid 方法,isValid 方法为校验逻辑,initialize 方法初始化工作 */ public class VerificationField implements ConstraintValidator<CheckField, Object> { /** * 从注解中获取合法的参数值 */ private List<String> paramValues; @Override public void initialize(CheckField checkField) { //初始化时获取注解上的值 paramValues = Arrays.asList(checkField.fieldValues()); } public boolean isValid(Object o, ConstraintValidatorContext context) { if (paramValues.contains(o)) { return true; } //自定义的参数列表中不存在 return false; } }
3、建一个实体类,使用注解@CheckField
package com.dongl.bean.mybean; import com.dongl.utils.annotation.CheckField; import org.hibernate.validator.constraints.Range; import javax.validation.constraints.NotNull; import java.util.Date; public class User { /**主键*/ private Long id; /**姓名*/ @NotNull private String name; /**性别*/ @CheckField(fieldValues = {"男", "女"}) private String sex; /**出生日期*/ private Date birthDay; /**年龄*/ @Range(min = 20 , max = 99) private Short age; /**详细地址*/ private String address; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthDay() { return birthDay; } public void setBirthDay(Date birthDay) { this.birthDay = birthDay; } public Short getAge() { return age; } public void setAge(Short age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", birthDay=" + birthDay + ", age=" + age + ", address='" + address + '\'' + '}'; } }
4、测试方法:
package com.dongl.controller; import com.dongl.bean.mybean.User; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** * @author D-L * @Classname AnnotationController * @Version 1.0 * @Description 测试自定义注解 controller * @Date 2020/8/24 */ @RestController @RequestMapping("Annotation") public class AnnotationController { @PostMapping(value = "user") public String test(@Validated @RequestBody User user) { System.out.println(user); return "do something you like ------"; } }
二、方法、类注解
<一> 权限注解(注解+拦截器)
1、自定义一个注解
package com.dongl.utils.permission; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author D-L * @Classname CheckPermission * @Version 1.0 * @Description 自定义注解 检查权限 * @Date 2020/8/24 */ @Target({ ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckPermission { /** * 用户名 */ String userName() default ""; /** * 用户code */ String userCode() default ""; /** * 提示信息 * */ String message() default "当前登录人权限不足"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
2、定义一个类继承HandlerInterceptorAdapter,并重写方法
拦截器适配器HandlerInterceptorAdapter 、HandlerInterceptor
在HandlerInterceptorAdapter中主要提供了以下的方法:
- preHandle:在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行,也就是说我们想调用的方法 不会被执行,但是你可以修改response为你想要的响应。
- postHandle:在方法执行后调用。
- afterCompletion:在整个请求处理完毕后进行回调,也就是说视图渲染完毕或者调用方已经拿到响应。
package com.dongl.utils.permission; import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author D-L * @Classname AnnotationController * @Version 1.0 * @Description 拦截器类 * @Date 2020/8/24 * * * 在HandlerInterceptorAdapter中主要提供了以下的方法: * preHandle:在方法被调用前执行。在该方法中可以做类似校验的功能。如果返回true,则继续调用下一个拦截器。如果返回false,则中断执行,也就是说我们想调用的方法 不会被执行,但是你可以修改response为你想要的响应。 * postHandle:在方法执行后调用。 * afterCompletion:在整个请求处理完毕后进行回调,也就是说视图渲染完毕或者调用方已经拿到响应。 */ public class CheckPermissionInterceptor extends HandlerInterceptorAdapter { /** * 处理器处理之前调用 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HandlerMethod handlerMethod = (HandlerMethod)handler; CheckPermission permission = findCheckPermission(handlerMethod); //获取注解中的值 String userName = null; String userCode = null; if(permission != null) { userName = permission.userName(); userCode = permission.userCode(); } //如果没有添加权限注解则直接跳过允许访问 if (StringUtils.isEmpty(userName) && StringUtils.isEmpty(userCode)) { return true; } /**这里校验是否有权限一般会到数据库中 或者redis中获取权限配置数据 查看此用户是否具有此权限 这里为了测试 就直接写死了*/ if (! "admin".equals(userName) && "admin".equals(userCode)) { System.out.println("拦截器1--------------------"); response.getOutputStream().write(permission.message().getBytes()); return false; } 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 { //在整个请求处理完毕后进行回调,也就是说视图渲染完毕或者调用方已经拿到响应 //日志肯定是在afterCompletion之后记录的,否则中途失败了,也记录了,那就扯淡了。 // 一定是程序正常跑完后,我们记录下那些对数据库做个增删改的操作日志进数据库。 // 所以我们只需要继承HandlerInterceptorAdapter,并重写afterCompletion一个方法即可, // 因为preHandle默认是true。 } /** * 处理器处理之前调用 */ @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } /** * 根据handlerMethod返回注解信息 * * @param handlerMethod 方法对象 * @return PermissionCheck注解 */ private CheckPermission findCheckPermission(HandlerMethod handlerMethod) { //在方法上寻找注解 CheckPermission permission = handlerMethod.getMethodAnnotation(CheckPermission.class); if (permission == null) { //在类上寻找注解 permission = handlerMethod.getBeanType().getAnnotation(CheckPermission.class); } return permission; } }
2、实现WebMvcConfigurer配置拦截器
package com.dongl.utils.permission; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author D-L * @Classname AnnotationController * @Version 1.0 * @Description 实现WebMvcConfigurer配置拦截器 * @Date 2020/8/24 * * WebMvcConfigurerAdapter 抽象类是对WebMvcConfigurer接口的简单抽象(增加了一些默认实现), * 但在在SpringBoot2.0及Spring5.0中WebMvcConfigurerAdapter已被废弃 。 * 官方推荐直接实现WebMvcConfigurer或者直接继承WebMvcConfigurationSupport */ @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //多个拦截器会按照顺序进行校验拦截 内部维护了一个list集合 registrations //private final List<InterceptorRegistration> registrations = new ArrayList<>(); registry.addInterceptor(new CheckPermissionInterceptor()); //registry.addInterceptor(new CheckPermissionHandler()); } }
3、功能测试
package com.dongl.controller; import com.dongl.bean.mybean.User; import com.dongl.utils.permission.CheckPermission; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** * @author D-L * @Classname AnnotationController * @Version 1.0 * @Description 测试自定义注解 controller * @Date 2020/8/24 */ @RestController @RequestMapping("Annotation") public class AnnotationController { @CheckPermission(userCode = "admin" ,userName = "admin") @PostMapping(value = "user") public String test(@Validated @RequestBody User user) { System.out.println(user); return "do something you like ------"; } }
<二> 缓存注解(注解+切面)
因为缓存注解需要在方法执行之前有返回值,所以没有通过拦截器处理这个注解,而是通过使用切面在执行方法之前对注解进行处理。如果注解没有返回值,将会返回方法中的值。
1、自定义注解
package com.dongl.utils.cache; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author D-L * @Classname CustomCacheAspect * @Version 1.0 * @Description 自定义注解 * @Date 2020/8/24 */ @Target({ ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CustomCache { /** * 缓存的key值 * */ String key(); }
2、自定义切面类
package com.dongl.utils.cache; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; /** * @author D-L * @Classname CustomCacheAspect * @Version 1.0 * @Description 测试自定义注解 切面类 * @Date 2020/8/24 */ @Aspect @Component public class CustomCacheAspect { /** * 在方法执行之前对注解进行处理 * * @param joinPoint * @param customCache 注解 * @return 返回中的值 */ @Around("@annotation(com.dongl.utils.cache.CustomCache) && @annotation(customCache)") public Object dealProcess(ProceedingJoinPoint joinPoint, CustomCache customCache) { Object result = null; if (customCache.key() == null) { //TODO throw error } //TODO 业务场景会比这个复杂的多,会涉及参数的解析如key可能是#{id}这些,数据查询 //TODO 这里做简单演示,如果key为testKey则返回 do something you like --- if ("testKey".equals(customCache.key())) { return "do something you like ---"; } //执行目标方法 开始执行业务代码 try { result = joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } //如果没返回值 接口也不会有返回值 return result; } }
3、测试方法
package com.dongl.controller; import com.dongl.utils.cache.CustomCache; import org.springframework.web.bind.annotation.*; /** * @author D-L * @Classname AnnotationController * @Version 1.0 * @Description 测试自定义注解 controller * @Date 2020/8/24 */ @RestController @RequestMapping("Annotation") public class AnnotationController { @GetMapping("cache") @CustomCache(key = "testKey") public Object testCustomCache() { return "don't hit cache -------------"; } }