1. 跟踪Spring请求

使用Spring构建的Web程序中,请求最先接触到的是Spring中的DispatcherServlet。从图中可以看见DispatcherServlet相当一个调度者,所有的核心环节最终都要汇总到DispatcherServlet中。

Spring 详解(四):Spring MVC
对图流程的概要说明:

  1. DispatcherServlet其实是一个Servlet,用于拦截客户端的所有请求。在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。
  2. DispatcherServlet的任务是将请求发送给Spring MVC控制器(controller)。DispatcherServlet会查询一个或多个处理器映射(handler mapping)来确定请求应该将请求发送给哪个控制器。处理器映射会根据请求所携带的URL信息来进行决策。
  3. 控制器会根据请求参数进行相关的业务处理,一般情况下控制器只负责解析请求中的参数,然后调用其它组件(如业务处理组件)并传递参数来处理请求。控制器在完成逻辑处理后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(model)。不过仅仅给用户返回原始的信息是不够的——这些信息需要以用户友好的方式进行格式化,一般会是HTML。所以,信息需要发送给一个视图(view),通常会是JSP。
  4. 在处理完成客户的请求后会返回逻辑视图的名称和模型。
  5. DispatcherServlet会调用视图解析器,将逻辑视图名称解析成物理视图名称(也就是视图在项目中的路径)。这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名并不直接表示某个特定的JSP。实际上,它甚至并不能确定视图就是JSP。相反,它仅仅传递了一个 逻辑名称,这个名字将会用来查找产生结果的真正视图。DispatcherServlet将会使用视图解析器(view resolver) 来将逻辑视图名匹配为一个特定的视图实现,它可能是也可能 不是JSP。 既然DispatcherServlet已经知道由哪个视图渲染结果,那请求的任务基本上也就完成 了。
  6. DispatcherServlet会调用视图实现,渲染视图,将模型交付给视图。
  7. 通过响应对象response将渲染完成的视图传递给客户端。这样就完成了响应。

2. 搭建Spring MVC 环境

2.1 配置Dispatcher

我们通常会在web.xml文件中配置DispatcherServlet。但是,借助于Servlet 3.0规范和Spring3.1功能的增强,这种方式已经不是唯一方式。我们将使用java类来配置DispatcherServlet。MVCWebAppInitializer 继承 AbstractAnnotationConfigDispatcherServletInitializer,这个类已经隐身的帮我配置了DispatcherServlet。

public class MVCWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebAppConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果能发现的话,就会用它来配置Servlet容器。Spring提供了这个接口的实现,名为SpringServletContainerInitializer。这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给他们来完成。Spring 3.2 引入了一个便利的AbstractAnnotationConfigDispatcherServletInitializer。因为我们的MVCWebAppInitializer继承自AbstractAnnotationConfigDispatcherServletInitializer所以当部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。

实际上AbstractAnnotationConfigDispatcherServletInitializer会同时创建Dispatcher和ContextLoaderListener。getServletConfigClasses()方法将会创建DispatcherServlet应用上下文的Bean容器。getRootConfigClasses()方法将会创建ContextLoaderListener应用上下文Bean容器。

2.2 两个应用上下文的故事

DispatcherServlet应用上下文的Bean容器:用来加载包含Web组件的Bean,比如控制器、实体解析器以及处理器映射、过滤器、拦截器等。

ContextLoaderListener应用上下文Bean容器:要加载应用中的其他Bean,比如这些Bean通常是后端驱动应用程序的中间层和数据层组件。

下面我们来看具体的两个Spring容器的实现:

DispatcherServlet应用上下文的Bean容器
@Configuration
@EnableWebMvc
@ComponentScan("com.hust.edu.controller")
public class WebAppConfig extends WebMvcConfigurerAdapter{

    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver=new InternalResourceViewResolver();
        viewResolver.setPrefix("/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setExposeContextBeansAsAttributes(true);
        return viewResolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

ContextLoaderListener应用上下文Bean容器
@Configuration
@ComponentScan(basePackages = "com.hust.edu",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,value = EnableWebMvc.class)
})
public class RootConfig {
}

3. 深入理解这两个应用上下文容器

  • getRootConfigClasses: Specify @Configuration and/or @Component classes to be provided to the root application context.(将有@Component注解或者@Component注解的配置类提供给根应用上下文)
  • getServletConfigClasses:Specify @Configuration and/or @Component classes to be provided to the dispatcher servlet application context.(将有@Component注解或者@Component注解的配置类提供给DispatcherServlet应用上下文)

我们一定会疑惑,这里在Web应用程序中有两个Spring IOC容器创建,那么Web应用程序到底会使用哪个容器呢?现在通过我们下面的解释,我们将解答这个问题。

在Spring中,应用上下文(ApplicationContext)的实例是可以范围化(Scoped)的,在Web MVC框架中,每个DispatcherServlet都可以有自己的Web应用上下文(WebApplicationContext),每个DispatcherServlet自己的WebApplicationContext继承所有的在根应用上下文中的bean。根应用上下文应该包含在其他DispatcherServlet中会用到的bean,比如DataSource等,这些在根应用上下文中的bean可以在DispatcherServlet自己的WebApplicationContext中被重载。

在Spring MVC中我们其实是可以创建出多个DispatcherServlet的(只要创建多个继承自AbstractAnnotationConfigDispatcherServletInitializer的类即可)。而每个DispatcherServlet有自己的应用上下文(WebApplicationContext),这个应用上下文只针对这个DispatcherServlet有用(比如本博客开头描述的图)。这也就是getServletConfigClasses的作用,获取这个DispatcherServlet的应用上下文的配置类。

而除了每个DispatcherServlet配置类的应用上下文之外,还有一个根应用上下文,这个应用上下文的作用是为了在多个DispatcherServlet之间共享Bean,比如数据源Bean,这就是getRootConfigClasses的作用,用于返回根应用上下文的配置类。

Spring框架的机制会保证如果在当前DispatcherServlet的应用上下文中没有找到想要的bean时,会去根应用上下文中去找。

Spring 详解(四):Spring MVC

Web应用中有DispatcherServlet时,每个DispatcherServlet的bean如Controller,ViewResolver,HandlerMapping等,均在getServletConfigClasses返回的类中配置。其中每个DispatcherServlet表示拦截器将拦截的不同URL,而这个生成的IOC容器将保存这些不同URL的Controller,ViewResolver,HandlerMapping。而一些公共的bean,如Services,Repository均在getRootConfigClasses返回的类中配置。

Spring 详解(四):Spring MVC

当Web应用中只有一个DispatcherServlet中时,可以将所有的bean配置均写在根应用上下文中。DispatcherServlet获取想要的bean时,如果没有在自己的应用上下文中找到,则会自动到根应用上下文中去找。这也就是Spring MVC的ApplicationContext继承机制。

相关文章: