一、简单提下使用入门(依赖于注解方式)
1、通过在web.xml中添加配置,引入spring mvc框架,类似于Struts2引入需要在web.xml中配置过滤器StrutsPrepareAndExecuteFilter一样,但是Spring MVC是通过servlet实现的,需要配置一个Servlet。
- <servlet>
- <servlet-name>dispatcher</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <init-param>
- <param-name>contextConfigLocation</param-name>
- <param-value>classpath:springConfig/springmvx-servlet.xml</param-value>
- </init-param>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>dispatcher</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
2、在springmvc中主要是配置Controller,静态文件的映射策略,视图的配置等(即后面原理要讲的几个组件)。
-
- <!-- 自动扫描的包名 -->
- <context:component-scan base-package="com.test"/>
-
- <!-- 默认的注解映射的支持,自动注册RequestMappingHandlerMapping和RequestMappingHandlerAdapter
- (spring 3.2以上版本已经不再是DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter)
- -->
- <mvc:annotation-driven />
-
- <!-- 视图解释类 -->
- <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/jsp/"/>
- <property name="suffix" value=".jsp"/>
- <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
- </bean>
3、编写controller
- @Controller
- @RequestMapping("/test")
- public class IndexController {
-
- @RequestMapping("/index")
- public ModelAndView index() {
- ModelAndView view = new ModelAndView("index");
- view.addObject("welcome", "hello");
- return view;
- }
-
- }
4、访问地址:xxx/contexpath/test/index。
说明:关于mvc:annotation-driven配置的详细说明可以参考:https://my.oschina.net/HeliosFly/blog/205343
二、先尝试理解原理
Spring的MVC框架主要由DispatcherServlet、处理器映射(HandlerMapping)、处理器(Controller)、视图解析器(ViewResolver)、视图(View)组成。DispatcherServlet是整个Spring MVC的核心。它负责接收HTTP请求组织协调Spring MVC的各个组成部分。
1、客户端发出一个http请求给web服务器,web服务器对http请求进行解析,如果匹配DispatcherServlet的请求映射路径(在web.xml中指定),web容器将请求转交给DispatcherServlet.
2、DipatcherServlet接收到这个请求之后将根据请求的信息(包括URL、Http方法、请求报文头和请求参数Cookie等)以及HandlerMapping的配置找到处理请求的处理器(Handler)。
3-4、DispatcherServlet根据HandlerMapping找到对应的Handler,将处理权交给Handler(Handler将具体的处理进行封装),再由具体的HandlerAdapter对Handler进行具体的调用。
5、Handler对数据处理完成以后将返回一个ModelAndView()对象给DispatcherServlet。
6、Handler返回的ModelAndView()只是一个逻辑视图并不是一个正式的视图,DispatcherSevlet通过ViewResolver将逻辑视图转化为真正的视图View。
7、Dispatcher通过model解析出ModelAndView()中的参数进行解析最终展现出完整的view并返回给客户端。
DispatcherServlet
Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。
HandlerMapping
Spring mvc 使用HandlerMapping来找到并保存url请求和处理函数间的mapping关系。
以RequestMappingHandlerMapping为例来具体看HandlerMapping的作用
DefaultAnnotationHandlerMapping将扫描当前所有已经注册的spring beans中的@requestmapping标注以找出url 和 handler method处理函数的关系并予以关联。
Handleradapter
Spring MVC通过HandlerAdapter来实际调用处理函数。
以RequestMappingHandlerAdapter为例
DispatcherServlet中根据handlermapping找到对应的handler method后,首先检查当前工程中注册的所有可用的handlerAdapter,根据handlerAdapter中的supports方法找到可以使用的handlerAdapter。通过调用handlerAdapter中的handle方法来处理及准备handler method中的参数及annotation(这就是spring mvc如何将reqeust中的参数变成handle method中的输入参数的地方),最终调用实际的handle method。
ViewResolver
Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。
三、初始化过程
DispatcherServlet的继承体系如:
看到它们继承自HttpServlet,你就知道初始化过程应该是从init方法开始了,整个初始化的流程为:
1、服务器启动的时候,tomcat容器执行init()方法,主要做两个事情:加载web.xml配置的初始化参数、让子类回调同时覆写方法initServletBean()。
- /**
- * 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 {
- // Set bean properties from init parameters.
-
- 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);
-
- // Let subclasses do whatever initialization they like.
- initServletBean();
- }
2、对于第一点的initServletBean(),由子类FrameworkServlet继承,覆写该方法,因此接下来执行该方法。
- /**
- * Overridden method of {@link HttpServletBean}, invoked after any bean properties
- * have been set. Creates this servlet's WebApplicationContext.
- */
- @Override
- protected final void initServletBean() throws ServletException {
-
- this.webApplicationContext = initWebApplicationContext();
- //此方法本类没有代码逻辑,由子类继承覆写该方法,
- //但是DispatcherServlet并没有覆写,一次此方法没有做任何事。
- initFrameworkServlet();
- }
第一行代码的作用是:初始化FrameworkServlet的属性webApplicationContext,这个属性代表SpringMVC上下文,它有个父类上下文,既web.xml中配置的ContextLoaderListener监听器初始化的容器上下文,然后提供onRefresh方法给DispatcherServlet覆写。
进入initWebApplicationContext()方法,我们可以发现代码分成两块,一块是获取WebApplicationContext ,另一块是拿到这个类的实例传递给onfresh()方法。对于onRefresh方法本类并没有任何的逻辑,只是提供了一个模版供子类覆写。
- 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;
- }
3、对于第二点提到的onfresh方法,由子类DispatcherServlet继承覆写,因此接下来执行DispatcherServlet类中的onRefresh()方法。
- /**
- * This implementation calls {@link #initStrategies}.
- */
- @Override
- protected void onRefresh(ApplicationContext context) {
- initStrategies(context);
- }
- protected void initStrategies(ApplicationContext context) {
- initMultipartResolver(context);
- initLocaleResolver(context);
- initThemeResolver(context);
- initHandlerMappings(context);
- initHandlerAdapters(context);
- initHandlerExceptionResolvers(context);
- initRequestToViewNameTranslator(context);
- initViewResolvers(context);
- initFlashMapManager(context);
- }
从代码可以清晰地知道,这个方法就是根据拿到的上下文初始化一些列的策略,即初始化什么样的handlerMappings、handlerAdapters等等,这里我们看initHandlerMappings这个方法,简化后的代码如下:
- /**
- * Initialize the HandlerMappings used by this class.
- * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
- * we default to BeanNameUrlHandlerMapping.
- */
- private void initHandlerMappings(ApplicationContext context) {
- this.handlerMappings = null;
-
- // 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);
- }
-
- // 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);
- }
- }
先根据前面获取好的ApplicationContext获取到所有的handlerMappings,然后排序,这里调试后的结果是这里获取到了两个handlerMapping:RequestMappingHandlerMapping、=和BeanNameUrlHandlerMapping,如果获取到的 handlerMappings 集合为空,则按照默认去加载,这个默认的规则,这个默认的规则是配置在DispatcherServlet.properties中,看下图代码结构即可知道。
四、总结
回顾整个SpringMVC的初始化流程,我们看到,通过HttpServletBean、FrameworkServlet、DispatcherServlet三个不同的类层次,SpringMVC的设计者将三种不同的职责分别抽象,运用模版方法设计模式分别固定在三个类层次中。其中HttpServletBean完成的是<init-param>配置元素的依赖注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具体编程元素的初始化策略。