admin管理员组

文章数量:1794759

Springboot自定义注解实现参数验证

Springboot自定义注解实现参数验证

元注解参数解释

注解说明
@Target定义注解的作用目标,也就是可以定义注解具体作用在类上,方法上,还是变量上
@Retention定义注解的保留策略,RetentionPolicy.SOURCE:注解仅存在于源码中在class字节码文件中不包含RetentionPolicy.CLASS:默认的保留策略注解会在class字节码文件中存在但运行时无法获得;RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Document说明该注解将被包含在javadoc中
@Inherited说明子类可以继承父类中的该注解
@Target类型说明
ElementType.TYPE接口、类、枚举、注解
ElementType.FIELD字段、枚举的常量
ElementType.METHOD方法
ElementType.PARAMETER方法参数
ElementType.CONSTRUCTOR构造函数
ElementType.LOCAL_VARIABLE局部变量
ElementType.ANNOTATION_TYPE注解
ElementType.PACKAGE
自己实现注解

目标:在controller的方法里实现自己的参数校验功能,比如我搭建了一个博客系统,现在想做一个演示的功能,用户只能操作自己的数据和信,别人的他只能看,但是我现在有没有足够的时间去做(或者不想搞太高的复杂度)RABC,把权限传给前端让前端验证,那么简单处理就是在每个需要修改操作的接口加上验证,是否是自己的数据,每个接口写一次,是不是很烦。 那么咱们就使用注解简单搞一下。

1.定义注解

接收参数,方便校验使用.x新建注解类DomainOwner

