spring mvc介绍

项目中用到了spring mvc,总结了个文档,分享给新手使用,欢迎转载。

概述

初始化

核心部件

整体流程

扩展机制

补充文档



概述

spring mvc为spring提供的一个基于java web的mvc框架,其核心理念与struct2等mvc框架大体相同,但由于其先天与spring结合较紧密,而spring已经基于JEE做了全栈的解决方案,所以在java的web应用中选型spring mvc不失为一个靠谱的解决方案。


下图为spring mvc官网提供的overview 



理论上来讲大多数基于java web规范的mvc框架,核心概念无非如此。

Front controller:为前端控制器,一般都在web.xml中配置了统一的入口servlet,spring mvc为:org.springframework.web.servlet.DispatcherServlet

Controller:也就是我们实现业务逻辑的action,spring mvc中的概念为controller.

View template:最终渲染的模板,例如jsp,freemarker,velocity等。

model:controller返回给template context的模型对象,用于模板渲染的时候合并使用。


整个请求过程为incoming request->Front controller->controller->view template->return control



初始化


spring mvc的初始化有两个阶段:

1.spring父容器初始化

2.spring子容器初始化


父容器:spring的applicationContext有父子容器的概念,子容器可以共享获取父容器的对象,而父容器不能看到子容器中的对象。


 <listener>

<listener-class>

    org.springframework.web.context.ContextLoaderListener

</listener-class>

    </listener>

    <context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath*:configs/beans.xml</param-value>

    </context-param>


我们在web.xml中配置的如上信息,即用于初始化spring 的父容器。

而在dispatcher-servlet.xml中配置的bean即为子容器。


例:

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd"

default-autowire="byName">

<context:component-scanbase-package="com.cmcc.normandy,com.cmcc.idmp"/>

<beanid="viewResolver"

class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">

<propertyname="suffix"value=".vm"/>

<!-- 解决乱码 -->

<propertyname="contentType">

<value>text/html;charset=UTF-8</value>

</property>

</bean>

<beanid="velocityConfig"

class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">

<propertyname="resourceLoaderPath"value="/WEB-INF/vm"/>

<!-- 解决乱码 -->

<propertyname="velocityProperties">

<props>

<propkey="input.encoding">UTF-8</prop>

<propkey="output.encoding">UTF-8</prop>

</props>

</property>

</bean>


<bean

class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">

<propertyname="messageConverters">

<list>

<bean

class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"/>

</list>

</property>

</bean>

</beans>




根据servlet规范,listener将会最先被初始化,所以父容器先于子容器初始化,然后DispatcherServlet的init方法将会初始化子容器。 





我们先看HttpServletBean的init方法:

/**

* Map config parameters onto bean properties of this servlet, and

* invoke subclass initialization.

* @throws ServletException if bean properties are invalid (or required

* properties are missing), or if subclass initialization fails.

*/

@Override

public final void init() throws ServletException {

if (logger.isDebugEnabled()) {

logger.debug("Initializing servlet '" + getServletName() + "'");

}


// Set bean properties from init parameters.

try {

PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);

ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());

bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));

initBeanWrapper(bw);

bw.setPropertyValues(pvs, true);

}

catch (BeansException ex) {

logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);

throw ex;

}


// Let subclasses do whatever initialization they like.

initServletBean();


if (logger.isDebugEnabled()) {

logger.debug("Servlet '" + getServletName() + "' configured successfully");

}

}



init方法在初始化配置完之后,继续调用子类FrameworkServlet的initServletBean()方法

@Override

protected final void initServletBean() throws ServletException {

getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");

if (this.logger.isInfoEnabled()) {

this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");

}

long startTime = System.currentTimeMillis();


try {

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

}

catch (ServletException ex) {

this.logger.error("Context initialization failed", ex);

throw ex;

}

catch (RuntimeException ex) {

this.logger.error("Context initialization failed", ex);

throw ex;

}


if (this.logger.isInfoEnabled()) {

long elapsedTime = System.currentTimeMillis() - startTime;

this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +

elapsedTime + " ms");

}

}



接下来看initWebApplicationContext方法


