hollins

告别xml——基于Java的Spring MVC配置

Spring为我们提供了三种配置方式:组件扫描、基于Java的配置、基于xml的配置。

从Spring 3.0版本开始,AnnotationConfigApplicationContext 和 AnnotationConfigWebApplicationContext的出现,大大简化了spring的配置过程。在此之前,spring世界一直笼罩在xml配置的阴影之下,整个配置过程极其繁琐,而且没法保证类型安全。下面以一个简单的demo为例,叙述如何基于java完成spring的配置。

maven配置文件

maven配置文件中仅仅导入了本demo需要的jar包,诸如jdbc、hibernate等无关的包皆已删去。配置如下:

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
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.hollins</groupId>
<artifactId>weblab</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>weblab Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<java-version>1.8</java-version>
<!-- Web -->
<jsp.version>2.2</jsp.version>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<!-- Spring -->
<spring-framework.version>4.2.0.RELEASE</spring-framework.version>
<!-- logging -->
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- java EE component -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
</dependencies>
<build>
<finalName>weblab</finalName>
</build>
</project>

RootConfig.java

此处的RootConfig.java等价于原来Spring配置中的application-config.xml。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.hollins.weblab.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* 非Web组件的配置
* @author hollins
*
*/
@ComponentScan(basePackages="org.hollins.weblab",
excludeFilters={
@Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class),
@Filter(type=FilterType.ANNOTATION, value=Controller.class)
})
@Configuration
public class RootConfig{
}

@Configuration注解表明这个类是一个Bean的配置类。

@ComponentScan注解表示启用组件扫描,等同于xml文件中的

1
<context:component-scan base-package="org.hollins.weblab" />

excludeFilters属性使得spring在组件扫描时排除指定的类。这里之所以要排除带有@EnableMVC注解和@Controller注解的类,是因为

  1. 带有@EnableMVC注解的类会通过钦点的方式加载
  2. 而带有@Controller注解的类会在另一个文件中进行加载

WebConfig.java

此处的WebConfig.java等同于原来Spring配置中的servlet-context.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
package org.hollins.weblab.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.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
/**
* web组件相关配置
* @author hollins
*
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages="org.hollins.weblab.controller")
public class WebConfig
extends WebMvcConfigurerAdapter{
/**
* 配置JSP视图解析器
* @return
*/
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
/**
* 配置静态资源的处理
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
registry.addResourceHandler("js/**").addResourceLocations("/js/");
registry.addResourceHandler("css/**").addResourceLocations("/css/");
registry.addResourceHandler("img/**").addResourceLocations("/img/");
registry.addResourceHandler("images/**").addResourceLocations("/images/");
registry.addResourceHandler("fonts/**").addResourceLocations("/fonts/");
registry.addResourceHandler("plugins/**").addResourceLocations("/plugins/");
}
}

@Configuration和@ComponentScan的作用同上,差别在于这里仅仅扫描controller包下的类

@EnableMVC注解旨在启用注解驱动的Spring MVC,等同于xml文件中的

1
<mvc:annotation-driven />

视图解析器的配置主要设置了视图的前缀和后缀,这样一来,在controller中返回jsp的文件名即可。

而对静态资源处理器的配置是为了防止Spring MVC拦截所有的请求,使得静态文件诸如CSS、JS、图片得以顺利访问。

WeblabWebAppInitializer.java

那么问题来了,我们还缺一个web.xml。按照传统的方式,web.xml中要配置ContextLoaderListener,DispatcherServlet,CharacterEncodingFilter等。但是,借助于Servlet 3 规范和Spring 3.1的功能增强,这种方式已经逐渐被淘汰了。代码如下:

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
package org.hollins.weblab.config;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 应用初始化器
* @author hollins
*
*/
public class WeblabWebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer{
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("characterEncodingFilter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
}

在这个类中分别钦点了RootConfig和WebConfig作为配置类。在onStartup方法中又增加了encodingFilter。

然而,这个类并没有任何注解,显然不是由Spring发现并启用的。那么凭什么它可以取代web.xml呢?

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

Craig WallsSpring实战(第4版)

创建jsp文件

在src/main/webapp/WEB-INF/views 文件夹中创建一个jsp文件,作为示例应用的显示界面。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+ request.getServerPort()+path+"/";
%>
<html>
<head>
<base href="<%=basePath %>" />
<title>Weblab</title>
</head>
<body>
<h1>Weblab</h1>
<p>Weblab served at: <%=basePath %></p>
</body>
</html>

创建控制器

本例只有显示页面的功能,并不涉及数据库的增删改查,因此,控制器类的代码也相当简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.hollins.weblab.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class DispatcherController {
@RequestMapping(value="/", method=RequestMethod.GET)
public String index() {
return "index";
}
}

测试

部署项目,访问http://localhost:8080/weblab/ 即可看到效果。

总结

我们尽可能使用自动化配置,以避免显式配置所带来的的维护成本。但是,如果确实需要显式配置Spring的话,应该优先选择基于Java的配置,它比基于XML的配置更加强大、类型安全且易于重构。