之前一直在看《Spring实战》第三版,看到第五章时发现很多东西已经过时被废弃了,于是现在开始读《Spring实战》第四版了,章节安排与之前不同了,里面应用的应该是最新的技术。
本章中,将会接触到Spring MVC基础,以及如何编写控制器来处理web请求,如何通明地绑定请求参数到业务对象上,同时还可以提供数据校验和错误处理的功能。
Spring MVC初探
跟踪Spring MVC请求
在请求离开浏览器时,会带有用户所请求内容的信息,例如请求的URL、用户提交的表单信息。
请求旅程的第一站是Spring的DispatcherServlet。Spring MVC所有的请求都会通过一个前端控制器Servlet。前端控制器是常用的Web应用程序模式,在这里一个单实例的Servlet将请求委托给应用程序的其他组件来执行实际的处理。在Spring MVC中,DispatcherServlet 就是前端控制器。
DispatcherServlet的任务是将请求发送给Spring MVC**控制器。控制器是一个用于处理请求的Spring组件。在典型的应用程序中可能会有多个控制器, Dispatcher Servlet需要知道应该将请求发送给哪个控制器。所以DispatcherServlet会查询一个或多个处理器映射来确定请求的下一站在哪里。处理器映射**会根据请求所携带的URL信息来进行决策。
一旦选择了合适的控制器,DispatcherServlet会将请求发送给选中的控制器。到达了控制器,请求会卸下其负载(用户提交的信息)并等待控制器处理这些信息(实际上,设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给个或多个服务对象)。
控制器在完成逻辑处理后通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示。这些信息被称为模型(Model)。不过仅仅给用户返回原始的信息是不够的–这些信息需要以用户友好的方式进行格式化,一般是HTML。所以,信息需要发送给—个视图(View),通常会是JSP。
控制器所做的最后一件事是将模型数据打包,并且标示出用于渲染输出的视图名称。它接下来会将请求连同模型和视图名称发送回DispatcherServlet。
这样,控制器就不会与特定的视图相耦合,传递给DispatcherServlet的视图名称并不直接表示某个特定的JSP。它仅仅传递了一个逻辑名,这个名字将会用来查找用来产生结果的真正视图。DispatcherServlet将会使用视图解析器来将逻辑视图名匹配为一个特定的视图实现。
既然DispatcherServlet已经知道由哪个视图渲染结果,那么请求的任务基本上也就完成了。它的最后一站是视图的实现(可能是JSP),在这里它交付模型数据。请求的任务就完成了。视图将使用模型数据渲染输出,并通过这个输出将响应对象传递给客户端。
搭建Spring MVC
配置DispatcherServlet
DispatcherServlet是Spring MVC的核心,它负责将请求分发到其他各个组件。
在旧版本中,DispatcherServlet之类的servlet一般在web.xml
文件中配置,该文件一般会打包进最后的war包种;但是Spring3引入了注解,我们在这一章将展示如何基于注解配置Spring MVC。
注意:
在使用maven构建web工程时,由于缺少web.xml文件,可能会出现web.xml is missing and <failOnMissingWebXml> is set to true
这样的错误,那么可以通过在pom.xml文件中添加如下配置来避免这种错误:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
既然不适用web.xml
文件,你需要在servlet容器中使用Java配置DispatcherServlet,具体的代码列举如下:
package spittr.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
}
任意继承自AbstractAnnotationConfigDispatcherServletInitializer
的类都会被自动用来配置DispatcherServlet,这个类负责配置DispatcherServlet、初始化Spring MVC容器和Spring容器。
SpittrWebAppInitializer重写了三个方法,getRootConfigClasses()
方法用于获取Spring应用容器的配置文件,这里我们给定预先定义的RootConfig.class;getServletConfigClasses()
负责获取SpringMVC应用容器,这里传入预先定义好的WebConfig.class;getServletMappings()
方法负责指定需要由DispatcherServlet映射的路径,这里给定的是”/”,意思是由DispatcherServlet处理所有向该应用发起的请求。
两种应用上下文
当DispatcherServlet启动时,会创建一个Spring应用上下文并且会加载配置文件中声明的bean,通过getServletConfigClasses()
方法,DispatcherServlet会加载WebConfig
配置类中所配置的bean。
在Spring web应用中,通常还有另外一种应用上下文:ContextLoaderListener
。
DispatcherServlet用来加载web组件bean,如控制器(controllers)、视图解析器(view resolvers)以及处理器映射(handler mappings)等。而ContextLoaderListener则用来加载应用中的其他bean,如运行在应用后台的中间层和数据层组件。
AbstractAnnotationConfigDispatcherServletInitializer会同时创建DispatcherServlet和ContextLoaderListener。getServletConfigClasses()
方法返回的@Configuration
类会定义DispatcherServlet应用上下文的bean。同时,getRootConfigClasses()
返回的@Configuration
类用来配置ContextLoaderListener上下文创建的bean。
相对于传统的web.xml
文件配置的方式,通过AbstractAnnotationConfigDispatcherServletInitializer来配置DispatcherServlet是一种替代方案。需要注意的是,这种配置只适用于Servlet 3.0,例如Apache Tomcat 7或者更高。
激活Spring MVC
正如有多种方式可以配置DispatcherServlet,激活Spring MVC组件也有不止一种方法。一般的,都会通过XML配置文件的方式来配置Spring,例如可以通过<mvc:annotation-driven>
来激活基于注解的Spring MVC。
最简单的配置Spring MVC的一种方式是通过@EnableWebMvc
注解:
package spittr.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@EnableWebMvc
public class WebConfig {
}
@Configuration
表示这是Java配置类;@EnableWebMvc
注解用于启动Spring MVC特性。
这样就可以激活Spring MVC了,但是还有其他一些问题:
- 没有配置视图解析器(view resolvers),这种情况下,Spring会默认使用BeanNameViewResolver
,它会通过寻找那些与视图id匹配的bean以及实现了View接口的类进行视图解析;
- 没有激活组件扫描:这样Spring会寻找配置中明确声明的任意控制器;
- DispatcherServlet会处理所有的请求,包括静态资源请求,如图片和样式(这些往往不是我们想要的)。
因此,需要为WebConfig增加一些配置:
package spittr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@ComponentScan("spitter.web") // 激活Spring MVC
public class WebConfig extends WebMvcConfigurerAdapter {
// 配置一个JSP视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB_INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
首先需要注意的是,WebConfig
使用了@ComponentScan
注解,因此会在spitter.web
包下扫描寻找组件,这些组件包括使用@Controller
进行注解的控制器。这样就不再需要在配置类中显式地声明其他控制器。
接下来,添加了一个ViewResolver
bean,即InternalResourceViewResolver
。它通过匹配符合设置的前缀和后缀的视图来用来寻找对应的JSP文件,比如视图home会被解析为/WEB-INF/views/home.jsp。这里的三个函数的含义依次是:setPrefix()
方法用于设置视图路径的前缀;setSuffix()
用于设置视图路径的后缀,即如果给定一个逻辑视图名称——”home”,则会被解析成”/WEB-INF/views/home.jsp”; setE