protected WebApplicationContext initWebApplicationContext() {

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;


if (this.webApplicationContext != null) {

// A context instance was injected at construction time -> use it

wac = this.webApplicationContext;

if (wac instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;

if (!cwac.isActive()) {

// The context has not yet been refreshed -> provide services such as

// setting the parent context, setting the application context id, etc

if (cwac.getParent() == null) {

// The context instance was injected without an explicit parent -> set

// the root application context (if any; may be null) as the parent

cwac.setParent(rootContext);

}

configureAndRefreshWebApplicationContext(cwac);

}

}

}

if (wac == null) {

// No context instance was injected at construction time -> see if one

// has been registered in the servlet context. If one exists, it is assumed

// that the parent context (if any) has already been set and that the

// user has performed any initialization such as setting the context id

wac = findWebApplicationContext();

}

if (wac == null) {

// No context instance is defined for this servlet -> create a local one

wac = createWebApplicationContext(rootContext);

}


if (!this.refreshEventReceived) {

// Either the context is not a ConfigurableApplicationContext with refresh

// support or the context injected at construction time had already been

// refreshed -> trigger initial onRefresh manually here.

onRefresh(wac);

}


if (this.publishContext) {

// Publish the context as a servlet context attribute.

String attrName = getServletContextAttributeName();

getServletContext().setAttribute(attrName, wac);

if (this.logger.isDebugEnabled()) {

this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +

"' as ServletContext attribute with name [" + attrName + "]");

}

}


return wac;

}



在子容器也初始化之后,调用子类DispatcherServlet的onRefresh方法.

@Override

protected void onRefresh(ApplicationContext context) {

initStrategies(context);

}


/**

* Initialize the strategy objects that this servlet uses.

* <p>May be overridden in subclasses in order to initialize further strategy objects.

*/

protected void initStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

initHandlerMappings(context);

initHandlerAdapters(context);

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}


该方法中初始化了spring mvc的核心组件。到此spring mvc初始化完毕。




核心部件



MultipartResolver:用于解决文件上传等请求解析的解析器。具体可以参考:StandardServletMultipartResolver


public boolean isMultipart(HttpServletRequest request) {

// Same check as in Commons FileUpload...

if (!"post".equals(request.getMethod().toLowerCase())) {

return false;

}

String contentType = request.getContentType();

return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));

}


@Override

public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {

return new StandardMultipartHttpServletRequest(request, this.resolveLazily);

}



可以看到针对contentType我们来判断是否为文件上传请求。

resolveMultipart方法将请求中的文件流解析成文件:


public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {

super(request);

if (!lazyParsing) {

parseRequest(request);

}

}

private void parseRequest(HttpServletRequest request) {

try {

Collection<Part> parts = request.getParts();

this.multipartParameterNames = new LinkedHashSet<String>(parts.size());

MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());

for (Part part : parts) {

String filename = extractFilename(part.getHeader(CONTENT_DISPOSITION));

if (filename != null) {

files.add(part.getName(), new StandardMultipartFile(part, filename));

}

else {

this.multipartParameterNames.add(part.getName());

}

}

setMultipartFiles(files);

}

catch (Exception ex) {

throw new MultipartException("Could not parse multipart servlet request", ex);

}

}




HandlerMapping:用于检测哪些bean和方法被绑定了url处理的handler.

具体可以参考:DefaultAnnotationHandlerMapping


@Override

protected String[] determineUrlsForHandler(String beanName) {

ApplicationContext context = getApplicationContext();

Class<?> handlerType = context.getType(beanName);

RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);

if (mapping != null) {

// @RequestMapping found at type level

this.cachedMappings.put(handlerType, mapping);

Set<String> urls = new LinkedHashSet<String>();

String[] typeLevelPatterns = mapping.value();

if (typeLevelPatterns.length > 0) {

// @RequestMapping specifies paths at type level

String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);

for (String typeLevelPattern : typeLevelPatterns) {

if (!typeLevelPattern.startsWith("/")) {

typeLevelPattern = "/" + typeLevelPattern;

}

boolean hasEmptyMethodLevelMappings = false;

for (String methodLevelPattern : methodLevelPatterns) {

if (methodLevelPattern == null) {

hasEmptyMethodLevelMappings = true;

}

else {

String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);

addUrlsForPath(urls, combinedPattern);

}

}

if (hasEmptyMethodLevelMappings ||

org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {

addUrlsForPath(urls, typeLevelPattern);

}

}

return StringUtils.toStringArray(urls);

}

