springMVC 源码级别学习

本文详细解析了Spring MVC的工作原理,包括DispatcherServlet的核心逻辑、九大组件及其初始化过程,以及请求处理、视图渲染和数据绑定的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

DispatcherServlet核心逻辑

DispatcherServlet处理逻辑如下。

  1. 获得对应handler,即自定义的controller
  2. 获得对应的handler adapter
  3. 适配器调用用户自定义方法,返回model和view
  4. 处理和显示视图
  5. 跳转页面

 

 

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
       ModelAndView mv = null;
       Exception dispatchException = null;
       try {
            // 1. 检查是否上传文件
           processedRequest = checkMultipart(request);
           multipartRequestParsed = (processedRequest != request);
            // 2. 获得handler,即对应的处理器
           // Determine handler for the current request.
           mappedHandler = getHandler(processedRequest);
           if (mappedHandler == null) {
              noHandlerFound(processedRequest, response);
              return;
           }
            // 3. 获得适配器,用于组合使用处理器
           // Determine handler adapter for the current request.
           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 (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                  return;
              }
           }
           if (!mappedHandler.applyPreHandle(processedRequest, response)) {
              return;
           }
            //4.  适配器调用用户自定义方法,返回model和view
           // Actually invoke the handler.
           mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
           if (asyncManager.isConcurrentHandlingStarted()) {
              return;
           }
            // 5. 处理视图
           applyDefaultViewName(processedRequest, mv);
           mappedHandler.applyPostHandle(processedRequest, response, mv);
       }
       catch (Exception ex) {
           dispatchException = ex;
       }
       catch (Throwable err) {
           // As of 4.3, we're processing Errors thrown from handler methods as well,
           // making them available for @ExceptionHandler methods and other scenarios.
           dispatchException = new NestedServletException("Handler dispatch failed", err);
       }
        // 6. 处理跳转
       processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
       triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
       triggerAfterCompletion(processedRequest, response, mappedHandler,
              new NestedServletException("Handler processing failed", err));
    }
    finally {
       if (asyncManager.isConcurrentHandlingStarted()) {
           // Instead of postHandle and afterCompletion
           if (mappedHandler != null) {
              mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
           }
       }
       else {
           // Clean up any resources used by a multipart request.
           if (multipartRequestParsed) {
              cleanupMultipart(processedRequest);
           }
       }
    }
}

getHandler

来看getHandler方法

方法遍历几个handlerMappings,尝试从这几个映射map中取出request中地址对应的controller
注意这几个mappings底层实际上就是linkedHashMap。

  • RequestMappingHandler
  • BeanNameUrlHandlerMapping
  • SimpleUrlHandlerMapping

从名字可以看出,就是对应着几个基础的容器的mapping。

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
       for (HandlerMapping mapping : this.handlerMappings) {
           HandlerExecutionChain handler = mapping.getHandler(request);
           if (handler != null) {
              return handler;
           }
       }
    }
    return null;
}

getAdapter

同理。

同理遍历几个适配器,找到合适的handler的适配器。这里应该使用了适配器模式,为不同的handler实现了不同的适配器接口

可以进行统一的调用。

适配器包括

  • HttpRequestHandlerAdapter
  • SimpleControllerHandlerAdapter
  • RequestMappingHandlerAdapter


protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
       for (HandlerAdapter adapter : this.handlerAdapters) {
           if (adapter.supports(handler)) {
              return adapter;
           }
       }
    }
    throw new ServletException("No adapter for handler [" + handler +
           "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

// handlerAdapter接口
public interface HandlerAdapter {
    boolean supports(Object handler);
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    long getLastModified(HttpServletRequest request, Object handler);

}

processDispatchResult

//调用该方法进行渲染视图
render(mv, request, response);

SpringMVC九大组件

/*
九大组件
 */
protected void initStrategies(ApplicationContext context) {
    // 文件上传解析器
    initMultipartResolver(context);
    // 区域信息解析器
    initLocaleResolver(context);
    // 主题解析器
    initThemeResolver(context);
    // handler映射器
    initHandlerMappings(context);
    // handler适配器
    initHandlerAdapters(context);
    // 异常解析器
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    // 视图解析器
    initViewResolvers(context);
    // 重定向携带数据
    initFlashMapManager(context);
}

初始化

初始化策略都非常类似。

  1. 从app context中获取bean
  2. 获取不到则使用默认的

/*
1. 从app context中获取bean
2. 获取不到则使用默认的

 */
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // 从容器中获取
       Map<String, HandlerMapping> matchingBeans =
              BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
       if (!matchingBeans.isEmpty()) {
           this.handlerMappings = new ArrayList<>(matchingBeans.values());
           AnnotationAwareOrderComparator.sort(this.handlerMappings);
       }
    }
    else {
       try {
           HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
           this.handlerMappings = Collections.singletonList(hm);
       }
       catch (NoSuchBeanDefinitionException ex) {}
    }

    if (this.handlerMappings == null) {
        // 获取默认的
       this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
       if (logger.isTraceEnabled()) {
           logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                  "': using default strategies from DispatcherServlet.properties");
       }
    }
}

方法执行过程

DispatcherServlet映射请求之后,需要执行对应的方法。

核心逻辑

  • 初始化,建造了工厂,并且完成容器中bean的各种初始化
  • 初始化数据模型,在这里@ModelAttribute注解的方法会被调用
    • 执行@ModelAttribute方法
      • 获取参数
      • 执行方法
  • 执行handle方法
    • 执行@requestMapping方法
      • 获取参数
      • 执行方法

在获取参数的时候,逻辑都是类似的。

  • 获取已经初始化好的参数列表
  • 调用对应的参数解析器进行解析,这些参数解析器都实现了各自的解析接口

执行方法的时候,逻辑都是类似的

  • 执行方法前需要解析参数
  • 如果参数带有@ModelAttribute标注,说明之前被初始化过,或者直接使用默认的变量名去看看model里有没有已有的对象,优先取出已有对象方便全字段更新业务

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
       HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 工厂方法,初始化容器中的bean
       WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
       ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
       ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
       if (this.argumentResolvers != null) {
           invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
       }
       if (this.returnValueHandlers != null) {
           invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
       }
       invocableMethod.setDataBinderFactory(binderFactory);
       invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
       ModelAndViewContainer mavContainer = new ModelAndViewContainer();
       mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 初始化模型,在这里注解ModelAttribute的方法会被调用,数据会被保存到model
       modelFactory.initModel(webRequest, mavContainer, invocableMethod);
       mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
       AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
       asyncWebRequest.setTimeout(this.asyncRequestTimeout);
       WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
       asyncManager.setTaskExecutor(this.taskExecutor);
       asyncManager.setAsyncWebRequest(asyncWebRequest);
       asyncManager.registerCallableInterceptors(this.callableInterceptors);
       asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
       if (asyncManager.hasConcurrentResult()) {
           Object result = asyncManager.getConcurrentResult();
           mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
           asyncManager.clearConcurrentResult();
           LogFormatUtils.traceDebug(logger, traceOn -> {
              String formatted = LogFormatUtils.formatValue(result, !traceOn);
              return "Resume with async result [" + formatted + "]";
           });
           invocableMethod = invocableMethod.wrapConcurrentResult(result);
       }
        // 执行方法
       invocableMethod.invokeAndHandle(webRequest, mavContainer);
       if (asyncManager.isConcurrentHandlingStarted()) {
           return null;
       }
       return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
       webRequest.requestCompleted();
    }
}

反射执行handle

handle调用自定义的controller方法,可以利用反射机制实现,但是controller的参数如何确定?我们知道,参数是由我们指定的,并且当使用@ModelAttribute的时候,全字段更新还需要使用已有的对象。

以下方法为初始化过程中,查找例如@ModelAttribute注解的方法的调用,初始化Model中一些已有对象。

核心逻辑是:

  1. 遍历所有方法
  2. 执行ModelAttribute注解的方法

// 该方法为modelFatory的方法
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
    throws Exception {
    // modelMethods为初始化之后保存的方法对象,遍历
    while (!this.modelMethods.isEmpty()) {
        InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }
        // 执行ModelAttribute注解的方法
        Object returnValue = modelMethod.invokeForRequest(request, container);
        if (!modelMethod.isVoid()){
            String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}

反射方法的执行

方法的执行基本没有差别:

  1. 获取参数
  2. 反射执行方法

// 执行调用invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 获得参数
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 执行
    return doInvoke(args);
}

获取参数

参数获取都是,获取所有参数,遍历调用参数解析方法。

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 获得所有参数列表,这些参数在初始化阶段被初始化
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 解析参数
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}

不同解析对象实现了参数解析接口

