springMVC是讲spring应用到web开发中。因此要明白springmvc我们得先看看spring和web这两部分。
spring是一个IoC容器,帮助我们处理类的依赖,web开发中应该也有许多的类,他们的控制由spring来反转。
先来看看最原始的,不使用任何框架的情况下,我们是怎么处理请求的。java的web开发,实际上是处理servlet。web服务器比如Tomcat是一个servlet容器。我们并不直接和client的request打交道。client的request都先由servlet容器处理,然后生成request和response交给我们处理,我们从request中解析数据,将处理的结果写入response中,这就是大概的流程,可以参照下面的图片。
下面是一个简单的例子:
<?xml version="1.0" encoding="UTF-8"?> <web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>home</servlet-name> <servlet-class>com.gx.HomeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>home</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
public class HomeServletextends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { PrintWriter writer = resp.getWriter(); writer.print("<html> <body> <h1> hello home </h1> </body> </html>"); } }
我们定义了一个servlet叫做home,home绑定了url : /。当我们访问 http://localhost:8080时,servlet容器就从web.xml中找到这个映射,再找到对应的servlet交给HomeServlet处理,HomeServlet向response中写入一个html。
servletcontext是什么呢?每个web应用都有一个servletcontext和 至少一个servlet。servletcontex用于访问web应用参数、访问服务器信息、全局共享一些信息等。比如:可能所有servlet都会用到的一些对象,我们可以将其设置为servletcontext的属性。
spring是IoC容器,将各种类的控制反转。而web开发中的类,我觉的大致分为两类:
好了再看看springmvc。他主要有两个WebApplicationContex。以我目前的理解,他们其实正是和上面两个方面的类对应。
我们有一些全局共享的类,我们将他们的控制交给root applicationcontext来管理。
另一些只有和servlet相关的类:Controller等,我们将其交给某个DispatchServlet创建的webapplicationcontext来管理。
再来看看dispatchservlet,为什么要有他呢,目前我的理解是为了简化web开发,因为按照最原始的方式开发,我们每增加一个接口就添加一个servlet,这样就需要频繁的配置web.xml。为了解决这个问题,创建一个前端控制器DispatchServlet,他统一接收然后分发给controller。而注解的方式来映射请求和controller,更简单一点。
ok,将他们结合起来,就是在适当的时机创建适当的WebApplicationContex,在用ApplicationContext来管理Beans。这样就将spring应用到了web开发中。
那应该什么时候创建WebApplicationContext呢?
RootApplicationContex是一个带有公共色彩的IoC。我们很容易想到生成一个IoC,将他放到全局的servletcontext,那么这个IoC不就可以全局为我们使用了么。
既然他需要依赖全局的servletcontext,那么就容易联想到应该在全局的servletcontext初始化完成后,就创建这个IoC,然后再将他作为属性添加到servletcontext中。
那么怎么监听servletcontext初始化完成呢?当然是在web.xml中配置一个listener,并指定一个实现了servletcontextlistener的类来监听啦。springmvc也确实是这么做的,他提供一个ContextLoaderListener,这个类实现了servletcontextlistener接口,因此servletcontext初始化完成后会通知ContextLoaderListener,ContextLoaderListener会在这个回调中去初始化Root WebApplicationContext,然后作为属性添加到servletcontext中。
明白了上面的流程,我们就知道了,如果我希望创建一个Root ApplicationContext来管理一些公用的Beans,那么需要在wen.xml中配置一个Listener:
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
不过只配置这个还不够,我们还需要指定这个IoC需要管理的Beans。默认springmvc是创建一个xmlWebApplicationContext,因此需要一个xml的路径,这个路径默认是:WEB-INF/application.xml。不过你可以在web.xml中改变:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/application.xml</param-value> </context-param>
创建WebApplication的参数还有几个,下面介绍下:
<init-param> 默认是xmlWebApplicationContext,不过我们可以通过contextClass来改变,比如下面指定为AnnotationConfigWebApplicationContext,由于他需要一个Config class,所以我们的contextConfigLocation就不再是指定xml而是指定某个类。 <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param>
<init-param> 在WebApplicationContext生成前,还允许我们对他进行些设置,我们可以通过这个参数来指定一个实现了ApplicationContextInitializer接口的类来做些额外的初始化。 <param-name>contextInitializerClasses</param-name> <param-value>xxx</param-value> </init-param>
最后我们可能会想,如果我并没有全局公共的类,因此也不需要一个Root IoC,可以么?是可以的,我们可以不用生成他,方法也就是不配置listener。
这个既然描述的是管理某个Servlet的Bean的IoC,那么他的创建时机也就是这个servlet初始化的时候。springmvc提供了DispatchServlet来作为前端控制器,并生成一个IoC。
DispatchServlet–>FrameworkServlet–>HttpServletBean–>HttpServlet。这是继承关系。
HttpServletBean负责将初始化参数设置到该组件上(contextClass,contextConfigLocation等)。并提供一个initServletBean交给子类实现。
FramworkServlet重写了这个方法。通过contextclass找到对应的class初始化。然后config他。然后如果提供了contextInitializerClasses,那么调用initializer方法。提供onRefresh方法供重载。
DispatchServlet重写了onRefresh,实现了对DispatchServlet的初始化:
initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context);
Servlet3.0+之后可以通过代码来配置。主要是因为servlet容器会查找实现了ServletContainerInitailizer接口的类,如果能发现的话就会用它来配置容器。SpringMvc为这个接口提供了实现类:SpringServletContainerInitializer.这个类又会去找实现了WebApplicationInitializer的类。
import org.springframework.web.WebApplicationInitializer; public class MyWebApplicationInitializerimplements WebApplicationInitializer{ //没有创建root ApplicaitonContext @Override public void onStartup(ServletContext container){ XmlWebApplicationContext appContext = new XmlWebApplicationContext(); appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext)); registration.setLoadOnStartup(1); registration.addMapping("/"); } }
当然spring 也提供了一些实现类:
//用它来提供RootConfig.class和AppConfig.class public class MyWebAppInitializerextends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { MyWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
如果你更喜欢xml,可以参照:
public class MyWebAppInitializerextends AbstractDispatcherServletInitializer{ @Override protected WebApplicationContext createRootApplicationContext(){ return null; } @Override protected WebApplicationContext createServletApplicationContext(){ XmlWebApplicationContext cxt = new XmlWebApplicationContext(); cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); return cxt; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }