Skip to content

Spring MVC Servlet 配置详解 ⚙️

引言:告别 web.xml 的时代 👋

在传统的 Java Web 开发中,我们总是需要在 web.xml 文件中配置各种 Servlet、Filter 和监听器。这种 XML 配置方式虽然功能强大,但也带来了一些问题:

  • 配置繁琐:需要编写大量的 XML 配置代码
  • 类型不安全:XML 配置在编译时无法检查错误
  • 维护困难:配置分散在多个文件中,难以统一管理

Spring MVC 为我们提供了一种更现代、更优雅的解决方案:编程式 Servlet 配置

TIP

编程式配置让我们可以用 Java/Kotlin 代码来替代传统的 XML 配置,享受类型安全、IDE 智能提示等现代开发体验。

核心概念:WebApplicationInitializer 接口 💡

什么是 WebApplicationInitializer?

WebApplicationInitializer 是 Spring MVC 提供的一个核心接口,它的作用是:

  • 自动发现:Spring 会自动检测实现了此接口的类
  • 容器初始化:在 Servlet 3.0+ 容器启动时自动执行初始化逻辑
  • 编程式配置:允许我们用代码来配置 DispatcherServlet

基础配置:手动注册 DispatcherServlet 🛠️

最原始的配置方式

让我们先看看最基础的配置方式:

kotlin
import org.springframework.web.WebApplicationInitializer
import org.springframework.web.context.support.XmlWebApplicationContext
import org.springframework.web.servlet.DispatcherServlet
import javax.servlet.ServletContext

class MyWebApplicationInitializer : WebApplicationInitializer {
    
    override fun onStartup(container: ServletContext) {
        // 创建Spring应用上下文
        val appContext = XmlWebApplicationContext()
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
        
        // 注册DispatcherServlet
        val registration = container.addServlet("dispatcher", DispatcherServlet(appContext))
        registration.setLoadOnStartup(1) // 容器启动时立即加载
        registration.addMapping("/") // 处理所有请求
    }
}
xml
<!-- /WEB-INF/spring/dispatcher-config.xml -->
<?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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="...">

    <mvc:annotation-driven />
    <context:component-scan base-package="com.example.controller" />
    
</beans>

NOTE

这种方式虽然灵活,但需要我们手动处理很多细节。Spring 提供了更简便的抽象类来简化这个过程。

进阶配置:使用抽象基类 🚀

基于注解的配置(推荐)

对于现代 Spring Boot 应用,我们更推荐使用基于 Java 配置的方式:

kotlin
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer

class MyWebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    
    // 根应用上下文配置类(通常用于服务层、数据层等)
    override fun getRootConfigClasses(): Array<Class<*>>? {
        return null // 在简单应用中可以返回null
    }
    
    // Servlet应用上下文配置类(用于Web层)
    override fun getServletConfigClasses(): Array<Class<*>>? {
        return arrayOf(MyWebConfig::class.java) 
    }
    
    // Servlet映射路径
    override fun getServletMappings(): Array<String> {
        return arrayOf("/") // 处理所有请求
    }
}

对应的配置类:

kotlin
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
@EnableWebMvc // 启用Spring MVC
@ComponentScan("com.example.controller") // 扫描控制器
class MyWebConfig : WebMvcConfigurer {
    
    // 可以在这里添加更多的Web配置
    // 比如视图解析器、拦截器、消息转换器等
}

基于 XML 的配置

如果你的项目仍然使用 XML 配置,可以这样做:

XML配置方式示例
kotlin
import org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.XmlWebApplicationContext

class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
    
    // 创建根应用上下文
    override fun createRootApplicationContext(): WebApplicationContext? {
        return null
    }
    
    // 创建Servlet应用上下文
    override fun createServletApplicationContext(): WebApplicationContext {
        return XmlWebApplicationContext().apply {
            setConfigLocation("/WEB-INF/spring/dispatcher-config.xml")
        }
    }
    
    // 配置Servlet映射
    override fun getServletMappings(): Array<String> {
        return arrayOf("/")
    }
}

高级特性:Filter 配置 🛡️

自动配置过滤器

Spring MVC 还允许我们轻松地添加过滤器:

kotlin
import org.springframework.web.filter.CharacterEncodingFilter
import org.springframework.web.filter.HiddenHttpMethodFilter
import javax.servlet.Filter

class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
    
    // ... 其他配置方法 ...
    
    // 配置过滤器
    override fun getServletFilters(): Array<Filter> {
        return arrayOf(
            HiddenHttpMethodFilter(),     // 支持PUT、DELETE等HTTP方法
            CharacterEncodingFilter().apply { // 字符编码过滤器
                setEncoding("UTF-8")
                setForceEncoding(true)
            }
        )
    }
}

过滤器的工作原理

IMPORTANT

过滤器会按照数组中的顺序执行,请求时正序执行,响应时逆序执行。

异步支持配置 ⚡

启用异步处理

现代 Web 应用经常需要处理异步请求,Spring MVC 默认启用了异步支持:

