背景 :
前端请求中的参数一直映射不到自定义的Bean中。于是便想自己看下这个转换的过程。
1.调研
当然已经有很多大佬进行过自己的分析。好文章一大摞,但是找到适合自己的,自己看的有意思的却很费劲。我这里先分享几个:
1)Spring序列化与反序列化设计探究
2)SpringMVC对象绑定时自定义名称对应关系
3)Tomcat各组件分析
2.实现
- 具体代码实现我抄了下第二篇文章。在最后注入argument-resolvers时有了一点自己的体会,这个内容之后分析下。
- 我觉得我们可以先看下第三篇文章对Tomcat的结构有一个大概的印象。
- 开始研究dispatchservlet中执行代码
3.dispatchservlet 代码执行逻辑分析
- doDispatch方法
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//根据request请求路径找到对应的requestMapping方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//做一些handle表象之外的额外事情
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
- handlerAdapter 的handle方法
看adapter的实现类,我们以RequestMappingHandlerAdapter为例,进入到handle方法中:
@Override
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
……
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
……
return mav;
}
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
……
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
……
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
//顺着方法一直进入到
private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
终于看到了和我们上一步加的resolver相关的代码了。接着看会儿源码:
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
//接口
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
我们首先看下都有哪些实现类吧:
是不是感觉一些实现类很眼熟。是的,就是我们经常使用的一些注解。我们先看下这些实现类的继承结构吧:
我们找几个实现类看下,觉得每个实现类都是对注解所达的含义进行相关值的解析。那么我们不使用任何注解,是如何进行解析操作的呢,那么就到了我们一开始的问题了:
@Override
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
……
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
……
}
// Add resolved attribute and BindingResult at the end of the model
……
return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
这里我们看的主要方法是bindRequestParameters,光看名字就觉的自己懂了。这个方法咱们自定义的resolver方法同样也实现了这个方法。我们就接着跟代码:
//由对应的dataBinder类调用bind方法:
camelBinder.bind(request.getNativeRequest(ServletRequest.class));
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);
}
到这里已经可以叫好了,我们自己的dataBinder重写了这个addBindValues方法。所以咱们自定义实体类的映射过程就大概如此。咱们再回到父类的resolveArgument方法中,方法最后还执行了convertIfNecessary方法,这个过程是进行了一些数据转换,如时间转换等和验证逻辑。
4.回顾
Q:为什么一开始我自定义的resolver没有注册成功呢,在argumentResolvers列表中没有看到呢?
@Configuration
public class WebContextConfiguration extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(processor());
}
}
}
A: 因为我这里是spring+springmvc项目,controller的实例化是放在springmvc容器中的。 这里涉及到父子容器的问题,也经常遇到事务相关问题。最后解决也是由配置:
<!-- 使用Annotation自动注册Bean,只扫描@Controller -->
<context:component-scan base-package="com.zhzg.appcore"
use-default-filters="false"><!-- base-package 如果多个,用“,”分隔 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 默认的注解映射的支持,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager">
<mvc:argument-resolvers>
<bean class="com.zhzg.appcore.common.servlet.SnakeToCamelModelAttributeMethodProcessor">
<constructor-arg name="annotationNotRequired" value="true"/>
</bean>
</mvc:argument-resolvers>
</mvc:annotation-driven>