else {

// actual paths specified by @RequestMapping at method level

return determineUrlsForHandlerMethods(handlerType, false);

}

}

else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {

// @RequestMapping to be introspected at method level

return determineUrlsForHandlerMethods(handlerType, false);

}

else {

return null;

}

}


determineUrlsForHandler方法在bean初始化的时候检测bean或方法是否绑定了url处理的handler.



HandlerExecutionChain:HanderMapping的getHandler(HttpServletRequest request)返回HandlerExecutionChain。

public class HandlerExecutionChain {


private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);


private final Object handler;


private HandlerInterceptor[] interceptors;


private List<HandlerInterceptor> interceptorList;


private int interceptorIndex = -1;


一个hander可以对应多个HandlerInterceptor来对请求进行过滤。


我们可以配置个例子:


<mvc:interceptors>  

        <mvc:interceptor>  

            <!-- 需拦截的地址 -->  

            <mvc:mappingpath="/hello"/>  

            <!-- 需排除拦截的地址 -->  

            <mvc:exclude-mappingpath="/login.htm"/>  

            <beanclass="com.cmcc.normandy.web.interceptor.SecurityInterceptor"/>  

        </mvc:interceptor>  

    </mvc:interceptors>  


public class SecurityInterceptor implements HandlerInterceptor {


    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,

                             Object handler) throws Exception {

        System.out.println("hahaha i'm in interceptor !!!");

        return true;


    }


    public void postHandle(HttpServletRequest request, HttpServletResponse response,

                           Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("post handle");

    }


    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,

                                Object handler, Exception ex) throws Exception {

        System.out.println("complete");

    }



HandlerAdapter:hander的代理器,该接口的hander方法用于真正调用controller。可以参考AnnotationMethodHandlerAdapter


protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler)

throws Exception {


ServletHandlerMethodResolver methodResolver = getMethodResolver(handler);

Method handlerMethod = methodResolver.resolveHandlerMethod(request);

ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker(methodResolver);

ServletWebRequest webRequest = new ServletWebRequest(request, response);

ExtendedModelMap implicitModel = new BindingAwareModelMap();


Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);

ModelAndView mav =

methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest);

methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);

return mav;

}



执行完之后,返回ModelAndView用于到时候传给模板的context.


HandlerExceptionResolver:异常处理解析器,可以针对特定异常进行特殊处理。自己随便找个实现看看,非常简单。


ViewResolver:视图解析器,用于调用模板并渲染。可以参考:VelocityViewResolver。具体实现在后续流程处理中会接着讲到。





整体流程



此处的流程是针对一个请求的流程,这里我把整个请求分为几个阶段:


1.解析请求->2.请求预处理(preHandler)->3.请求处理->4.请求处理后(postHandle)->5.模板渲染->6.异常处理->7.请求处理完成(afterCompletion)

整个流程可以阅读DispatcherServlet中的doDispatch方法


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 {

processedRequest = checkMultipart(request);

multipartRequestParsed = (processedRequest != request);


// Determine handler for the current request.

mappedHandler = getHandler(processedRequest);

if (mappedHandler == null || mappedHandler.getHandler() == null) {

noHandlerFound(processedRequest, response);

return;

}


// 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 (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;

}


try {

// Actually invoke the handler.

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

}

finally {

if (asyncManager.isConcurrentHandlingStarted()) {

return;

}

}


applyDefaultViewName(request, mv);

mappedHandler.applyPostHandle(processedRequest, response, mv);

}

catch (Exception ex) {

dispatchException = ex;

}

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}

catch (Exception ex) {

triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

}

catch (Error err) {

triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);

}

finally {

if (asyncManager.isConcurrentHandlingStarted()) {

// Instead of postHandle and afterCompletion

mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

return;

}

// Clean up any resources used by a multipart request.

if (multipartRequestParsed) {

cleanupMultipart(processedRequest);

}

}

}



在interceptor的hander被执行后,最后将进行模板合并:processDispatchResult


private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,

HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {


boolean errorView = false;


if (exception != null) {

if (exception instanceof ModelAndViewDefiningException) {

logger.debug("ModelAndViewDefiningException encountered", exception);

mv = ((ModelAndViewDefiningException) exception).getModelAndView();

}

else {

Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);

mv = processHandlerException(request, response, handler, exception);

errorView = (mv != null);

}

}


// Did the handler return a view to render?

if (mv != null && !mv.wasCleared()) {

render(mv, request, response);

if (errorView) {

WebUtils.clearErrorRequestAttributes(request);

}

}

else {

if (logger.isDebugEnabled()) {

logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +

"': assuming HandlerAdapter completed request handling");

}

}


if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {

// Concurrent handling started during a forward

return;

}


if (mappedHandler != null) {

mappedHandler.triggerAfterCompletion(request, response, null);

}

}

最后我们看render方法:


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

// Determine locale for request and apply it to the response.

Locale locale = this.localeResolver.resolveLocale(request);

response.setLocale(locale);


View view;

if (mv.isReference()) {

// We need to resolve the view name.

view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);

if (view == null) {

throw new ServletException("Could not resolve view with name '" + mv.getViewName() +

"' in servlet with name '" + getServletName() + "'");

}

}

else {

// No need to lookup: the ModelAndView object contains the actual View object.

view = mv.getView();

if (view == null) {

throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +

"View object in servlet with name '" + getServletName() + "'");

}

}


// Delegate to the View object for rendering.

if (logger.isDebugEnabled()) {

logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");

}

try {

view.render(mv.getModelInternal(), request, response);

}

catch (Exception ex) {

if (logger.isDebugEnabled()) {

logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +

getServletName() + "'", ex);

}

throw ex;

}

}


假如我们使用velocity,最后调用,VelocityView的


@Override

protected void renderMergedTemplateModel(

Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {


exposeHelpers(model, request);


Context velocityContext = createVelocityContext(model, request, response);

exposeHelpers(velocityContext, request, response);

exposeToolAttributes(velocityContext, request);


doRender(velocityContext, response);

}



protected void doRender(Context context, HttpServletResponse response) throws Exception {

if (logger.isDebugEnabled()) {

logger.debug("Rendering Velocity template [" + getUrl() + "] in VelocityView '" + getBeanName() + "'");

}

mergeTemplate(getTemplate(), context, response);

}





protected void mergeTemplate(

Template template, Context context, HttpServletResponse response) throws Exception {


try {

template.merge(context, response.getWriter());

}

catch (MethodInvocationException ex) {

Throwable cause = ex.getWrappedThrowable();

throw new NestedServletException(

"Method invocation failed during rendering of Velocity view with name '" +

getBeanName() + "': " + ex.getMessage() + "; reference [" + ex.getReferenceName() +

"], method '" + ex.getMethodName() + "'",

cause==null ? ex : cause);

}

}




扩展机制



spring mvc中的所有组件都是容器中的bean,所以扩展非常方便,只要配置相应的bean即可。默认的实现已经在DispatcherServlet.properties中


org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver


org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver


org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\

org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping


org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\

org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver


org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator


org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver


org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager




如果你想替换默认实现,组件的获取方法可以看下相应的init方法就很明了了。比如:



private void initHandlerMappings(ApplicationContext context) {

this.handlerMappings = null;


if (this.detectAllHandlerMappings) {

// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.

Map<String, HandlerMapping> matchingBeans =

BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

if (!matchingBeans.isEmpty()) {

this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());

// We keep HandlerMappings in sorted order.

OrderComparator.sort(this.handlerMappings);

}

}

else {

try {

HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);

this.handlerMappings = Collections.singletonList(hm);

}

catch (NoSuchBeanDefinitionException ex) {

// Ignore, we'll add a default HandlerMapping later.

}

}


// Ensure we have at least one HandlerMapping, by registering

// a default HandlerMapping if no other mappings are found.

if (this.handlerMappings == null) {

this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);

if (logger.isDebugEnabled()) {

logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");

}

}

}





补充文档



具体使用的时候可以参考:

http://docs.spring.io/spring/docs/4.0.5.RELEASE/spring-framework-reference/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值