package com.warmer.web.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DomainOwner { String domainIdPattern() default "";//传入的参数可能是id,也可能是编码,所以定义了两个参数 String domainPattern() default ""; } 2.定义切面

新建切面类DomainValidAspect,告诉spring切入点

package com.warmer.web.aspect; import com.warmer.base.enums.ReturnStatus; import com.warmer.base.util.R; import com.warmer.base.util.SpringUtils; import com.warmer.base.util.StringUtil; import com.warmer.web.annotation.AnnotationResolver; import com.warmer.web.annotation.DomainOwner; import com.warmer.web.entity.KgDomain; import com.warmer.web.security.TokenService; import com.warmer.web.service.KnowledgeGraphService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.List; @Aspect @Component public class DomainValidAspect { @Autowired TokenService tokenService; @Pointcut("@annotation(com.warmer.web.annotation.DomainOwner)") public void annotationPointcut() { } @Around("annotationPointcut()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); DomainOwner domainOwner = methodSignature.getMethod().getAnnotation(DomainOwner.class); //获取domainId表达式,(类似形参),可以是正则,可以是具体参数,下面使用的时候讲 String domainIdPattern = domainOwner.domainIdPattern(); String domainPattern = domainOwner.domainPattern(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //获取当前用户的数据列表,这两行是根据具体的业务,获取数据库中的数据,可以使用SpringUtils获取对象实例,也可以是@Autowried自动注入 KnowledgeGraphService kgService = SpringUtils.getBean(KnowledgeGraphService.class); List<KgDomain> myDomains = kgService.getDomainByUuid(tokenService.getLoginUserUuid(request)); //下面就是解析参数了,具体实现靠AnnotationResolver类,下面会贴出代码 AnnotationResolver annotationResolver = AnnotationResolver.newInstance(); String domain = ""; int domainId = 0; if(StringUtil.isNotBlank(domainIdPattern)&&StringUtil.isNotBlank(domainPattern)){ return R.create(ReturnStatus.NoRight, "没有权限"); } if(StringUtil.isNotBlank(domainIdPattern)){ domainId = (int) annotationResolver.resolver(joinPoint, domainIdPattern); int finalDomainId = domainId; if (myDomains.stream().filter(n -> n.getId()== finalDomainId).count() == 0) { return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据"); } } if(StringUtil.isNotBlank(domainPattern)){ domain = (String) annotationResolver.resolver(joinPoint, domainPattern); String finalDomain = domain; if (myDomains.stream().filter(n -> n.getName().equalsIgnoreCase(finalDomain)).count() == 0) { return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据"); } } return joinPoint.proceed(); } } 3.具体使用

我们定义注解的时候指定@Target({ElementType.METHOD}),作用在方法上,所以注解使用范围也只是方法

@Controller @RequestMapping(value = "/") public class UserManagerController { //1.传入的是一个具体的值 @DomainOwner(domainPattern = "#{userCode}") @ResponseBody @RequestMapping(value = "/getUserDetail") public R<String> getUserDetail(String userCode) { try { int domainId = (int) params.get("domainId"); //处理自己的业务 } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } return R.error("操作失败"); } //2.传入的是一个对象,这里User类就不贴了,属性userCode,userId之类的 @DomainOwner(domainPattern = "#{userItem.name}") @ResponseBody @RequestMapping(value = "/saveUser") public R<String> saveUser(@RequestBody User userItem) { try { //处理自己的业务 } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } return R.error("操作失败"); } //3.传入的可能是一个map,同样指定一个表达式 @DomainOwner(domainIdPattern = "#{params.domainId}") @ResponseBody @RequestMapping(value = "/saveNodeImage") public R<String> saveNodeImage(@RequestBody Map<String, Object> params) { try { int domainId = (int) params.get("domainId"); //处理自己的业务 } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } return R.error("操作失败"); } } AnnotationResolver类实现 package com.warmer.web.annotation; import java.lang.reflect.Method; import java.util.Map; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; /** * 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名} * 能解析类似#{userCode}或者#{userItem.name}或者{params.domainId} */ public class AnnotationResolver { private static AnnotationResolver resolver ; public static AnnotationResolver newInstance(){ if (resolver == null) { return resolver = new AnnotationResolver(); }else{ return resolver; } } /** * 解析注解上的值 * @param joinPoint * @param str 需要解析的字符串 * @return */ public Object resolver(JoinPoint joinPoint, String str) { if (str == null) return null ; Object value = null; if (str.matches("#\\\\{\\\\D*\\\\}")) {// 如果name匹配上了#{},则把内容当作变量 String newStr = str.replaceAll("#\\\\{", "").replaceAll("\\\\}", ""); if (newStr.contains(".")) { // 复杂类型 try { value = complexResolver(joinPoint, newStr); } catch (Exception e) { e.printStackTrace(); } } else { value = simpleResolver(joinPoint, newStr); } } else { //非变量 value = str; } return value; } private Object complexResolver(JoinPoint joinPoint, String str) throws Exception { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] names = methodSignature.getParameterNames(); Object[] args = joinPoint.getArgs(); String[] strs = str.split("\\\\."); for (int i = 0; i < names.length; i++) { if (strs[0].equals(names[i])) { Object obj = args[i]; //这里处理出入参数为Map的逻辑 if(obj instanceof Map){ Map item=(Map) obj; return item.get(strs[1]); } Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null); Object value = dmethod.invoke(args[i]); return getValue(value, 1, strs); } } return null; } private Object getValue(Object obj, int index, String[] strs) { try { if (obj != null && index < strs.length - 1) { Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null); obj = method.invoke(obj); getValue(obj, index + 1, strs); } return obj; } catch (Exception e) { e.printStackTrace(); return null; } } private String getMethodName(String name) { return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase()); } private Object simpleResolver(JoinPoint joinPoint, String str) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] names = methodSignature.getParameterNames(); Object[] args = joinPoint.getArgs(); for (int i = 0; i < names.length; i++) { if (str.equals(names[i])) { return args[i]; } } return null; } } 工具类SpringUtils

获取spring对象实例

import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringUtils implements ApplicationContextAware{ private static ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext arg0) throws BeansException { if (SpringUtils.applicationContext == null) { SpringUtils.applicationContext = arg0; } } // 获取applicationContext public static ApplicationContext getApplicationContext() { return applicationContext; } // 通过name获取 Bean. public static Object getBean(String name) { return getApplicationContext().getBean(name); } // 通过class获取Bean. public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } // 通过name,以及Clazz返回指定的Bean public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } } 补充

还有一点想说的,就是@Pointcut的用法,刚才我们写的是 @annotation:用于匹配当前执行方法持有指定注解的方法

/** * 切入点 * 设置切入点为标记为DomainOwner的地点 * AspectJ支持命名切入点,方法必须是返回void类型 */ @Pointcut("@annotation(com.warmer.web.annotation.DomainOwner)") public void annotationPointcut() { }

@Pointcut有12中用法,常用的还有@execution用于匹配方法执行的连接点,具体的参考下方博客

/** * 切入点 * 设置切入点为web层 * AspectJ支持命名切入点,方法必须是返回void类型 */ @Pointcut("execution(public * xxx.*.controller.*.*(..))") public void aopMethod() { } 参考博客

@Pointcut 的 12 种用法,你知道几种 java中注解的使用

本文标签: 自定义注解参数SpringBoot