继上一次处理了Json请求和地址参数的加密问题后,又出现了新的问题,后端在处理Get请求的时候,RequestParam的接收采用了两种模式,一种是直接在接口上平铺了所有的参数(平铺模式),一种是通过一个对象来接受参数(对象模式),由于是老系统,并且时间有限,直接改造所有接口的话时间够,于是考虑采用Spring内置的机制来进行处理。
由于上一次处理地址参数的处理经验,本次依旧考虑采用Spring的HandlerMethodArgumentResolver进行处理。
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null} if not resolvable
* @throws Exception in case of errors with the preparation of argument values
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
优先处理平铺模式,通过自定义的注解进行标记,然后从NativeWebRequest
中根据MethodParameter获取到的参数名称获取对应的参数值接口,解密之后直接返回对应的数据即可。
webRequest.getParameter(parameter.getParameterName())
然而实际测试的过程中发现parameter.getParameterName()
并不能获取到真正的参数名称,而是arg0
,arg1
这种,这样我们从webRequest中就无法获取需要处理的数据。
因此考虑将自己的注解与@RequestParam
注解进行结合,通过@RequestParam
获取属性的值,然而依旧存在一个问题,如果@RequestParam
不写属性值,也是可以映射上的。
因此再创建一个自定义注解,并且让这个注解的属性值为必填,以此来强制获取属性的名称,从而获取我们需要的值,平铺模式的参数就这么处理好了。
当平铺模式处理完之后,准备处理对象模式的参数,发现Spring提供了ServletModelAttributeMethodProcessor
来处理对象参数的映射操作,查其源码,发现在bindRequestParameters
中通过ServletRequestDataBinder
进行了参数了和对象的绑定操作
/**
* This implementation downcasts {@link WebDataBinder} to
* {@link ServletRequestDataBinder} before binding.
* @see ServletRequestDataBinderFactory
*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
Assert.state(servletRequest != null, "No ServletRequest");
ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;
servletBinder.bind(servletRequest);
}
一路深追ServletRequestDataBinder的bind方法
public void bind(ServletRequest request) {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
doBind(mpvs);
}
@Override
protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs);
super.doBind(mpvs);
}
protected void doBind(MutablePropertyValues mpvs) {
checkAllowedFields(mpvs);
checkRequiredFields(mpvs);
applyPropertyValues(mpvs);
}
最终发现是在applyPropertyValues方法中进行了参数的映射操作
protected void applyPropertyValues(MutablePropertyValues mpvs) {
try {
// Bind request parameters onto target object.
getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
}
catch (PropertyBatchUpdateException ex) {
// Use bind error processor to create FieldErrors.
for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
}
}
}
applyPropertyValues是protectd的,只要可以继承并重写此方法,就可以满足我们的解密要求。
于是去查看如何WebDataBinder是从何而来,发现是在ModelAttributeMethodProcessor类中由WebDataBinderFactory创建的
f (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
查看源码发现WebDataBinderFactory
是由RequestMappingHandlerAdapter
创建的,如果其进行修改,一旦有没有考虑到的地方影响会很大,因此考虑将Spring的ServletModelAttributeMethodProcessor复制出来,将里面的WebDataBinderFactory修改为自定义的,由自定义的WebDataBinderFactory创建自定义的ServletRequestDataBinder去进行数据处理。
首先创建自定义的数据绑定器CustomServletRequestDataBinder
public class CustomServletRequestDataBinder extends ServletRequestDataBinder {
public CustomServletRequestDataBinder(Object target) {
super(target);
}
public CustomServletRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
@Override
protected void applyPropertyValues(MutablePropertyValues mpvs) {
Object target = getTarget();
PropertyValue[] propertyValues = mpvs.getPropertyValues();
Map<String, PropertyValue> map = Arrays.stream(propertyValues).collect(Collectors.toMap(e -> e.getName(), e -> e));
Field[] declaredFields = target.getClass().getDeclaredFields();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(IdCrypto.class)) {
String name = declaredField.getName();
PropertyValue propertyValue = map.get(name);
mpvs.removePropertyValue(propertyValue);
mpvs.addPropertyValue(new PropertyValue(propertyValue,"999"));
}
}
super.applyPropertyValues(mpvs);
}
}
接着创建自定义的数据绑定器工厂CustomWebDataBinderFactory,让它创建我们的自定义绑定器
public class CustomWebDataBinderFactory extends DefaultDataBinderFactory {
/**
* Create a new {@code DefaultDataBinderFactory} instance.
*
* @param initializer for global data binder initialization
* (or {@code null} if none)
*/
public CustomWebDataBinderFactory(WebBindingInitializer initializer) {
super(initializer);
}
@Override
protected WebDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest) throws Exception {
return new CustomServletRequestDataBinder(target, objectName);
}
}
接着复制Spring的ServletModelAttributeMethodProcessor,由于ServletModelAttributeMethodProcessor继承了ModelAttributeMethodProcessor,并且创建绑定器的操作也是在ModelAttributeMethodProcessor中进行的,因此也需要ModelAttributeMethodProcessor复制一份CustomModelAttributeMethodProcessor,并给他传递自定义的绑定器的工厂CustomWebDataBinderFactory
public class CustomModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
protected final Log logger = LogFactory.getLog(getClass());
private final boolean annotationNotRequired;
private final CustomWebDataBinderFactory customWebDataBinderFactory;
......
......
}
将创建绑定器的地方修改为通过自定义的工厂创建
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
// 使用自定义工厂
attribute = createAttribute(name, parameter, customWebDataBinderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
// 使用自定义工厂
WebDataBinder binder = customWebDataBinderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
复制ServletModelAttributeMethodProcessor
public class CustomServletModelAttributeMethodProcessor extends CustomModelAttributeMethodProcessor{
/**
* Class constructor.
*
* @param customWebDataBinderFactory
* @param annotationNotRequired if "true", non-simple method arguments and
* return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation
*/
public CustomServletModelAttributeMethodProcessor(CustomWebDataBinderFactory customWebDataBinderFactory, boolean annotationNotRequired) {
super(customWebDataBinderFactory, annotationNotRequired);
}
......
......
}
最后注册到RequestMappingHandlerAdapter中
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@PostConstruct
public void init() {
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
List<HandlerMethodArgumentResolver> list = new ArrayList<>();
list.add(0, new IdWithPathVariableValueConvert());
list.add(1, new CustomServletModelAttributeMethodProcessor(new CustomWebDataBinderFactory(null), true));
list.add(2, new CustomeRequestParamAttributeMethodProcessor());
list.addAll(argumentResolvers);
requestMappingHandlerAdapter.setArgumentResolvers(list);
}
采用此方式注册的原因已在处理地址参数的时候说明,不再赘述。
后续发现无论是form-data的请求,还是application/json的请求,都会进入这里,而且平铺模式也会进行处理,于是对supportsParameter进行扩展
@Override
public boolean supportsParameter(MethodParameter parameter) {
Parameter[] parameters = parameter.getMethod().getParameters();
if (parameter == null || parameters.length == 0) {
return false;
}
long excludeParameterCount = Arrays.stream(parameters)
.filter(e -> !e.isAnnotationPresent(PathVariable.class))
.filter(e -> !ServletResponse.class.isAssignableFrom(e.getType()))
.filter(e -> !ServletRequest.class.isAssignableFrom(e.getType()))
.filter(e -> !MultipartFile.class.isAssignableFrom(e.getType())).count();
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(excludeParameterCount == 1 && this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()) && !parameter.hasParameterAnnotation(RequestBody.class)));
}
要求参数在排除了地址参数、ServletRequest、ServletResponse、MultipartFile的下只能有一个参数,并且不能含有RequestBody注解。
在后续的调试过程中发现,有的接口虽然只有一个参数,但是不是它并不是用的我们自己创建的类,比如使用的是 List,或者 String 这种,这种场景下也会进入到我们的自定义对象处理器中,
因此继续增加限制条件,要求这个单一的参数必须是由我们项目里创建的,根据标准的包命名规范,报名最开始采用公司域名反写,因此,检查参数的class全限定名称,检查其是否由公司域名反写开头。
public boolean supportsParameter(MethodParameter parameter) {
Parameter[] parameters = parameter.getMethod().getParameters();
if (parameter == null || parameters.length == 0) {
return false;
}
long excludeParameterCount = Arrays.stream(parameters)
.filter(e -> !e.isAnnotationPresent(PathVariable.class))
.filter(e -> !ServletResponse.class.isAssignableFrom(e.getType()))
.filter(e -> !ServletRequest.class.isAssignableFrom(e.getType()))
.filter(e -> !MultipartFile.class.isAssignableFrom(e.getType())).count();
boolean isCustomClass = parameter.getParameterType().getName().startsWith("xx.yyy.zzz");
return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
(excludeParameterCount == 1 && isCustomClass && this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()) && !parameter.hasParameterAnnotation(RequestBody.class)));
}
后续如要限制死为HttpGet请求,则在supportsParameter中判断Method的Http注解即可。