// 不同解析器对象都实现了参数解析接口
public interface HandlerMethodArgumentResolver {

    boolean supportsParameter(MethodParameter parameter);

    @Nullable
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
           NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;

}

以modelAttribute为例,来看实现参数解析的过程

  1. 获得ModelAttribute的value属性,该属性标记已有对象的id(也是变量名)
  2. 查看model中是否已经有该对象,没有则新建
  3. 根据web请求中的数据,封装更新对应字段

// 来看modelAttribute的参数解析的实现
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
       NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
    Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
    // 通过ModelFactor获得参数名,这边如果没有在参数注解,则默认用变量名去查看model中有没有
    String name = ModelFactory.getNameForParameter(parameter);
    ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
    if (ann != null) {
       mavContainer.setBinding(name, ann.binding());
    }
    Object attribute = null;
    BindingResult bindingResult = null;
    // model中是否包含该对象
    if (mavContainer.containsAttribute(name)) {
       attribute = mavContainer.getModel().get(name);
    }
    // 不包含则新建
    else {
       try {
           attribute = createAttribute(name, parameter, binderFactory, webRequest);
       }
       catch (BindException ex) {
           if (isBindExceptionRequired(parameter)) {
              throw ex;
           }
           if (parameter.getParameterType() == Optional.class) {
              attribute = Optional.empty();
           }
           bindingResult = ex.getBindingResult();
       }
    }
    // 封装web请求中携带的新数据
    if (bindingResult == null) {
       WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
       if (binder.getTarget() != null) {
           if (!mavContainer.isBindingDisabled(name)) {
                // 绑定更新,web中的数据和参数中的对象绑定
              bindRequestParameters(binder, webRequest);
           }
           validateIfApplicable(binder, parameter);
           if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
              throw new BindException(binder.getBindingResult());
           }
       }
       if (!parameter.getParameterType().isInstance(attribute)) {
           attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
       }
       bindingResult = binder.getBindingResult();
    }
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    return attribute;
}

视图解析过程

核心逻辑

DispatcherServlet中获得mv之后处理转发结果。

// DispatcherServlet::doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    try {
       // ......
       // 处理转发结果,mv中已经有转发地址success
       processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
       // 该方法最终调用了DispatcherServlet中的render渲染
    }
}

调用该类下的render方法进行渲染。

  • 获得视图名,解析视图
    • 解析视图方法会遍历所有视图解析器(xml中配置的),这些解析器实现了各自的解析方法,解析视图
      • 遍历所有视图解析器,尝试解析视图
      • 检查是否有缓存,没有则创建
      • 创建视图会根据我们的controller方法返回的地址前缀创建对应的视图,如果没有指定,则创建xml中配置的视图解析
    • 解析视图过程中会将转发地址“success”补全前后缀(配置了InternalResourceViewResolver)
  • 渲染视图
    • 将mv中的数据全部写入request原生对象中
    • request对象将使用转发器进行转发

// DispatcherServlet::render
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {

    Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);
    View view;
    // 从mv中获得视图名
    String viewName = mv.getViewName();
    if (viewName != null) {
       // 开始解析view
       view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
       if (view == null) {
           throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                  "' in servlet with name '" + getServletName() + "'");
       }
    }
    else {
       //.....默认视图
    }
    try {
       if (mv.getStatus() != null) {
           response.setStatus(mv.getStatus().value());
       }
       // 调用视图本身的渲染方法
       view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
       if (logger.isDebugEnabled()) {
           logger.debug("Error rendering view [" + view + "]", ex);
       }
       throw ex;
    }
}

解析视图

遍历所有视图解析器,解析视图

// DispatcherServlet::resolveViewName
// 解析视图
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
       Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
       // 遍历所有解析器,解析器是在bean初始化过程中装载的,此时将会调用
       // InternalResourceViewResolver -> UrlBasedViewResolver -> AbstractCachingViewResolver
       // -> WebApplicationObjectSupport
       // AbstractCachingViewResolver实现了ViewResolver接口的resolveViewName方法
       for (ViewResolver viewResolver : this.viewResolvers) {
           // 调用解析器的解析方法
           View view = viewResolver.resolveViewName(viewName, locale);
           if (view != null) {
              return view;
           }
       }
    }
    return null;
}

AbstractCachingViewResolver实现了resolveViewName。

  • 判断是否有缓存
  • 创建视图
  • 放入缓存
    • 这有两处缓存,访问缓存
    • 创建缓存

