抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

摘要:本文学习了Spring MVC的配置方式。

环境

Windows 10 企业版 LTSC 21H2
Java 1.8
Tomcat 8.5.50
Maven 3.6.3
Spring 5.3.31

1 XML配置

现在很少使用XML配置,一般都是使用半注解配置或全注解配置。

1.1 基本配置

创建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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="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>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定配置文件的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 在应用启动时立即加载 -->
<load-on-startup>1</load-on-startup>
</servlet>

<!-- 配置DispatcherServlet映射URL -->
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 解决POST请求中文乱码问题 -->
<filter>
<filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

创建spring-mvc.xml配置文件:

spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置控制器 -->
<bean id="demoController" class="com.example.controller.DemoController"/>

<!-- 配置控制器映射 -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/demo">demoController</prop>
</props>
</property>
</bean>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
</beans>

创建前端控制器,实现控制器接口:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
public class DemoController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 创建模型视图对象
ModelAndView mav = new ModelAndView();
// 设置视图名称
mav.setViewName("demo");
// 添加模型数据
mav.addObject("message", "欢迎");
// 返回模型视图对象
return mav;
}
}

创建首页,在webapp目录下创建index.jsp页面:

index.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div>
<a href="${pageContext.request.contextPath}/demo">请求接口</a>
</div>
</body>
</html>

创建视图,在views目录下创建demo.jsp页面:

demo.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>信息</title>
</head>
<body>
<div>
<p><strong>查看消息:</strong>${message}</p>
</div>
<div>
<a href="${pageContext.request.contextPath}/">返回首页</a>
</div>
</body>
</html>

1.2 配置映射

示例:

xml
1
2
3
4
5
6
7
8
9
10
11
<!-- 配置控制器 -->
<bean id="demoController" class="com.example.controller.DemoController"/>

<!-- 配置控制器映射 -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/demo">demoController</prop>
</props>
</property>
</bean>

1.3 静态资源

spring-mvc.xml配置文件中使用mvc:resources标签配置静态资源映射,自动创建SimpleUrlHandlerMapping控制器映射,需要添加mvc命名空间。

常用属性:

属性名 作用 取值
mapping URL映射路径 路径字符串,支持通配符
location 资源存放路径 路径字符串,支持绝对路径和相对路径:
  • Web应用根目录:/static/
  • ClassPath目录:classpath:/META-INF/resources/

示例:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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: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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置控制器,自动将/开头的名称注册为URL映射,无需手动配置控制器映射 -->
<bean name="/demo" id="demoController" class="com.example.controller.DemoController"/>

<!-- 配置静态资源映射 -->
<mvc:resources mapping="/static/**" location="/static/"/>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
</beans>

2 半注解配置

从Spring的2.5版本开始,引入了基于注解的配置方式。

2.1 基本配置

修改spring-mvc.xml配置文件:

spring-mvc.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.example.controller"/>

<!-- 启用注解驱动 -->
<mvc:annotation-driven/>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
</beans>

修改前端控制器,使用注解代替配置:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class DemoController {
@RequestMapping(value = "/demo")
public ModelAndView demo() {
// 创建模型视图对象
ModelAndView mav = new ModelAndView();
// 设置视图名称
mav.setViewName("demo");
// 添加模型数据
mav.addObject("message", "欢迎");
// 返回模型视图对象
return mav;
}
}

2.2 配置映射

示例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class DemoController {
@RequestMapping(value = "/demo")
public ModelAndView demo() {
// 创建模型视图对象
ModelAndView mav = new ModelAndView();
// 设置视图名称
mav.setViewName("demo");
// 添加模型数据
mav.addObject("message", "欢迎");
// 返回模型视图对象
return mav;
}
}

2.3 注解驱动

spring-mvc.xml配置文件中使用mvc:annotation-driven标签启用注解驱动,自动创建RequestMappingHandlerMapping控制器映射。

启用注解驱动后,可以使用以下注解:

  • 使用@RequestMapping注解配置路径和控制器方法的映射。
  • 使用@RequestParam注解和@PathVariable注解获取请求参数。
  • 使用@RequestBody注解和@ResponseBody注解处理JSON数据。
  • 使用@ModelAttribute注解处理模型数据。

