五、SpringMVC起步

​SpringMVC基于模型-视图-通知器(Model-View-Controller,MVC)模式实现的,它能够帮助我们构建像Spring框架那样灵活和松耦合的Web应用程序。

跟踪SpringMVC流程

图示:

步骤说明:

  1. 用户发送请求到前端控制器(DispatchServlet),该控制器会过滤出哪些请求可以访问servlet、哪些不能访问。就是URL-Pattern的作用,并且会加载SpringMVC.xml配置文件。
  2. 前端控制器会找到处理器映射器(HandlerMapping),通过HandlerMapping完成URL到Controller映射的组件,简单来说,就是将在SpringMVC.xml中配置的或者注解的URL与对应的处理类找到并进行存储,用Map<URL,handler>这样的方式来存储。
  3. HandlerMapping有了映射关系,并且找到了URL对应的处理器,HandlerMapping就会将其处理器(Handler)返回,在放回前,会加上很多拦截器。
  4. DispatchServlet拿到Handler之后,找到HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。
  5. 执行处理器。
  6. 处理器会返回一个ModelAndView对象到HandlerAdapter。
  7. 通过HandlerAdapter将ModelAndView对象返回到前端控制器DispatchServlet。
  8. 前端控制器请求视图解析器(ViewResolver)去进行视图解析,根据逻辑视图名解析成真真的视图(JSP),其实就是将ModelAndView对象中存放视图的名称进行查找,找到对应的页面形成视图对象。
  9. 返回视图对象到前端控制器。
  10. 视图渲染,就是将ModelAndView对象中的数据放到Request域中,用来让页面加载数据的。
  11. 通过第8步,通过名称找到了对应的页面,通过第10步,request域中有了所需要的数据,那么就可以进行视图渲染了。最后将其返回即可。

这里再附上网上的一张详细的图:

web.xml版本的Web项目

1. 项目结构

说明:蓝色的Java文件夹,标注为 sources,标有条形圆筒的resources文件夹标注为Resources。

  • java文件夹用来存放java源码。
  • resources文件夹用来存放资源文件:如spring配置文件,mapper文件。
  • webapp目录则用来存放web相关的资源。

File–>Project Structure

2. Pom 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ooyhao.spring</groupId>
<artifactId>spring-in-action-05-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>spring-in-action-05-01 Maven Webapp</name>
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.15.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!-- json 解析,不配置转化器和引入依赖,返回List则会抛出异常-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>


</dependencies>

<build>
<finalName>spring-in-action-05-01</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

3. 生成web.xml

项目结构搭建好之后,需要生成web.xml文件并配置。

web.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>

<!--配置applicationContext配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>


<!--配置编码过滤器-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--配置Spring上下文加载器的监听器-->
<!--默认加载classpath:下面的applicationContext.xml文件。需要自定义可以配置
<context-param></context-param> 进行配合使用
-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

4. 配置Spring和SpringMVC

springmvc.xml配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
">

<context:component-scan base-package="com.ooyhao.spring.**.controller"/>


<!--注解驱动,配置controller返回的转换器-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>

<!--解决@ResponseBody返回中文乱码情况-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" >
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=utf-8</value>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!--静态页面,如html,css,js,images可以访问-->
<mvc:default-servlet-handler/>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

spring.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--组件扫描-->
<context:component-scan base-package="com.ooyhao.spring">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>

5. 编写逻辑代码

User类:

1
2
3
4
5
6
7
8
9
public class User implements Serializable {
private String username;
private Integer age;
private String sex;
//noArgsConstructor
//allArgsConstructor
//getter and setter
//toString
}

UserService类:

1
2
3
4
5
6
7
8
9
@Service
public class UserService {
public List<User> findAllUsers(){
List<User> users = new ArrayList<>();
users.add(new User("林黛玉",18,"女"));
users.add(new User("贾宝玉",20,"男"));
return users;
}
}

UserController类:

1
2
3
4
5
6
7
8
9
10
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/hello")
@ResponseBody
public List<User> hello(){
return userService.findAllUsers();
}
}

6. 测试和结果

