作者:实验室2013级hollins

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

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

1. maven配置文件

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

<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>

2. RootConfig.java

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

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文件中的

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

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

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

3. WebConfig.java

此处的WebConfig.java等同于原来Spring配置中的servlet-context.xml。

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文件中的

<mvc:annotation-driven />

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

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

4. WeblabWebAppInitializer.java

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

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上下文

——《Spring实战(第4版)》

5. 创建jsp文件

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

<%@ 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>

6. 创建控制器

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

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";
	}
}

7. 测试

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

8. 总结

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

项目代码下载: weblab.zip (下载544)

原文地址:hollins的博客 告别xml——基于Java的Spring MVC配置