SpringBoot 自定义注解 + AOP实现参数效验,默认值赋值

SpringBoot 自定义注意 + AOP实现参数效验,默认值赋值,和@RequestParam相同功能

写自定义注解的意义在于,@RequestParam没有对参数值进行效验,如空值;

经过测试发现,@RequestParam只对本次请求中带不带参数名进行了效验,如参数是?userName=@RequestParam则会放行,只有当userName不存在参数列表中是,才会提示报错,这就会导致很多问题,需要后台每次对数据都进行空值判断;

又或者前端人员参数为定义,传入了一个undefined,那造成的麻烦更多,所以自定义注解 + AOP来解决这个问题。

开发的这个自定义注解,能实现和@RequestParam一样的功能,但是进行了加强,对参数空值或者undefined进行效验,还可以实现默认值赋值

自定义注解

自定义注解相比怎么写应该都知道,其实就是一个接口,在接口上添加一个其他注解,实现了自己的注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Retention 作用:标识此注解的生命周期,有三种可选值
 * 1.RetentionPolicy.SOURCE:在源文件中有效(即源文件保留)
 * 2.RetentionPolicy.CLASS:在class文件中有效(即class保留)
 * 3.RetentionPolicy.RUNTIME:在运行时有效(即运行时保留)
 *
 *  @Target 作用:标识此注解能用在什么地方
 * 1.ElementType.CONSTRUCTOR:用于构造器
 * 2.ElementType.FIELD:用于属性
 * 3.ElementType.LOCAL_VARIABLE:用于局部变量
 * 4.ElementType.METHOD:用于方法
 * 5.ElementType.PACKAGE:用于包
 * 6.ElementType.PARAMETER:用于参数
 * 7.ElementType.TYPE:用于类、接口(包括注解类型) 或enum声明
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamCheck {
    /**
     * 是否非空,默认不能为空
     */
    boolean notNull() default true;

    /**
     * 默认值
     * @return
     */
    String defaultValue() default "";
}

自定义注解是写好了,Spring Boot项目中带有了AOP的jar包,可以不用引入,接下来写实现逻辑

用AOP实现注解功能

import com.hm.common.util.cast.CastValueTypeUtil;
import com.hm.common.util.exception.ParamIsNullException;
import com.hm.spss.annotation.ParamCheck;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * 参数效验AOP
 */
@Component
@Aspect
public class ParamCheckAOP {

    /**
     * 定义有一个切入点,范围为web包下的类
     */
    @Pointcut("execution(public * com.hm.spss.controller..*.*(..))")
    public void checkParam() {
    }

    /**
     * 方法执行前执行
     * @param joinPoint
     */
    @Before("checkParam()")
    public void doBefore(JoinPoint joinPoint) {
    }

    @Around("checkParam()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        MethodSignature signature = ((MethodSignature) pjp.getSignature());
        //得到拦截的方法
        Method method = signature.getMethod();
        //获取方法参数注解,返回二维数组是因为某些参数可能存在多个注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        if (parameterAnnotations == null || parameterAnnotations.length == 0) {
            return pjp.proceed();
        }
        //获取方法参数名
        String[] paramNames = signature.getParameterNames();
        //获取参数值
        Object[] paranValues = pjp.getArgs();
        //获取方法参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (int j = 0; j < parameterAnnotations[i].length; j++) {
                //如果该参数前面的注解不为空并且是ParamCheck的实例,并且notNull()=true,并且默认值为空,则进行非空校验
                if (parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && ((ParamCheck) parameterAnnotations[i][j]).notNull() && StringUtils.isEmpty(((ParamCheck)parameterAnnotations[i][j]).defaultValue())) {
                    paramIsNull(paramNames[i], paranValues[i], parameterTypes[i] == null ? null : parameterTypes[i].getName());
                    break;
                }
                //如果该参数前面的注解不为空并且是ParamCheck的实例,并且默认值不为空,并且参数值为空,则进行赋默认值
                if(parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && !StringUtils.isEmpty(((ParamCheck)parameterAnnotations[i][j]).defaultValue()) && (paranValues[i] == null || StringUtils.isEmpty(paranValues[i].toString()))){
                    paranValues[i] = putParam(((ParamCheck)parameterAnnotations[i][j]).defaultValue(), parameterTypes[i]);
                }
            }
        }
        return pjp.proceed(paranValues);
    }

    /**
     * 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
     * TODO 留着做以后处理返回值用
     * @param joinPoint
     */
    @AfterReturning(value = "checkParam()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
      
    }

    /**
     * 参数非空校验,如果参数为空,则抛出ParamIsNullException异常
     * @param paramName
     * @param value
     * @param parameterType
     */
    private void paramIsNull(String paramName, Object value, String parameterType) {
        if (value == null || "".equals(value.toString().trim())) {
            throw new ParamIsNullException(paramName, parameterType, ",不能为空!");
        }
        if ("undefined".equals(value.toString().trim())){
            throw new ParamIsNullException(paramName, parameterType, ",不能为undefined!");
        }
    }

    private Object putParam(Object value, Class<?> parameterType){
        return CastValueTypeUtil.parseValue(parameterType, value.toString());
    }

}

其中有两个Java类,一个是Value转换,一个是自定义异常,转换类的意义在于,默认值在赋予的时候是String字符串,但是实际赋值的时候,给参数赋值,是不确定元素,可以确实的是,这个参数肯定是八大类型中的其中一个

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 转换Object类型
 *
 */
public class CastValueTypeUtil {

