Spring MVC

设计良好的控制器本身只处理很少甚至不处理工作,而是将业务逻辑委托给一个或多个服务对象。

使用Java类配置Spring MVC

Servlet 3.0 查找 javax.servlet.ServletContainerInitializer 的实现类来配置 Servlet 容器;Spring 提供了该接口的实现:SpringServletContainerInitializer , 并且又会查找实现WebApplicationInitializer 的类来进行配置,并引入了一个AbstractAnnotationConfigDispatcherServletInitializer的基础实现,所以在Spring中通过Java代码进行配置的时候,需要扩展该基础实现类。

这种配置DispatcherServlet的方式只能部署到支持Servlet 3.0的服务器中才能工作。

UML 图示:

SpringServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Delegate the ServletContext to any WebApplicationInitializer implementations present on the application classpath.
Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring's WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method.

Assuming that one or more WebApplicationInitializer types are detected, they will be instantiated (and sorted if the @@Order annotation is present or the Ordered interface has been implemented). Then the WebApplicationInitializer.onStartup(ServletContext) method will be invoked on each instance, delegating the ServletContext such that each instance may register and configure servlets such as Spring's DispatcherServlet, listeners such as Spring's ContextLoaderListener, or any other Servlet API componentry such as filters.

/* Servlet 3.0+ 会自动在应用的 classpath 中寻找 WebApplicationInitializer 的实现类,并对每一个实现类进行实例化,将 ServletContext 委托给他们,对每一个实例调用 WebApplicationInitializer.onStartup(ServletContext), 加载和注册所有的 ServletListenerFilter */

If no WebApplicationInitializer implementations are found on the classpath, this method is effectively a no-op. An INFO-level log message will be issued notifying the user that the ServletContainerInitializer has indeed been invoked but that no WebApplicationInitializer implementations were found.


// org.springframework.web.SpringServletContainerInitializer

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, @NotNull ServletContext servletContext) throws ServletException {

}
}

Root Application Context
As such, it typically contains middle-tier services, data sources, etc.

Servlet Application Context
As such, it typically contains controllers, view resolvers, locale resolvers, and other web-related beans.

Root Application Context 用来初始化 ContextLoaderListener 对象,并将该对象注册到 Servlet 容器中

Servlet Application Context 用来初始化 DispatcherServlet 对象,并将该对象注册到 Servlet 容器中,随后添加 Filters, 添加 Mapping 映射, 当然用户还可以通过 customizeRegistration(ServletRegistration.Dynamic registration) 来进行自定义之后的操作,如配置 Multipart 处理的参数

DispatcherServlet

1
2
3
4
5
6
7
8
// DispatcherServlet api 与 Web.xml中 对应的初始化参数
setContextClass(Class) / 'contextClass'
setContextConfigLocation(String) / 'contextConfigLocation'
setContextAttribute(String) / 'contextAttribute'
setNamespace(String) / 'namespace'

// indicates which ApplicationContextInitializer classes should be used to further configure the internal application context prior to refresh().
setContextInitializerClasses(String) / 'contextInitializerClasses'

编写控制器方法

  • 处理查询参数请求
1
2
3
4
5
6
7
8
/**
* 对类似 /messageList?count=10 的url进行处理
*/
@RequestMapping(value = "/messageList", method = RequestMethod.GET)
public String message(@RequestParam(value = "count", defaultValue = "20") int count, Model model) {
model.addAttribute("messageList", repository.getMessageList(count));
return "message";
}
  • 处理路径变量
1
2
3
4
5
6
7
8
/**
* 对类似 /messageList/Tom 这样的url进行处理
*/
@RequestMapping(value = "/messageList/{name}", method = RequestMethod.GET)
public String messageListByName(@PathVariable String name, Model model) {
model.addAttribute("messageList", repository.getMessageListByName(name));
return "message";
}
  • 处理表单提交,以及增加校验
1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(@Valid User user, Errors errors) {
if (errors.hasErrors()) {
errors.getAllErrors().forEach(error -> printError(error));
return "registerFormSpringForm";
}
System.out.println("--------" + user.getUsername());
repository.saveUser(user);
return "redirect:/blog/" + user.getUsername();
}

==C:\Users\guo.IntelliJIdea2018.3\system\tomcat\Unnamed_spring_web_java_config\work\Catalina\localhost\ROOT==

表单数据校验

在使用validator对表单数据进行校验时,需要

Spring对Java校验API(Java Validation API,又称JSR-303)的支持。从Spring 3.0开始,在Spring MVC中提供了对Java校验API的支持。在Spring MVC中要使用Java校验API的话,并不需要什么额外的配置。只要保证在类路径下包含这个Java API的实现即可,比如Hibernate Validator。

java的 JSR 303: Bean Validation 只是一个规范,并没有具体的实现,hibernate-validator 项目是对其的一个实现,因此,具体使用要引入相应的JAR包:validation-api(定义校验注解) hibernate-validator(具体实现)

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>

==并且如果之前没有添加,后来添加了,要生效,必须重新打包部署,即重新生产 Artifacts, 并部署到tomcat上,否则就不会起作用!!==

使用spring-test进行测试

在用spring-test进行测试的时候,需要和spring其他组件的jar包版本一致,否则就可能会出现NoSuchMethodError

1
java.lang.NoSuchMethodError: org.springframework.util.StreamUtils.emptyInput()Ljava/io/InputStream;

Apache Common Lang包来实现equals()hashCode()方法。

对“/spittles/12345”发起GET请求要优于对“/spittles/show?spittle_id=12345”发起请求。前者能够识别出要查询的资源,而后者描述的是带有参数的一个操作——本质上是通过HTTP发起的RPC。