启动程序,并测试 localhost:8080/SpringDemo/getAllUsers

无配置文件的Web项目

如果使用过SpringBoot的都知道,SpringBoot可以做到零配置,即没有xml文件,这是因为Spring3.2之后,引入了WebApplicationInitializer。我们可以看一下这个类的源码中:

容在下,拿着这一级英语水平去翻译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/**
Interface to be implemented in Servlet 3.0+ environments in order to configure the {@link ServletContext} programmatically as opposed to (or possibly in conjunction with) the traditional {@code web.xml}-based approach.
译:
在servlet3.0版本以上,通过实现接口以编程的方式进行配置ServletContext,这与传统的web.xml的形式近乎相反(不用web.xml文件,同样可以创建一个web应用)

Implementations of this SPI will be detected automatically by {@link
SpringServletContainerInitializer}, which itself is bootstrapped automatically by any Servlet 3.0 container.
译:
实现了这个SPI,将会被SpringServletContainerInitializer自动识别,并自动启动。

See {@linkplain SpringServletContainerInitializer its
Javadoc} for details on this bootstrapping mechanism.
译:
可以通过查看SpringServletContainerInitializer文档去了解自启动机制的详情。

<h2>Example</h2>
译:示例
<h3>The traditional, XML-based approach</h3>
译:传统基于Xml的方式
Most Spring users building a web application will need to register Spring's {@code DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as,follows:
译:大部分Spring用户在构建一个web应用程序都需要去注册一个Spring的前端控制器(DispatchServlet),而其典型的就是在WEB-INF下的web.xml文件中注册。如下:

--->源码不是这样的,源码的尖括号使用了转义字符
* <servlet>
* <servlet-name>dispatcher</servlet-name>
* <servlet-class>
* org.springframework.web.servlet.DispatcherServlet
* </servlet-class>
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>/WEB-INF/spring/dispatcher-config.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>

以上配置就是使用传统的xml配置文件的方式,向servlet容器中注册一个DispatchServlet。

The code-based approach with {@code WebApplicationInitializer}
* Here is the equivalent {@code DispatcherServlet} registration logic,
* {@code WebApplicationInitializer}-style:
译:
通过使用WebApplicationInitializer的基于代码的方式,其与xml是一个相同的注册逻辑。WebApplicationInitializer代码方式:

* public class MyWebAppInitializer implements WebApplicationInitializer {
* @Override
* public void onStartup(ServletContext container) {
* XmlWebApplicationContext appContext = new XmlWebApplicationContext();
* appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
*
* ServletRegistration.Dynamic dispatcher =
* container.addServlet("dispatcher", new
DispatcherServlet(appContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
* }
*
* As an alternative to the above, you can also extend from {@link
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}.

译:你也可以通过集成自AbstractDispatcherServletInitializer来实现,以上二者任选其一


* As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method we're actually registering an instance of the {@code DispatcherServlet}, and this means that the {@code DispatcherServlet} can now be treated like any other object receiving constructor injection of its application context in this case.

译:如你所见,正是由于servlet3.0 的新方法(ServletContext#addServlet)出现,我们可以通过它注册一个DispatchServlet的实例了。这也意味着DispatchServlet可以像其他对象那
样,通过构造注入的方式接受一个Application Context

This style is both simpler and more concise. There is no concern for dealing with init-params, etc, just normal JavaBean-style properties and constructor arguments. You are free to create and work with your Spring application contexts as necessary before injecting them into the {@code DispatcherServlet}.

译:这种方式既简单又简洁。我们不需要如何去处理初始化参数,等,仅仅需要处理像普通JavaBean那样的属性和构造方法参数。在你必须将他们注入到DispatchServlet中之前,你可以很自由的构建和处理你的spring应用程序。

Most major Spring Web components have been updated to support this style of registration. You'll find that {@code DispatcherServlet}, {@code FrameworkServlet},{@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support constructor arguments. Even if a component (e.g. non-Spring, other third party) has not been specifically updated for use within {@code WebApplicationInitializers}, they still
may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting init-params, context-params, etc programmatically.

译:大部分Spring web 组件都进行了更新,以至于能够支持这个注册方式,你可以发现像 DispatcherServlet、FrameworkServlet、ContextLoaderListener和DelegatingFilterProxy ,现在全部都支持构造参数注入。甚至是一些非spring,其他第三方组织的组件在WebApplicationInitializers中使用的没有支持的也会更新,以至于一直可以使用。Servlet3.0 API 也可以通过编程的方式 去设置 初始化参数、容器参数等


A 100% code-based approach to configuration In the example above,
{@code WEB-INF/web.xml} was successfully replaced with code in
* the form of a {@code WebApplicationInitializer}, but the actual
* {@code dispatcher-config.xml} Spring configuration remained XML-based.
* {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based
* {@code @Configuration} classes.
See @{@link org.springframework.context.annotation.Configuration Configuration} Javadoc for complete details, but the following example demonstrates refactoring to use Spring's {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and user-defined {@code @Configuration} classes {@code AppConfig} and {@code DispatcherConfig} instead of Spring XML files.

译 :上面是一个完全由代码的配置方式的案例,web.xml文件已经被一个WebApplicationInitializer 成功取代。当然,实际上 Spring的配置任然保持着基于xml方式,WebApplicationInitializer 是一个更加适合 Spring代码的 配置类。可以查看Configuration的文档,了解完整的详情。但是下面这个例子是使用Spring的 AnnotationConfigWebApplicationContext 来代替XmlWebApplicationContext进行重构的。并且用户通过@Configuration,AppConfig,DispatcherConfig来代替xml配置文件。



This example also goes a bit beyond those above to demonstrate typical configuration of the 'root' application context and registration of the {@code ContextLoaderListener}:
译:这个例子比上面的例子更进一步,使用root application context 来重构的典型配置,并且注册ContextLoaderListener

* public class MyWebAppInitializer implements WebApplicationInitializer {
* @Override
* public void onStartup(ServletContext container) {
* // Create the 'root' Spring application context
创建root Spring的应用上下文
* AnnotationConfigWebApplicationContext rootContext =
* new AnnotationConfigWebApplicationContext();
* rootContext.register(AppConfig.class);

* // Manage the lifecycle of the root application context
管理root 应用上下文的生命周期
* container.addListener(new ContextLoaderListener(rootContext));

* // Create the dispatcher servlet's Spring application context
// 创建了Servlet的前端控制器的Spring应用上下文
* AnnotationConfigWebApplicationContext dispatcherContext =
* new AnnotationConfigWebApplicationContext();
* dispatcherContext.register(DispatcherConfig.class);

* // Register and map the dispatcher servlet
//注册并且映射前端控制器
* ServletRegistration.Dynamic dispatcher =
* container.addServlet("dispatcher", new
DispatcherServlet(dispatcherContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
* }



*
* As an alternative to the above, you can also extend from {@link
org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}.
你也可以通过集成自AbstractAnnotationConfigDispatcherServletInitializer,以上二者任选其一。

Remember that {@code WebApplicationInitializer} implementations are
detected automatically -- so you are free to package them within your application as you see fit.

记住,WebApplicationInitializer 的实现类 都是自动识别的。所以,你可以自由的将他们打包到你认为合适的应用程序中。


Ordering {@code WebApplicationInitializer} execution
{@code WebApplicationInitializer} implementations may optionally be annotated at the
* class level with Spring's @{@link org.springframework.core.annotation.Order Order}
* annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered}
* interface. If so, the initializers will be ordered prior to invocation. This provides
* a mechanism for users to ensure the order in which servlet container initialization
* occurs. Use of this feature is expected to be rare, as typical applications will likely
* centralize all container initialization within a single {@code WebApplicationInitializer}.

Caveats :说明、警告

web.xml versioning
web.xml版本

{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually exclusive; for example, web.xml can register one servlet, and a {@code WebApplicationInitializer} can register another. An initializer can even modify registrations performed in {@code web.xml} through methods such as {@link ServletContext#getServletRegistration(String)}.

译:web.xml与WebApplicationInitializer不会相互排斥,例如,web.xml可注册一个Servlet,而WebApplicationInitializer可以注册另一个,后者甚至可以修改在xml中注册的。


However, if {@code WEB-INF/web.xml} is present in the application, its {@code version} attribute must be set to "3.0" or greater, otherwise {@code ServletContainerInitializer} bootstrapping will be ignored by the servlet container

译:然而,如果web.xml存在于应用中,那么它的版本必须在3.0或以上,否则ServletContainerInitializer 自己化启动将会被Servlet容器忽略。


* <h3>Mapping to '/' under Tomcat</h3>、
在tomcat下映射/
* <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions
<= 7.0.14, this servlet mapping cannot be overridden programmatically
7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested successfully under GlassFish 3.1
译:在tomcat7.0.14及以下,tomcat使用其自身的DefaultServlet去映射/,这个Servlet映射将不能被编程方式重写在7.0.15版本。在GlassFish 3.1下, 成功测试出可以重写/这个Servlet映射器。
*/

