自带的@RequestParam解析器的问题
一、@RequestParam参数默认有非空校验,非空校验我们当然要做,只是springboot的非空校验返回的是request的错误码,我需要此时请求响应码是200,而且需要根据不同接口返回不一样的提示。
二、@RequestParam参数不能接收post的body中的参数。
经过研究,可以通过自定义参数解析器解决这两个问题。
自定义注解Par
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Par {
String value();
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
自定义参数解析器
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver;
import javax.servlet.http.HttpServletRequest;
/**
* @author chenzy
* @since 2020-12-03
*/
public class MyArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(Par.class);
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
return new RequestParamNamedValueInfo(parameter.getParameterAnnotation(Par.class));
}
@Override
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
if (RequestInfo.instance().unInit()){
RequestInfo.instance().setPar(RequestUtil.getMap((HttpServletRequest) request.getNativeRequest()));
}
return RequestInfo.instance().get(name.strip(),parameter.getParameterType());
}
private static class RequestParamNamedValueInfo extends NamedValueInfo {
public RequestParamNamedValueInfo() {
super("", false, ValueConstants.DEFAULT_NONE);
}
public RequestParamNamedValueInfo(Par annotation) {
super(annotation.value(), false, annotation.defaultValue());
}
}
}
MyArgumentResolver .resolveName中request.getParameter(name); 只能获取url后的参数,body中的数据无法获取。
所以在RequestUtil.getMap(HttpServletRequest req)中获取所有数据。代码如下,
/**
* request 请求参数
*/
public static StringMap<String> getMap(HttpServletRequest req) {
StringMap<String> par = new StringMap<>();
/*获取请求头参数*/
req.getParameterMap().forEach((key,values)-> par.put(key.strip(), StringUtil.join(values).strip()));
/*获取body参数*/
par.putAll(getPost(req));
return par;
}
private static StringMap<String> getPost(HttpServletRequest req) {
if (req.getContentLength()< 1) {
return StringMap.empty();
}
try(var reader=req.getInputStream()){
/*使用@RequestBody注解后,不能再读取请求体数据*/
if (reader.isFinished()){
return StringMap.empty();
}
var json = new String(reader.readAllBytes(), Const.charset);
if (json.length() < 1) {
return StringMap.empty();
}
return StringMap.translate(JsonUtil.str2Model(json, Map.class));
} catch (IOException e) {
e.printStackTrace();
return StringMap.empty();
}
}
StringMap是我自定义的map类,是HashMap<String,Object>的子类,可用HashMap<String,String>替代StringMap<String>
JsonUtil是自己对json操作的工具类,在这不赘述
RequestInfo是单例对象,核心代码在下面,懂得都懂。
public class RequestInfo {
private RequestInfo() {
}
private ThreadLocalMapPar par = new ThreadLocalMapPar();
/*判断是否已经把请求信息存入*/
public boolean unInit() {
return !get("alreadyInit", Boolean.class, false);
}
public <T> T get(String name, Class<T> tClass, T defaultValue) {
return par.get(name, tClass, defaultValue);
}
public <T> T get(String name, Class<T> tClass) {
return par.get(name, tClass);
}
public void setPar(StringMap<String> par) {
this.par.putALL(par);
this.par.put("alreadyInit", true);
}
public void clear() {
par.clear();
}
}
/**
* @author chenzy
* @since 2020-12-03
* 线程变量池
*/
public class ThreadLocalMapPar extends InheritableThreadLocal<StringMap<?>> {
@Override
protected StringMap<?> childValue(StringMap<?> parentValue) {
if (parentValue == null) {
return null;
}
return new StringMap<>(parentValue);
}
public StringMap<?> getCopyMap() {
return (StringMap<?>) getMap().clone();
}
public <T> ThreadLocalMapPar put(String key, T val) {
if (StringUtil.isBlankOr(key, val)) {
return this;
}
StringMap map = getMap();
map.add(key, val);
return this;
}
public <T> T get(String key, Class<T> tClass,T defaultValue) {
if (StringUtil.isBlank(key)) {
return null;
}
StringMap map = getMap();
Object value = map.get(key);
if (value == null) {
return defaultValue;
}
if (ClassUtil.isType(value, tClass)) {
return tClass.cast(value);
}
return JsonUtil.model2Model(value, tClass);
}
public <T> T get(String key, Class<T> tClass) {
return get(key,tClass,null);
}
public String get(String key) {
if (StringUtil.isBlank(key)) {
return "";
}
StringMap map = getMap();
Object value = map.get(key);
return value == null ? "" : JsonUtil.model2Str(value);
}
public ThreadLocalMapPar putALL(StringMap<?> par) {
if (par==null) {
return this;
}
StringMap map = getMap();
map.putAll(par);
return this;
}
public boolean containsKey(String key) {
return getMap().containsKey(key);
}
private StringMap<?> getMap(){
StringMap map = this.get();
if (map == null) {
map = new StringMap();
this.set(map);
}
return map;
}
public void clear(){
getMap().clear();
}
}
切面 清除线程变量
需要注意的是,在切面中需要clear掉线程变量。以下是切面代码
public static AspectJExpressionPointcutAdvisor getAdvisor(String expression){
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(expression);
advisor.setOrder(1);
advisor.setAdvice((MethodInterceptor) invocation -> {
try {
/*参数存入RequestInfo*/
HttpServletRequest request = (HttpServletRequest) RequestContextHolder.getRequestAttributes().resolveReference(RequestAttributes.REFERENCE_REQUEST);
if (RequestInfo.instance().unInit()){
RequestInfo.instance().setPar(RequestUtil.getMap(request));
}
//执行业务方法
return invocation.proceed();
} catch (Throwable e) {
e.printStackTrace();
}finally {
/*线程在线程池中复用,每个请求结束后需要把线程变量清掉。*/
RequestInfo.instance().clear();
}
});
return advisor;
}
@Bean
public AspectJExpressionPointcutAdvisor dataAspect(@Value("${spring.application.name}") String rootPath) {
return AspectUtil.getAdvisor("execution(public * com.zhilei."+rootPath.replace("/","")+".*.control..*.*(..))");
}
切面中也需要RequestInfo.instance().setPar()是因为MyArgumentResolver的参数注入只有在controller接口参数有加@Par注解才会生效,没有@Par参数时需要在切面中setPar()。
注册参数解析器
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new MyArgumentResolver());
}
}
使用线程变量的好处
1、业务比较复杂时,可以减少方法的参数传递,确保方法只有一两个参数,使代码优雅起来。
只需要在线程结束时把线程变量clear掉 即可避免线程变量的泄漏
注意事项
若使用@RequestBody注解,则RequestUtil.getMap(request)无法获取数据,因为outPutStream只能读取一次。
本文介绍了如何通过自定义注解和参数解析器解决Spring Boot中@RequestParam注解带来的问题,如非空校验响应码和接收POST body参数。作者创建了名为@Par的自定义注解,并实现了一个名为MyArgumentResolver的参数解析器,同时使用线程变量RequestInfo来存储请求参数,以在多个方法间共享。此外,文中还展示了如何在切面中清除线程变量,以及配置参数解析器。

1170

被折叠的 条评论
为什么被折叠?