// AbstractCachingViewResolver::resolveViewName
// AbstractCachingViewResolver实现了ViewResolver接口的resolveViewName方法
public View resolveViewName(String viewName, Locale locale) throws Exception {
    // 是否开启缓存,不开启的话则直接创建
    if (!isCache()) {
       return createView(viewName, locale);
    }
    else {
       // 查看缓存中是否已存在视图
       Object cacheKey = getCacheKey(viewName, locale);
       View view = this.viewAccessCache.get(cacheKey); // 注意此处有两个缓存,创建缓存和访问缓存
       // 不存在则创建
       if (view == null) {
           // 同步加锁操作创建视图,操作缓存
           synchronized (this.viewCreationCache) {
              // 查看创建缓存中是否存在
              view = this.viewCreationCache.get(cacheKey);
              if (view == null) {
                  // 不存在,创建视图
                  // AbstractCachingViewResolver自己抽象了,调用了UrlBasedViewResolver子类的方法
                  view = createView(viewName, locale);
                  if (view == null && this.cacheUnresolved) {
                     view = UNRESOLVED_VIEW;
                  }
                  // 写入缓存
                  if (view != null) {
                     this.viewAccessCache.put(cacheKey, view);
                     this.viewCreationCache.put(cacheKey, view);
                  }
              }
           }
       }
       else {
           if (logger.isTraceEnabled()) {
              logger.trace(formatKey(cacheKey) + "served from cache");
           }
       }
       return (view != UNRESOLVED_VIEW ? view : null);
    }
}

创建视图

  • 判断需要创建的视图,创建之
  • 补全转发地址

// UrlBasedViewResolver::createView 子类的方法
protected View createView(String viewName, Locale locale) throws Exception {
    if (!canHandle(viewName, locale)) {
       return null;
    }
    // 判断是否重定向视图
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
       String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
       // 新建重定向视图
       RedirectView view = new RedirectView(redirectUrl,
              isRedirectContextRelative(), isRedirectHttp10Compatible());
       String[] hosts = getRedirectHosts();
       if (hosts != null) {
           view.setHosts(hosts);
       }
       return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    }
    // 判断是否转发视图
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
       String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
       // 新建转发视图
       InternalResourceView view = new InternalResourceView(forwardUrl);
       return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
    }
    // 否则调用父类中的createView方法
    // 父类AbstractCachingViewResolver实际上调了抽象方法loadView(viewName, locale)
    // loadView(viewName, locale)在UrlBasedViewResolver中被实现
    // loadView调用了buildView方法,该方法在InternalResourceViewResolver
    return super.createView(viewName, locale);
}
//InternalResourceViewResolver::buildView
AbstractUrlBasedView buildView(String viewName) throws Exception {
    // 调用了父类的方法,并强转为InternalResourceView
    InternalResourceView view = (InternalResourceView) super.buildView(viewName);
    if (this.alwaysInclude != null) {
       view.setAlwaysInclude(this.alwaysInclude);
    }
    view.setPreventDispatchLoop(true);
    return view;
}
//UrlBasedViewResolver::buildView
// super.buildView(viewName);
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    Class<?> viewClass = getViewClass();
    Assert.state(viewClass != null, "No view class");
    // 初始化
    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
    // 前缀后缀补全
    view.setUrl(getPrefix() + viewName + getSuffix());
    String contentType = getContentType();
    if (contentType != null) {
       view.setContentType(contentType);
    }
    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());
    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
       view.setExposePathVariables(exposePathVariables);
    }
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
       view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
       view.setExposedContextBeanNames(exposedContextBeanNames);
    }
    return view;
}

渲染转发

解析得到视图之后,调用视图的转发方法

  • 将mv中存在的数据写入request
  • 跳转

// view.render(mv.getModelInternal(), request, response);
// AbstractView中的,赋值渲染视图
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
       HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
       logger.debug("View " + formatViewName() +
              ", model " + (model != null ? model : Collections.emptyMap()) +
              (this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
    }
    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    prepareResponse(request, response);
    // 渲染最终的合并模型
    // AbstractView中为抽象方法,在子类renderMergedOutputModel
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

// InternalResourceView :: renderMergedOutputModel
protected void renderMergedOutputModel(
       Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 将模型中的数据项写入request
    exposeModelAsRequestAttributes(model, request);
    // Expose helpers as request attributes, if any.
    exposeHelpers(request);
    // 获取最终地址
    String dispatcherPath = prepareForRendering(request, response);
    // 获取转发器
    RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
    if (rd == null) {
       throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
              "]: Check that the corresponding file exists within your web application archive!");
    }

    if (useInclude(request, response)) {
       response.setContentType(getContentType());
       if (logger.isDebugEnabled()) {
           logger.debug("Including [" + getUrl() + "]");
       }
       rd.include(request, response);
    }
    else {
       if (logger.isDebugEnabled()) {
           logger.debug("Forwarding to [" + getUrl() + "]");
       }
       // 转发
       rd.forward(request, response);
    }
}