    public static Object parseValue(Class<?> parameterTypes, String value) {

        if(value==null || value.trim().length()==0){
            return null;
        }
        value = value.trim();

		if (Byte.class.equals(parameterTypes) || Byte.TYPE.equals(parameterTypes)) {
			return parseByte(value);
		} else if (Boolean.class.equals(parameterTypes) || Boolean.TYPE.equals(parameterTypes)) {
            return parseBoolean(value);
        }/* else if (Character.class.equals(fieldType) || Character.TYPE.equals(fieldType)) {
			 return value.toCharArray()[0];
		}*/ else if (String.class.equals(parameterTypes)) {
            return value;
        } else if (Short.class.equals(parameterTypes) || Short.TYPE.equals(parameterTypes)) {
            return parseShort(value);
        } else if (Integer.class.equals(parameterTypes) || Integer.TYPE.equals(parameterTypes)) {
            return parseInt(value);
        } else if (Long.class.equals(parameterTypes) || Long.TYPE.equals(parameterTypes)) {
            return parseLong(value);
        } else if (Float.class.equals(parameterTypes) || Float.TYPE.equals(parameterTypes)) {
            return parseFloat(value);
        } else if (Double.class.equals(parameterTypes) || Double.TYPE.equals(parameterTypes)) {
            return parseDouble(value);
        } else if (Date.class.equals(parameterTypes)) {
            return parseDate(value);
        } else {
            throw new RuntimeException("request illeagal type, type must be Integer not int Long not long etc, type=" + parameterTypes);
        }
    }

    public static Byte parseByte(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Byte.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseByte but input illegal input=" + value, e);
        }
    }

    public static Boolean parseBoolean(String value) {
        value = value.replaceAll(" ", "");
        if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
            return Boolean.TRUE;
        } else if (Boolean.FALSE.toString().equalsIgnoreCase(value)) {
            return Boolean.FALSE;
        } else {
            throw new RuntimeException("parseBoolean but input illegal input=" + value);
        }
    }

    public static Integer parseInt(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Integer.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseInt but input illegal input=" + value, e);
        }
    }

    public static Short parseShort(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Short.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseShort but input illegal input=" + value, e);
        }
    }

    public static Long parseLong(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Long.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseLong but input illegal input=" + value, e);
        }
    }

    public static Float parseFloat(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Float.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseFloat but input illegal input=" + value, e);
        }
    }

    public static Double parseDouble(String value) {
        try {
            value = value.replaceAll(" ", "");
            return Double.valueOf(value);
        } catch(NumberFormatException e) {
            throw new RuntimeException("parseDouble but input illegal input=" + value, e);
        }
    }

    public static Date parseDate(String value) {
        try {
            String datePattern = "yyyy-MM-dd HH:mm:ss";
            SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
            return dateFormat.parse(value);
        } catch(ParseException e) {
            throw new RuntimeException("parseDate but input illegal input=" + value, e);
        }
    }

}

自定义异常类

public class ParamIsNullException extends RuntimeException {
    private final String parameterName;
    private final String parameterType;
    private final String message;

    public ParamIsNullException(String parameterName, String parameterType, String message) {
        super();
        this.parameterName = parameterName;
        this.parameterType = parameterType;
        this.message = message;
    }

    @Override
    public String getMessage() {
        return "请求参数类型:" + this.parameterType + ",参数名: \'" + this.parameterName + message;
    }

    public final String getParameterName() {
        return this.parameterName;
    }

    public final String getParameterType() {
        return this.parameterType;
    }
}

以上就是完成了,非常的简单,接下来看如何使用

@GetMapping("/getRisk")
    public Result getRisk(@ParamCheck(defaultValue = "1") Integer page,
                          @ParamCheck(defaultValue = "10") Integer size,
                          @ParamCheck(notNull = false) String worksName) {
        
        return ResultUtil.success();
    }

结语

当参数名传了,参数值没传时,会抛出异常返回,参数名没传也一样的效果,并且也可以赋予默认值,具体看代码逻辑实现,也可以自行修改,只描述个大概。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要通过自定义注解AOP实现Spring Security配置指定接口不需要Token才能访问,可以按照以下步骤进行操作: 1. 创建一个自定义注解,例如`@NoTokenRequired`,用于标识不需要Token的接口。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoTokenRequired { } ``` 2. 创建一个切面类,用于拦截带有`@NoTokenRequired`注解的方法,并跳过Spring Security的Token验证。 ```java @Aspect @Component public class TokenValidationAspect { @Before("@annotation(com.example.NoTokenRequired)") public void skipTokenValidation(JoinPoint joinPoint) { // 跳过Spring Security的Token验证逻辑 SecurityContextHolder.getContext().setAuthentication(null); } } ``` 3. 配置Spring Security,将AOP切面类添加到Spring Security的配置中。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private TokenValidationAspect tokenValidationAspect; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 配置需要Token验证的接口 .anyRequest().authenticated() .and() .csrf().disable(); // 将AOP切面类添加到Spring Security的配置中 http.addFilterBefore(tokenValidationAspect, UsernamePasswordAuthenticationFilter.class); } } ``` 4. 在需要不需要Token验证的接口上,添加`@NoTokenRequired`注解。 ```java @RestController public class ExampleController { @NoTokenRequired @GetMapping("/example") public String example() { return "This API does not require Token"; } } ``` 这样配置之后,带有`@NoTokenRequired`注解的接口将不会进行Spring Security的Token验证,即可在没有Token的情况下访问该接口。其他接口仍然需要进行Token验证。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值