public interface WebApplicationInitializer {

/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners context-params and attributes necessary for initializing this web application.
为你这个web容器配置任何必要的 filter,listener,servlet,及其参数和属性

See examples {@linkplain WebApplicationInitializer above}.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;

}

​ 当你耐心的看完源码和翻译,你大概就知道SpringBoot为何可以做到不用web.xml文件就可以搭建一个web项目。这是使用了WebApplicationInitializer。其实当我们好好学习Spring,会发现曾经SpringBoot那些神秘面纱,慢慢被揭开了。这也是我学了SpringBoot基础之后,又回头学习Spring的原因。废话不多说了,开始搭建我们无配置文件的wen项目。

1. 替代web.xml的InitWeb

web.xml这里使用InitWeb.java代替,这里使用的是AbstractAnnotationConfigDispatcherServletInitializer,WebApplicationInitializer的子类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InitWeb extends AbstractAnnotationConfigDispatcherServletInitializer {


@Override
protected Class<?>[] getRootConfigClasses() {
//配置rootConfig,即原来的applicationContext.xml
return new Class[]{RootConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
//配置ServletConfig,即原来的springmvc.xml
return new Class[]{ServletConfig.class};
}

@Override
protected String[] getServletMappings() {
//配置servlet映射,即之前在web.xml中DispatchServlet中配置的。
return new String[]{"/"};
}
}

2. POM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ooyhao.spring</groupId>
<artifactId>spring-in-action-05-02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>spring-in-action-05-02 Maven Webapp</name>
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

</dependencies>

<build>
<finalName>spring-in-action-05-02</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

3. RootConfig

1
2
3
4
5
/*相当于spring.xml*/
@ComponentScan(basePackages = "com.ooyhao.spring", useDefaultFilters = true,excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = EnableWebMvc.class)
})
public class RootConfig {

扫描基础包,使用默认的过滤器。即全部都扫描。排除标有Controller注解的。

4. ServletConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*相当于springmvc.xml*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.ooyhao.spring.**.controller")
public class ServletConfig implements WebMvcConfigurer {


/*配置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();
}
}

可以看出,SpringMvc的配置文件,比前面的spring配置文件多了很多内容。扫描所有的Controller,配置视图解析器,配置静态资源处理。开启webmvc(@EnableWebMvc)

@EnableWebMvc 相当于之前在springmvc.xml文件中配置的<mvc:annotation-driven>

5. 编写逻辑代码

Article:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Article implements Serializable {


private Integer id;
private String title;
private String content;
private String author;

public Article() {}

public Article(Integer id, String title, String content,
String author) {
this.id = id;
this.title = title;
this.content = content;
this.author = author;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

@Override
public String toString() {
return "Article{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
", author='" + author + '\'' +
'}';
}
}

ArticleService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class ArticleService {

private static List<Article> articles = new ArrayList<>();

static {
Article article1 = new Article(1,"红楼梦","红楼梦","曹雪芹");
Article article2 = new Article(2,"三国演义","三国演义","罗贯中");
Article article3 = new Article(3,"水浒传","水浒传","施耐庵");
Article article4 = new Article(4,"西游记","西游记","吴承恩");
articles.add(article1);
articles.add(article2);
articles.add(article3);
articles.add(article4);
}

public List<Article> findAllArticle(){
return articles;
}
}

ArticleController:

1
2
3
4
5
6
7
8
9
10
11
12
@Controller
public class ArticleController {
@Autowired
private ArticleService articleService;

@RequestMapping("/findAllArticles")
@ResponseBody
public List<Article> findAllArticles(){
List<Article> articles = articleService.findAllArticle();
return articles;
}
}

6. 测试和结果

接受请求参数的输入

SpringMVC允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:

  • 查询参数(Query Parameter)
  • 表单参数(Form Patameter)
  • 路径参数(Path Variable)

将上述示例进行了一定的修改,配合页面进行展示:

1. 查询参数

查询参数,我们以分页来做示例:

ArticleService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class ArticleService {
private static Map<Integer,Article> articles = new HashMap<>();

static {
Article article1 = new Article(1,"红楼梦","林黛玉、贾宝玉","曹雪芹");
Article article2 = new Article(2,"三国演义","诸葛亮、刘备","罗贯中");
Article article3 = new Article(3,"水浒传","鲁智深、武松","施耐庵");
Article article4 = new Article(4,"西游记","孙悟空、猪八戒","吴承恩");
articles.put(1,article1);
articles.put(2,article2);
articles.put(3,article3);
articles.put(4,article4);
}

public List<Article> findAllArticle(){
return new ArrayList<>(articles.values());
}

public Article findArticleById(Integer id) {
return articles.get(id);
}

public List<Article> findArticleByPage(Integer page, Integer size){
int firstIndex = (page-1) * size;
int lastIndex = page * size;
return new ArrayList<>
(articles.values()).subList(firstIndex,lastIndex);
}

}

ArticleController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller
public class ArticleController {

@Autowired
private ArticleService articleService;

@GetMapping("/article")
public String article(Model model,
@RequestParam(value = "page",defaultValue = "1") Integer page,
@RequestParam(value = "size",defaultValue = "2") Integer size){
List<Article> articles = articleService.findArticleByPage(page,
size);
model.addAttribute("articles",articles);
return "articleList";
}
}

articleList.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: ouYang
Date: 2019/9/1
Time: 11:13
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>

<%--文章信息--%>
<h1>文章列表</h1>

<c:forEach items="${articles}" var="article">
<ul>
<li>ID:<c:out value="${article.id}"/></li>
<li>标题:<c:out value="${article.title}"/></li>
<li>内容:<c:out value="${article.content}"/></li>
<li>作者:<c:out value="${article.author}"/></li>
</ul>
</c:forEach>
</body>
</html>

测试及结果:

说明:

​ 请求参数会拼接在请求路径后面使用?隔开,参数和值之间使用=连接。多个参数之间使用&进行拼接。Controller可以通过@RequestParam注解接收,可以设置默认值,当然,也可以省略@RequestParam注解。

2. 通过路径参数

ArticleController:

1
2
3
4
5
6
7
8
9
/*将参数写到路径上*/
@GetMapping("/article/{id}")
public String article(Model model,@PathVariable("id") Integer id){
Article article = articleService.findArticleById(id);
List<Article> articles = new ArrayList<>();
articles.add(article);
model.addAttribute("articles",articles);
return "articleList";
}

测试及结果:

说明:

​ 可以看出,路径参数与请求参数是不一样的,路径参数是请求地址中的一部分,在Controller中通过@PathVariable注解来注释接受。/article/{id} 在路径上使用{}来标注参数的位置。

3. 处理表单

处理表单参数,首先我们需要一个表单,这里以注册为例:

register jsp文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
Created by IntelliJ IDEA.
User: ouYang
Date: 2019/9/1
Time: 10:29
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Blog</title>
</head>
<body>

<h1>欢迎加入Spring的大家庭</h1>
<form method="post" >
用户名:<input name="username" type="text" ><br>
密码:<input name="password" type="password" ><br>
年龄:<input name="age" type="number" ><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>

一个普通的表单,使用post请求提交,当我们没有指定action属性的时候,点击提交之后,会使用原地址,即请求表单页面的地址,通过post请求提交,本例中是/register. 表单包括 用户名,密码和年龄三个属性。后台进行相应的Bean接受(User)

User:

1
2
3
4
5
6
public class User implements Serializable {
private String username;
private String password;
private Integer age;
//all/noArgConstructor, getter and setter, toString
}

UserService:

1
2
3
4
5
6
7
@Service
public class UserService {
private static List<User> users = new ArrayList<>();
public void saveUser(User user){
users.add(user);
}
}

IndexController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Controller
public class IndexController {

@Autowired
private UserService userService;

@GetMapping("/")
public String home(){
return "home";
}

@GetMapping("/register")
public String toRegister(){
return "register";
}

/*处理表单数据,并验证*/
@PostMapping("/register")
public String register(User user){
userService.saveUser(user);
return "redirect:/registerSuccess";
}

@GetMapping("/registerSuccess")
public String registerSuccess(){
return "registerSuccess";
}

@GetMapping("/registerFail")
public String registerFail(){
return "registerFail";
}
}

​ 通过上面代码可以看出,通过get请求类型,请求路径为/register,通过返回逻辑视图名,DispatchServlet将逻辑视图名去查找视图,并渲染返回,就可以进入注册表单页面。显而易见,前端提交的参数会自动填写到User对象中,这就是SpringMVC的强大之处,可以将数据自动与实体进行映射。

注册成功之后就会跳到注册成功的页面:

1
2
3
4
5
6
7
8
9
10
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Blog</title>
</head>
<body>
<h1>注册成功~</h1>
</body>
</html>

如下:

但是此时我们可以尝试,在页面不填写任何数据,同样是可以提交成功的,这明显不符合我们的业务要求。所以这里需要进行参数校验。

4. 参数校验

首先,我们需要导入一个Jar包

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Alpha3</version>
</dependency>

其次我们需要修改我们的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User implements Serializable {
@NotNull
@Size(min = 4,max = 20)
private String username;

@NotNull
@Size(min = 6,max = 32)
private String password;

@NotNull
@Min(1)
@Max(150)
private Integer age;

//all/noArgConstructor, getter and setter, toString
}

并且需要在Controller的方式接受参数的地方加上@Valid注解,如下:

1
2
3
4
5
6
7
8
9
/*处理表单数据,并验证*/
@PostMapping("/register")
public String register(@Valid User user, Errors errors){
if (errors.hasErrors()){
return "redirect:/registerFail";
}
userService.saveUser(user);
return "redirect:/registerSuccess";
}

​ 可以看出这里有个Errors参数,这个参数必须紧跟在@Valid注解之后,并且可以通过它判断参数填写是否正确。因为即使加了@Valid,也无法阻止方法的执行,所以需要手动判断处理。

我们增加一个注册失败的提示页面。

registerFail:

1
2
3
4
5
6
7
8
9
10
<%@ taglib prefix="c" uri="http://www.springframework.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Blog</title>
</head>
<body>
<h1>注册失败,请检查数据是否填写正确~</h1>
</body>
</html>

如未按要求填写数据就提交:

4.1 参数检验注解

注解 描述
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString的值
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString的值
@Digists 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String,集合或数组,并且它的长度要符合给定的范围

5. 本地中的POM 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.ooyhao.spring</groupId>
<artifactId>spring-in-action-05-02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<name>spring-in-action-05-02 Maven Webapp</name>
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<!-- @ResponseBody自动转JSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.5</version>
</dependency>


<!-- servlet参数 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>


<!-- 参数校验 -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Alpha3</version>
</dependency>

<!--jsp中使用jstl标签-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

<build>
<finalName>spring-in-action-05-02</finalName>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

总结:

​ 这一节主要是将web.xml构建web项目和WebApplicationInitializer构建项目对比,手动搭建一个无xml文件的web项目。同时也揭开了SpringBoot无配置文件启动的神秘面纱。并且连接了三种参数输入的方式。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×