数据绑定

基本逻辑

在方法调用之前调取获取所有参数,这里需要调用resolveArgument对参数进行解析。

// 来看modelAttribute的参数解析的实现,其中实现了参数绑定
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
       NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
     //....
    // 封装web请求中携带的新数据
    if (bindingResult == null) {
        // 创建了参数绑定器
       WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
       if (binder.getTarget() != null) {
           if (!mavContainer.isBindingDisabled(name)) {
                // 绑定更新,web中的数据和参数中的对象绑定
              bindRequestParameters(binder, webRequest);
           }
           validateIfApplicable(binder, parameter);
           if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
              throw new BindException(binder.getBindingResult());
           }
       }
       if (!parameter.getParameterType().isInstance(attribute)) {
           attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
       }
       bindingResult = binder.getBindingResult();
    }
    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);
    return attribute;
}

我们来考虑一下,WebDataBinder它需要做什么事。

  • 参数类型转换,数据格式化(例如日期)
  • 合法性校验,前端的校验防君子不防小人
  • 数据绑定,遍历参数,一一设置
  • 结果处理,例如错误处理

事实上,WebDataBinder正是包含这些组件。

 

自定义转换器

  1. 实现Converter接口
  2. 将WebDataBinder中的ConversionService设置成我们这个加了自定义类型转换器的ConversionService

<!-- 1. 告诉SpringMVC别用默认的ConversionService
        而用我自定义的ConversionService、可能有我们自定义的Converter; -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <!--converters转换器中添加我们自定义的类型转换器  -->
    <property name="converters">
        <set>
            <bean class="com.atguigu.component.MyStringToEmployeeConverter"></bean>
        </set>
    </property>
</bean>
<!-- 2. conversion-service="conversionService":使用我们自己配置的类型转换组件 -->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

 