常用属性:

属性名 作用 取值
conversion-service 引用自定义的类型转换服务 类型转换服务的引用
validator 引用自定义的校验器 校验器的引用

示例:

xml
1
2
<!-- 启用注解驱动 -->
<mvc:annotation-driven/>

3 全注解配置

从Spring的3.1版本开始,引入了全注解配置方式:

  • 使用@EnableWebMvc注解替代mvc:annotation-driven标签启用注解驱动。需要Servlet的2.5以上的版本支持。
  • 使用WebApplicationInitializer接口替代web.xml配置文件。需要Servlet的3.0以上的版本支持。

3.1 基本配置

创建Web初始化类,代替web.xml配置文件,需要实现WebApplicationInitializer接口:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WebInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 创建AnnotationConfigWebApplicationContext容器,支持在Web环境中使用注解的方式获取配置
AnnotationConfigWebApplicationContext webContext = new AnnotationConfigWebApplicationContext();
// 注册Web配置类,比如Controller控制器类
webContext.register(WebConfig.class);

// 配置前端控制器
DispatcherServlet dispatcherServlet = new DispatcherServlet(webContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", dispatcherServlet);
// 配置DispatcherServlet映射URL
registration.addMapping("/");
// 在应用启动时立即加载
registration.setLoadOnStartup(1);

// 注册字符编码过滤器
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", CharacterEncodingFilter.class);
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
}

创建Web配置类,代替spring-mvc.xml配置文件,需要实现WebMvcConfigurer接口:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置视图解析器
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setContentType("text/html;charset=UTF-8");
return resolver;
}
}

3.2 配置说明

3.2.1 Web初始化类

从Servlet的3.0版本开始,JavaEE规范提供了一套动态注册机制,让Spring框架可以使用WebApplicationInitializer接口代替web.xml配置文件,以编程的方式配置组件。

Servlet规范提供了SPI容器启动扩展机制,允许框架在Servlet容器启动时动态注册组件,提供ServletContainerInitializer接口作为标准扩展点,在Servlet容器启动时发现所有实现类,并调用实现类的onStartup()方法。

Spring基于Servlet规范,通过SpringServletContainerInitializer类实现了ServletContainerInitializer接口,其作用是:

  • 使用Servlet规范提供的@HandlesTypes注解指定WebApplicationInitializer接口,让Servlet容器在启动时发现所有实现了WebApplicationInitializer接口的类。
  • 将实现了WebApplicationInitializer接口的类作为参数,传入SpringServletContainerInitializer类的onStartup()方法中。
  • 在SpringServletContainerInitializer类的onStartup()方法中调用WebApplicationInitializer接口的onStartup()方法,从而实现了WebApplicationInitializer接口的功能。
  • 实现WebApplicationInitializer接口并重写onStartup()方法,代替web.xml配置文件创建DispatcherServlet等组件。

为了进一步简化开发,Spring还提供了实现了WebApplicationInitializer接口的AbstractAnnotationConfigDispatcherServletInitializer抽象类。

示例:

java
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
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 指定根配置类,在核心容器中创建,具有唯一性
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
// 指定Web配置类,在MVC容器中创建,可以有多个
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConfig.class};
}
// 指定映射路径
@Override
protected String[] getServletMappings() {
// 设置DispatcherServlet的映射路径
return new String[] {"/"};
}
// 指定过滤器
@Override
protected Filter[] getServletFilters() {
// 注册字符编码过滤器
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
return new Filter[] {encodingFilter};
}
}

3.2.2 根配置类

根配置类用于创建核心容器,需要使用注解:

  • 使用@Configuration注解标记为配置类。
  • 使用@ComponentScan注解扫描组件类,注册组件到容器。

3.2.3 Web配置类

Web配置类用于创建MVC容器,除了需要实现WebMvcConfigurer接口,还需要使用注解:

  • 使用@Configuration注解标记为配置类。
  • 使用@ComponentScan注解扫描控制器类,注册控制器到容器。
  • 使用@EnableWebMvc注解启用注解驱动,可以通过注解启用核心功能。

