一、使用场景
Springboot参数接收方式,提供了@RequestParam
、@RequestBody
,还有@RequestAttribute
、@PathVariable
。但是有的时候,接口入参只需要一个参数,如果将一个参数封装成一个对象显得有些麻烦且没有必要。我么可以通过自定义注解实现,参数的自动解析和赋值。
二、自定义注解解析接口入参
1. 添加自定义注解
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestSingleParam {
String value();
boolean required() default true;
String defaultValue() default "";
}
2. 定义参数解析器
import com.alibaba.fastjson.JSONObject;
import com.keyou.evm.common.annotation.RequestSingleParam;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
public class RequestSingleParamHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestSingleParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RequestSingleParam requestSingleParam = parameter.getParameterAnnotation(RequestSingleParam.class);
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
int rd;
while ((rd = reader.read(buf)) != -1) {
sb.append(buf, 0, rd);
}
JSONObject jsonObject = JSONObject.parseObject(sb.toString());
String value = requestSingleParam.value();
return jsonObject.get(value);
}
}
3. Spring注入参数解析器
Spring5.x可以添加配置类:
import com.keyou.evm.common.Handler.RequestSingleParamHandlerMethodArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* 使系统加载jar包外的文件
*/
@Configuration
public class ParameterConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){
argumentResolvers.add(new RequestSingleParamHandlerMethodArgumentResolver());
}
}
如果是Spring4.x以前的版本,则可以修改Springboot启动类:
- 继承WebMvcConfigurerAdapter;
- 重写addArgumentResolvers()方法。
@SpringBootApplication
@EnableWebMvc
public class SpringbootDemoApplication extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers){
argumentResolvers.add(new RequestSingleParamHandlerMethodArgumentResolver());
super.addArgumentResolvers(argumentResolvers);
}
}
三、多个入参优化
基于7-1我们发现在使用的过程中,假如代码中存在第二个参数,则使用@RequestSingleParam
就不能再次读取第二个参数了。读取第二个参数的时候会爆出空指针异常。
@PostMapping("/roles/active")
@ApiOperation(value = "用户切换角色")
@LogAnnotation(module = "user-center", recordRequestParam = true)
public CommonResult activeRole(@RequestSingleParam ("roleCode") String roleCode,@RequestSingleParam ("client") String client) throws ControllerException {
int i = userCertificateService.activeRole(roleCode, client);
if (i > 0) {
return CommonResult.success("切换角色成功!");
}
return CommonResult.failed("切换角色失败!");
}
根本原因
request请求流中数据只能读取一次。
我的解决方法
Request中的参数不能多次读取,但是Request还提供了一种参数方式Attribute,在请求传递的过程中,还可以添加或修改参数。所以基于这个原理,我们对RequestSingleParamHandlerMethodArgumentResolver
代码进行了优化:
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.keyou.evm.common.annotation.RequestSingleParam;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
public class RequestSingleParamHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestSingleParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RequestSingleParam requestSingleParam = parameter.getParameterAnnotation(RequestSingleParam.class);
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
String value = requestSingleParam.value();
JSONObject jsonObject ;
if (ObjectUtil.isNotEmpty(request.getAttribute("data"))){ //注意:入参不能为data
jsonObject = (JSONObject) request.getAttribute("data");
}else {
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
char[] buf = new char[1024];
int rd;
while ((rd = reader.read(buf)) != -1) {
sb.append(buf, 0, rd);
}
jsonObject = JSONObject.parseObject(sb.toString());
request.setAttribute("data",jsonObject);
}
return jsonObject.get(value);
}
}
四、使用方法
@ApiOperation("检查是否是第一次接收消息")
@PostMapping("/checkFirstByDay")
public CommonResult<Object> checkFirstByDay(@RequestSingleParam("phone") String phone){
boolean b = chatService.checkFirstByDay(phone);
if (b) {
return CommonResult.success(true,"消息发送成功");
}else {
return CommonResult.success(false,"24小时内已提醒过该用户");
}
}