kotlin
class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
    
    // ... 其他配置 ...
    
    // 异步支持(默认为true)
    override fun isAsyncSupported(): Boolean {
        return true
    }
}

异步处理的应用场景

异步处理的优势

  • 提高吞吐量:释放线程资源,处理更多并发请求
  • 长时间操作:适合文件上传、数据导出等耗时操作
  • 实时通信:支持 Server-Sent Events、WebSocket 等技术

自定义 DispatcherServlet 🔧

高级定制

如果需要对 DispatcherServlet 进行更深层次的定制:

kotlin
import org.springframework.web.servlet.DispatcherServlet

class MyWebAppInitializer : AbstractDispatcherServletInitializer() {
    
    // ... 其他配置 ...
    
    // 自定义DispatcherServlet
    override fun createDispatcherServlet(servletAppContext: WebApplicationContext): DispatcherServlet {
        return object : DispatcherServlet(servletAppContext) {
            override fun onRefresh(context: ApplicationContext) {
                super.onRefresh(context)
                // 在这里可以添加自定义的初始化逻辑
                println("DispatcherServlet 初始化完成!")
            }
        }
    }
}

实际应用示例 💻

完整的 Spring Boot 风格配置

让我们看一个更贴近实际项目的完整示例:

kotlin
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer

class WebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    
    override fun getRootConfigClasses(): Array<Class<*>>? {
        return arrayOf(RootConfig::class.java) // 根配置
    }
    
    override fun getServletConfigClasses(): Array<Class<*>>? {
        return arrayOf(WebConfig::class.java) // Web配置
    }
    
    override fun getServletMappings(): Array<String> {
        return arrayOf("/api/*") // 只处理API请求
    }
    
    override fun getServletFilters(): Array<Filter> {
        return arrayOf(
            CharacterEncodingFilter().apply {
                setEncoding("UTF-8")
                setForceEncoding(true)
            },
            CorsFilter() // 跨域支持
        )
    }
}
kotlin
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
class WebConfig : WebMvcConfigurer {
    
    // 配置消息转换器
    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        converters.add(MappingJackson2HttpMessageConverter())
    }
    
    // 配置跨域
    override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/api/**")
            .allowedOrigins("*")
            .allowedMethods("GET", "POST", "PUT", "DELETE")
    }
}
kotlin
@Configuration
@ComponentScan(
    basePackages = ["com.example.service", "com.example.repository"],
    excludeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, classes = [Controller::class])]
)
class RootConfig {
    // 服务层和数据层的配置
}

配置方式对比 ⚖️

传统 vs 现代配置

xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="3.0">
    
    <!-- 字符编码过滤器 -->
    <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>
    </filter>
    
    <!-- DispatcherServlet配置 -->
    <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>
    
</web-app>
kotlin
class WebAppInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    
    override fun getRootConfigClasses() = null
    
    override fun getServletConfigClasses() = arrayOf(WebConfig::class.java)
    
    override fun getServletMappings() = arrayOf("/")
    
    override fun getServletFilters() = arrayOf(
        CharacterEncodingFilter().apply {
            setEncoding("UTF-8")
            setForceEncoding(true)
        }
    )
}

NOTE

编程式配置的优势:

  • 类型安全:编译时检查错误
  • IDE 支持:智能提示和重构
  • 代码复用:可以继承和组合
  • 动态配置:可以根据环境动态调整

最佳实践建议 ⭐

1. 选择合适的配置方式

kotlin
// 推荐:对于新项目使用注解配置
class ModernWebInitializer : AbstractAnnotationConfigDispatcherServletInitializer() {
    // 基于Java配置类
}

// 兼容:对于遗留项目可以混合使用
class LegacyWebInitializer : AbstractDispatcherServletInitializer() {
    // 基于XML配置文件
}

2. 合理组织配置类

kotlin
// 根配置:业务逻辑相关
@Configuration
@ComponentScan(basePackages = ["com.example.service", "com.example.repository"])
class RootConfig

// Web配置:Web层相关
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = ["com.example.controller"])
class WebConfig : WebMvcConfigurer

3. 过滤器顺序很重要

kotlin
override fun getServletFilters(): Array<Filter> {
    return arrayOf(
        CharacterEncodingFilter(),  // 1. 首先设置字符编码
        CorsFilter(),              // 2. 然后处理跨域
        HiddenHttpMethodFilter(),  // 3. 最后处理HTTP方法
    )
}

WARNING

过滤器的执行顺序会影响应用的行为,请确保按照正确的顺序配置。

总结 🎉

Spring MVC 的编程式 Servlet 配置为我们提供了一种现代化、类型安全的配置方式。通过 WebApplicationInitializer 接口和相关的抽象类,我们可以:

  • 告别 XML:用 Java/Kotlin 代码替代繁琐的 XML 配置
  • 类型安全:享受编译时检查和 IDE 智能提示
  • 灵活配置:轻松添加过滤器、自定义 Servlet 等
  • 现代化开发:与 Spring Boot 等现代框架完美集成

选择合适的配置方式,让你的 Spring MVC 应用更加优雅和易于维护! ✨