WebMvcConfigurer接口是Web配置的核心接口,提供了一系列可重写的方法:

  • 配置视图解析器:通过重写configureViewResolvers()方法配置。
  • 配置静态资源映射:通过重写addResourceHandlers()方法配置。
  • 配置拦截器:通过重写addInterceptors()方法配置。
  • 配置跨域映射:通过重写addCorsMappings()方法配置。

3.2.4 全局配置类

使用@ControllerAdvice注解配合其他注解可以实现不同的功能:

  • 在配置类中使用@ModelAttribute注解定义全局模型对象。
  • 在配置类中使用@InitBinder注解定义全局数据绑定规则。
  • 在配置类中使用@ExceptionHandler注解定义全局异常处理器。

3.3 静态资源

在Web配置类中配置静态资源映射:

java
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置静态资源映射
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
// ...
}

3.4 内容协商

3.4.1 解决乱码

在Web配置类中配置消息转换器,防止中文乱码:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置消息转换器,设置编码格式,处理返回String类型的响应
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 遍历消息转换器列表
for (int i = 0; i < converters.size(); i++) {
// 修改原始StringHttpMessageConverter转换器
if (converters.get(i) instanceof StringHttpMessageConverter) {
// 设置UTF-8编码
StringHttpMessageConverter custom = new StringHttpMessageConverter(StandardCharsets.UTF_8);
// 设置为false可以避免响应头过大
custom.setWriteAcceptCharset(false);
// 替换原始StringHttpMessageConverter转换器
converters.set(i, custom);
break;
}
}
}
// ...
}

3.4.2 转换对象

自定义消息转换器,处理自定义对象的转换:

java
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
public class DemoMessageConverter implements HttpMessageConverter {
@Override
public boolean canRead(Class clazz, MediaType mediaType) {
return User.class.isAssignableFrom(clazz);
}
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return User.class.isAssignableFrom(clazz);
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(new MediaType("application", "x-demo"));
}
@Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
Map<String, String> param = Arrays.stream(body.split(";"))
.map(data -> data.split(":"))
.collect(Collectors.toMap(data -> data[0], data -> data[1]));
User user = new User();
user.setId(Integer.parseInt(param.get("id")));
user.setName(param.get("name"));
return user;
}
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
User user = (User) o;
String body = String.format("id:%d;name:%s", user.getId(), user.getName());
StreamUtils.copy(body.getBytes(StandardCharsets.UTF_8), outputMessage.getBody());
}
}

在Web配置类中配置自定义消息转换器:

java
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new DemoMessageConverter());
}
// ...
}

在控制器中处理请求:

java
1
2
3
4
5
6
7
8
@Controller
public class DemoController {
@PostMapping("/demo")
@ResponseBody
public User demo(@RequestBody User user) {
return user;
}
}

请求体为Text格式:

text
1
id:1;name:张三

使用Postman发送POST请求,请求体为Text格式:

  • 如果请求头Accept为application/x-demo类型,返回自定义字符串。
  • 如果请求头Accept为application/json类型,返回JSON字符串。
  • 如果请求头Accept为application/xml类型,返回XML字符串。

3.4.3 路径参数

在处理内容协商时,根据getAcceptableMediaTypes()方法获取请求头Accept的媒体类型,可以通过配置改为根据路径参数指定媒体类型。

在Web配置类中配置内容协商策略:

java
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
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
// 配置内容协商策略
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 注册自定义媒体类型
MediaType APPLICATION_X_DEMO = new MediaType("application", "x-demo");
// 配置支持的媒体类型
Map<String, MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("xml", MediaType.APPLICATION_XML);
mediaTypes.put("x-demo", APPLICATION_X_DEMO);
// 开启路径参数
configurer.favorParameter(true);
// 配置路径参数名,默认将format作为参数名
configurer.parameterName("content");
// 配置默认媒体类型
configurer.defaultContentType(APPLICATION_X_DEMO);
// 配置支持的媒体类型
configurer.mediaTypes(mediaTypes);
}
// ...
}

使用Postman发送POST请求,请求体为Text格式,以路径参数为主,当路径参数不存在时使用Accept请求头:

  • 如果请求路径为/demo?content=x-demo,返回自定义字符串。
  • 如果请求路径为/demo?content=json,返回JSON字符串。
  • 如果请求路径为/demo?content=xml,返回XML字符串。

评论