弃用了struts,用spring mvc框架做了几个项目,感觉都不错,而且使用了注解方式,可以省掉一大堆配置文件。本文主要介绍使用注解方式配置的spring mvc,之前写的spring3.0 mvc和rest小例子没有介绍到数据层的内容,现在这一篇补上。下面开始贴代码。 文中用的框架版本:spring 3,hibernate 3,没有的,自己上网下。 先说web.xml配置: [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> 03. <display-name>s3h3</display-name> 04. <context-param> 05. <param-name>contextConfigLocation</param-name> 06. <param-value>classpath:applicationContext*.xml</param-value> 07. </context-param> 08. <listener> 09. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 10. </listener> 11. 12. <servlet> 13. <servlet-name>spring</servlet-name> 14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 15. <load-on-startup>1</load-on-startup> 16. </servlet> 17. <servlet-mapping> 18. <servlet-name>spring</servlet-name> <!-- 这里在配成spring,下边也要写一个名为spring-servlet.xml的文件,主要用来配置它的controller --> 19. <url-pattern>*.do</url-pattern> 20. </servlet-mapping> 21. <welcome-file-list> 22. <welcome-file>index.jsp</welcome-file> 23. </welcome-file-list> 24.</web-app> spring-servlet,主要配置controller的信息 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02. <beans 03. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 04. xmlns:context="http://www.springframework.org/schema/context" 05. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 06. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 07. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 09. 10. <context:annotation-config /> 11. <!-- 把标记了@Controller注解的类转换为bean --> 12. <context:component-scan base-package="com.mvc.controller" /> 13. <!-- 启动Spring MVC的注解功能,完成请求和注解POJO的映射 --> 14. <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> 15. 16. <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 --> 17. <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" 18. p:prefix="/WEB-INF/view/" p:suffix=".jsp" /> 19. 20. <bean id="multipartResolver" 21. class="org.springframework.web.multipart.commons.CommonsMultipartResolver" 22. p:defaultEncoding="utf-8" /> 23. </beans> applicationContext.xml代码 [java] view plaincopy 01.<?xml version="1.0" encoding="UTF-8"?> 02.<beans 03. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" 04. xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" 05. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 06. xsi:schemaLocation=" 07. http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 08. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 09. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd 10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 11. 12. <context:annotation-config /> 13. <context:component-scan base-package="com.mvc" /> <!-- 自动扫描所有注解该路径 --> 14. 15. <context:property-placeholder location="classpath:/hibernate.properties" /> 16. 17. <bean id="sessionFactory" 18. class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> 19. <property name="dataSource" ref="dataSource" /> 20. <property name="hibernateProperties"> 21. <props> 22. <prop key="hibernate.dialect">${dataSource.dialect}</prop> 23. <prop key="hibernate.hbm2ddl.auto">${dataSource.hbm2ddl.auto}</prop> 24. <prop key="hibernate.hbm2ddl.auto">update</prop> 25. </props> 26. </property> 27. <property name="packagesToScan"> 28. <list> 29. <value>com.mvc.entity</value><!-- 扫描实体类,也就是平时所说的model --> 30. </list> 31. </property> 32. </bean> 33. 34. <bean id="transactionManager" 35. class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 36. <property name="sessionFactory" ref="sessionFactory" /> 37. <property name="dataSource" ref="dataSource" /> 38. </bean> 39. 40. <bean id="dataSource" 41. class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 42. <property name="driverClassName" value="${dataSource.driverClassName}" /> 43. <property name="url" value="${dataSource.url}" /> 44. <property name="username" value="${dataSource.username}" /> 45. <property name="password" value="${dataSource.password}" /> 46. </bean> 47. <!-- Dao的实现 --> 48. <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> 49. <property name="sessionFactory" ref="sessionFactory" /> 50. </bean> 51. <tx:annotation-driven transaction-manager="transactionManager" /> 52. <tx:annotation-driven mode="aspectj"/> 53. 54. <aop:aspectj-autoproxy/> 55.</beans> hibernate.properties数据库连接配置 [java] view plaincopy 01.dataSource.password=123 02.dataSource.username=root 03.dataSource.databaseName=test 04.dataSource.driverClassName=com.mysql.jdbc.Driver 05.dataSource.dialect=org.hibernate.dialect.MySQL5Dialect 06.dataSource.serverName=localhost:3306 07.dataSource.url=jdbc:mysql://localhost:3306/test 08.dataSource.properties=user=${dataSource.username};databaseName=${dataSource.databaseName};serverName=${dataSource.serverName};password=${dataSource.password} 09.dataSource.hbm2ddl.auto=update 配置已经完成,下面开始例子 先在数据库建表,例子用的是mysql数据库 [java] view plaincopy 01.CREATE TABLE `test`.`student` ( 02. `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 03. `name` varchar(45) NOT NULL, 04. `psw` varchar(45) NOT NULL, 05. PRIMARY KEY (`id`) 06.) 建好表后,生成实体类 [java] view plaincopy 01.package com.mvc.entity; 02. 03.import java.io.Serializable; 04. 05.import javax.persistence.Basic; 06.import javax.persistence.Column; 07.import javax.persistence.Entity; 08.import javax.persistence.GeneratedValue; 09.import javax.persistence.GenerationType; 10.import javax.persistence.Id; 11.import javax.persistence.Table; 12. 13.@Entity 14.@Table(name = "student") 15.public class Student implements Serializable { 16. private static final long serialVersionUID = 1L; 17. @Id 18. @Basic(optional = false) 19. @GeneratedValue(strategy = GenerationType.IDENTITY) 20. @Column(name = "id", nullable = false) 21. private Integer id; 22. @Column(name = "name") 23. private String user; 24. @Column(name = "psw") 25. private String psw; 26. public Integer getId() { 27. return id; 28. } 29. public void setId(Integer id) { 30. this.id = id; 31. } 32. 33. public String getUser() { 34. return user; 35. } 36. public void setUser(String user) { 37. this.user = user; 38. } 39. public String getPsw() { 40. return psw; 41. } 42. public void setPsw(String psw) { 43. this.psw = psw; 44. } 45.} Dao层实现 [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.public interface EntityDao { 06. public List<Object> createQuery(final String queryString); 07. public Object save(final Object model); 08. public void update(final Object model); 09. public void delete(final Object model); 10.} [java] view plaincopy 01.package com.mvc.dao; 02. 03.import java.util.List; 04. 05.import org.hibernate.Query; 06.import org.springframework.orm.hibernate3.HibernateCallback; 07.import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 08. 09.public class EntityDaoImpl extends HibernateDaoSupport implements EntityDao{ 10. public List<Object> createQuery(final String queryString) { 11. return (List<Object>) getHibernateTemplate().execute( 12. new HibernateCallback<Object>() { 13. public Object doInHibernate(org.hibernate.Session session) 14. throws org.hibernate.HibernateException { 15. Query query = session.createQuery(queryString); 16. List<Object> rows = query.list(); 17. return rows; 18. } 19. }); 20. } 21. public Object save(final Object model) { 22. return getHibernateTemplate().execute( 23. new HibernateCallback<Object>() { 24. public Object doInHibernate(org.hibernate.Session session) 25. throws org.hibernate.HibernateException { 26. session.save(model); 27. return null; 28. } 29. }); 30. } 31. public void update(final Object model) { 32. getHibernateTemplate().execute(new HibernateCallback<Object>() { 33. public Object doInHibernate(org.hibernate.Session session) 34. throws org.hibernate.HibernateException { 35. session.update(model); 36. return null; 37. } 38. }); 39. } 40. public void delete(final Object model) { 41. getHibernateTemplate().execute(new HibernateCallback<Object>() { 42. public Object doInHibernate(org.hibernate.Session session) 43. throws org.hibernate.HibernateException { 44. session.delete(model); 45. return null; 46. } 47. }); 48. } 49.} Dao在applicationContext.xml注入 <bean id="entityDao" class="com.mvc.dao.EntityDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> Dao只有一个类的实现,直接供其它service层调用,如果你想更换为其它的Dao实现,也只需修改这里的配置就行了。 开始写view页面,WEB-INF/view下新建页面student.jsp,WEB-INF/view这路径是在spring-servlet.xml文件配置的,你可以配置成其它,也可以多个路径。student.jsp代码 [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>添加</title> 09.<mce:script language="javascript" src="<%=request.getContextPath()%><!-- 10./script/jquery.min.js"> 11.// --></mce:script> 12.<mce:style><!-- 13.table{ border-collapse:collapse; } 14.td{ border:1px solid #f00; } 15.--></mce:style><style mce_bogus="1">table{ border-collapse:collapse; } 16.td{ border:1px solid #f00; }</style> 17.<mce:script type="text/javascript"><!-- 18.function add(){ 19. [removed].href="<%=request.getContextPath() %>/student.do?method=add"; 20.} 21. 22.function del(id){ 23.$.ajax( { 24. type : "POST", 25. url : "<%=request.getContextPath()%>/student.do?method=del&id;=" + id, 26. dataType: "json", 27. success : function(data) { 28. if(data.del == "true"){ 29. alert("删除成功!"); 30. $("#" + id).remove(); 31. } 32. else{ 33. alert("删除失败!"); 34. } 35. }, 36. error :function(){ 37. alert("网络连接出错!"); 38. } 39.}); 40.} 41.// --></mce:script> 42.</head> 43.<body> 44. 45.<input id="add" type="button" value="添加"/> 46.<table > 47. <tr> 48. <td>序号</td> 49. <td>姓名</td> 50. <td>密码</td> 51. <td>操作</td> 52. </tr> 53. <c:forEach items="${list}" var="student"> 54. <tr id="<c:out value="${student.id}"/>"> 55. <td><c:out value="${student.id}"/></td> 56. <td><c:out value="${student.user}"/></td> 57. <td><c:out value="${student.psw}"/></td> 58. <td> 59. <input type="button" value="编辑"/> 60. <input type="button" value="${student.id}"/>')" value="删除"/> 61. </td> 62. </tr> 63. </c:forEach> 64. 65.</table> 66.</body> 67.</html> student_add.jsp [xhtml] view plaincopy 01.<%@ page language="java" contentType="text/html; charset=UTF-8" 02. pageEncoding="UTF-8"%> 03.<%@ include file="/include/head.jsp"%> 04.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 05.<html> 06.<head> 07.<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 08.<title>学生添加</title> 09.<mce:script type="text/javascript"><!-- 10.function turnback(){ 11. [removed].href="<%=request.getContextPath() %>/student.do"; 12.} 13.// --></mce:script> 14.</head> 15.<body> 16.<form method="post" action="<%=request.getContextPath() %>/student.do?method=save"> 17.<div><c:out value="${addstate}"></c:out></div> 18.<table> 19. <tr><td>姓名</td><td><input id="user" name="user" type="text" /></td></tr> 20. <tr><td>密码</td><td><input id="psw" name="psw" type="text" /></td></tr> 21. <tr><td colSpan="2" align="center"><input type="submit" value="提交"/><input type="button" value="返回" /> </td></tr> 22.</table> 23. 24.</form> 25.</body> 26.</html> controller类实现,只需把注解写上,spring就会自动帮你找到相应的bean,相应的注解标记意义,不明白的,可以自己查下@Service,@Controller,@Entity等等的内容。 [java] view plaincopy 01.package com.mvc.controller; 02. 03.import java.util.List; 04. 05.import javax.servlet.http.HttpServletRequest; 06.import javax.servlet.http.HttpServletResponse; 07. 08.import org.apache.commons.logging.Log; 09.import org.apache.commons.logging.LogFactory; 10.import org.springframework.beans.factory.annotation.Autowired; 11.import org.springframework.stereotype.Controller; 12.import org.springframework.ui.ModelMap; 13.import org.springframework.web.bind.annotation.RequestMapping; 14.import org.springframework.web.bind.annotation.RequestMethod; 15.import org.springframework.web.bind.annotation.RequestParam; 16.import org.springframework.web.servlet.ModelAndView; 17. 18.import com.mvc.entity.Student; 19.import com.mvc.service.StudentService; 20. 21.@Controller 22.@RequestMapping("/student.do") 23.public class StudentController { 24. protected final transient Log log = LogFactory 25. .getLog(StudentController.class); 26. @Autowired 27. private StudentService studentService; 28. public StudentController(){ 29. 30. } 31. 32. @RequestMapping 33. public String load(ModelMap modelMap){ 34. List<Object> list = studentService.getStudentList(); 35. modelMap.put("list", list); 36. return "student"; 37. } 38. 39. @RequestMapping(params = "method=add") 40. public String add(HttpServletRequest request, ModelMap modelMap) throws Exception{ 41. return "student_add"; 42. } 43. 44. @RequestMapping(params = "method=save") 45. public String save(HttpServletRequest request, ModelMap modelMap){ 46. String user = request.getParameter("user"); 47. String psw = request.getParameter("psw"); 48. Student st = new Student(); 49. st.setUser(user); 50. st.setPsw(psw); 51. try{ 52. studentService.save(st); 53. modelMap.put("addstate", "添加成功"); 54. } 55. catch(Exception e){ 56. log.error(e.getMessage()); 57. modelMap.put("addstate", "添加失败"); 58. } 59. 60. return "student_add"; 61. } 62. 63. @RequestMapping(params = "method=del") 64. public void del(@RequestParam("id") String id, HttpServletResponse response){ 65. try{ 66. Student st = new Student(); 67. st.setId(Integer.valueOf(id)); 68. studentService.delete(st); 69. response.getWriter().print("{/"del/":/"true/"}"); 70. } 71. catch(Exception e){ 72. log.error(e.getMessage()); 73. e.printStackTrace(); 74. } 75. } 76.} service类实现 [java] view plaincopy 01.package com.mvc.service; 02. 03.import java.util.List; 04. 05.import org.springframework.beans.factory.annotation.Autowired; 06.import org.springframework.stereotype.Service; 07.import org.springframework.transaction.annotation.Transactional; 08. 09.import com.mvc.dao.EntityDao; 10.import com.mvc.entity.Student; 11. 12.@Service 13.public class StudentService { 14. @Autowired 15. private EntityDao entityDao; 16. 17. @Transactional 18. public List<Object> getStudentList(){ 19. StringBuffer sff = new StringBuffer(); 20. sff.append("select a from ").append(Student.class.getSimpleName()).append(" a "); 21. List<Object> list = entityDao.createQuery(sff.toString()); 22. return list; 23. } 24. 25. public void save(Student st){ 26. entityDao.save(st); 27. } 28. public void delete(Object obj){ 29. entityDao.delete(obj); 30. } 31.} OK,例子写完。有其它业务内容,只需直接新建view,并实现相应comtroller和service就行了,配置和dao层的内容基本不变,也就是每次只需写jsp(view),controller和service调用dao就行了。 怎样,看了这个,spring mvc是不是比ssh实现更方便灵活。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值