Spring ServletContextPropertySource 源码解析:ServletContext 初始化参数如何融入 Spring 环境属性体系

发布时间:2026/9/13 16:35:25
Spring ServletContextPropertySource 源码解析:ServletContext 初始化参数如何融入 Spring 环境属性体系 Spring ServletContextPropertySource 源码解析ServletContext 初始化参数如何融入 Spring 环境属性体系【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter导读本文以org.springframework.web.context.support.ServletContextPropertySource为核心剖析 Spring 如何把 Servlet 容器的ServletContext初始化参数context-param包装成PropertySource从而纳入统一的 Spring 环境属性解析体系。通过本文你将掌握ServletContextPropertySource的完整实现、它与EnumerablePropertySource/PropertySource的继承关系、StandardServletEnvironment在 Web 容器启动时注册它的时机以及它与ServletConfigPropertySource的区别和实际应用场景。一、类定位与全路径ServletContextPropertySource位于 Spring Web 模块的环境支持包中类全路径org.springframework.web.context.support.ServletContextPropertySource所属模块spring-weborg.springframework.web.context.support包内部数据结构javax.servlet.ServletContext接口继承关系ServletContextPropertySource extends EnumerablePropertySourceServletContext这个类本质上是「适配器」它把 Servlet 规范中的ServletContextWeb 应用的全局上下文对象暴露的初始化参数适配成 SpringPropertySource统一接口使ServletContext中通过context-param声明的全局配置可以被 Spring 环境抽象Environment/PropertyResolver以统一方式读取。二、核心源码解析该类整体代码非常精简只有构造器、getPropertyNames()和getProperty(String)两个方法public class ServletContextPropertySource extends EnumerablePropertySourceServletContext { public ServletContextPropertySource(String name, ServletContext servletContext) { super(name, servletContext); } Override public String[] getPropertyNames() { // javax.servlet.ServletContext.getInitParameterNames 方法调用 return StringUtils.toStringArray(this.source.getInitParameterNames()); } Override Nullable public String getProperty(String name) { // javax.servlet.ServletContext.getInitParameter return this.source.getInitParameter(name); } }2.1 构造器包装 ServletContext构造器接收两个参数参数类型说明nameStringPropertySource 的名称用于在MutablePropertySources集合中唯一标识与排序servletContextServletContextServlet 规范定义的 Web 应用全局上下文对象作为PropertySource的底层数据源source构造器直接调用父类EnumerablePropertySource(String, T)再向上调用PropertySource(String, T)。父类构造器有两条断言Assert.hasText(name, Property source name must contain at least one character); Assert.notNull(source, Property source must not be null);即name 不允许为空字符串、source 不允许为 null。这一点保证了每个ServletContextPropertySource必然持有一个真实可用的ServletContext引用。2.2 getPropertyNames()枚举所有 context-param 名称Override public String[] getPropertyNames() { // javax.servlet.ServletContext.getInitParameterNames 方法调用 return StringUtils.toStringArray(this.source.getInitParameterNames()); }该方法把 Servlet 容器提供的ServletContext.getInitParameterNames()返回EnumerationString通过StringUtils.toStringArray转换成字符串数组。getInitParameterNames返回的是部署描述文件web.xml中所有context-param的param-name集合因此这里的String[]就是全部全局初始化参数名称。2.3 getProperty(String)按名称读取参数值Override Nullable public String getProperty(String name) { // javax.servlet.ServletContext.getInitParameter return this.source.getInitParameter(name); }直接委托给ServletContext.getInitParameter(name)返回与name对应的context-param参数值若不存在则返回null。注意Nullable注解表明返回值允许为空这也是PropertySource#getProperty抽象方法约定的语义未找到的属性返回null而非抛异常。2.4 一个未重写的关键方法containsPropertyServletContextPropertySource没有重写containsProperty(String)它从EnumerablePropertySource继承而来。EnumerablePropertySource位于org.springframework.core.env包其实现是Override public boolean containsProperty(String name) { return ObjectUtils.containsElement(getPropertyNames(), name); }也就是说判断「是否包含某属性」的方式是先枚举全部属性名数组再判断数组中是否包含目标 name。这是一个线性查找的通用实现正确性依赖于子类正确实现getPropertyNames()。ServletContextPropertySource正是通过getInitParameterNames()保证了这一语义。三、继承链与类族背景ServletContextPropertySource的继承链为PropertySourceServletContext (org.springframework.core.env) └── EnumerablePropertySourceServletContext (org.springframework.core.env) └── ServletContextPropertySource (org.springframework.web.context.support)3.1 PropertySource属性源抽象基类PropertySourceT是 Spring 环境抽象的核心抽象类持有两个字段name属性源名称与source底层数据源对象并定义了抽象方法getProperty(String name)。其containsProperty默认实现是getProperty(name) ! null。由于ServletContext的getInitParameter本身可能返回空串等有值结果EnumerablePropertySource覆写的枚举式containsProperty语义更精确。3.2 EnumerablePropertySource可枚举属性源EnumerablePropertySourceT在PropertySource之上追加了抽象方法getPropertyNames()用于列出所有属性名。凡是继承它的属性源都可以被遍历例如被CompositePropertySource.getPropertyNames()聚合、被ConfigurationProperties绑定、被PropertySourcesPropertyResolver完整枚举。3.3 同族实现对比在org.springframework.core.env/org.springframework.web.context.support包中EnumerablePropertySource的典型子类包括类底层数据结构getPropertyNames 实现MapPropertySourceMapString, ObjectStringUtils.toStringArray(source.keySet())PropertiesPropertySourcePropertiesMap 子类复用MapPropertySource.keySet()并加synchronized保护CompositePropertySourceSetPropertySource?聚合子属性源名称要求子源均可枚举否则抛IllegalStateExceptionServletContextPropertySourceServletContextStringUtils.toStringArray(source.getInitParameterNames())ServletConfigPropertySourceServletConfigStringUtils.toStringArray(source.getInitParameterNames())其中ServletConfigPropertySourceorg.springframework.web.context.support.ServletConfigPropertySource与本文主角结构几乎完全一致但包装的是ServletConfig单个 Servlet 的配置对象枚举的是ServletConfig.getInitParameterNames()即servlet节点内的init-param而ServletContextPropertySource对应的是全局context-param。二者一个面向单个 Servlet 局部配置一个面向Web 应用全局配置共同覆盖了 web.xml 中两类初始化参数的属性化读取。四、注册时机StandardServletEnvironment 与 initPropertySourcesServletContextPropertySource并非凭空存在它由 Spring 的StandardServletEnvironment在 Web 容器启动阶段创建并注册进属性源集合。StandardServletEnvironmentorg.springframework.web.context.support.StandardServletEnvironment继承StandardEnvironment在其customizePropertySources(MutablePropertySources)方法中会按优先级从高到低加入三类属性源ServletContextPropertySource名称servletContextInitParams包装全局context-paramServletConfigPropertySource名称servletConfigInitParams包装init-paramJNDI 属性源jndiProperties与系统属性/环境变量属性源。注意这里使用了StubPropertySource占位技巧在ServletContext尚不可用的阶段例如容器刷新早期Spring 先以StubPropertySource占据servletContextInitParams这个名称位置保证属性源的顺序稳定待WebApplicationContext刷新时通过initPropertySources(servletContext, servletConfig)用真正的ServletContextPropertySource替换占位符。这一点在PropertySource源码的StubPropertySource类注释中有明确说明。4.1 调用链佐证仓库中的 SpringMVC 设计与实现文档 记录了刷新上下文时对环境的初始化调用// 在刷新上下文的任何情况下都将会调用此 wac 的 env 的 initPropertySources() 方法 ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());这印证了ServletContextPropertySource的装载发生在WebApplicationContext.refresh()阶段——此时ServletContext已经由 Web 容器创建完毕Spring 才能将全局context-param包装为属性源并完成占位符替换。五、实战web.xml 中的 context-param 如何被读到以 Tomcat 为 Web 容器web.xml中典型配置如下参见 IoC 容器在 Web 环境中的启动文档servlet servlet-namesample/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class load-on-startup6/load-on-startup /servlet servlet-mapping servlet-namesample/servlet-name url-pattern/*/url-pattern /servlet-mapping context-param param-namecontextConfigLocation/param-name param-value/WEB-INF/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener这里的context-param声明了全局初始化参数contextConfigLocation/WEB-INF/applicationContext.xml。当ContextLoaderListener与DispatcherServlet启动、StandardServletEnvironment.initPropertySources执行后这个键值对被包装进ServletContextPropertySource。此后在代码中即可通过统一环境接口读取// 通过 ApplicationContext 的环境读取全局 context-param String location applicationContext.getEnvironment() .getProperty(contextConfigLocation); // 结果为 /WEB-INF/applicationContext.xml // 或通过 PropertyResolver 接口读取 String location resolver.getProperty(contextConfigLocation);读取时PropertySourcesPropertyResolver会按MutablePropertySources中属性源的优先级顺序依次调用各PropertySource.getProperty(name)ServletContextPropertySource最终委托给ServletContext.getInitParameter(contextConfigLocation)拿到值。整个过程对调用方完全透明——这正是 Spring 环境抽象的价值所在。5.1 与其他属性源的优先级MutablePropertySources内部使用CopyOnWriteArrayList存储属性源addFirst头插高优先级、addLast尾插低优先级并提供addBefore/addAfter/replace等操作参见 PropertySources 文档。在StandardServletEnvironment中ServletContextPropertySource被置于较高优先级因此相同属性名若同时存在于系统属性和context-param中优先命中ServletContextPropertySource的值这一排序可在应用启动时通过自定义ApplicationContextInitializer或PropertySource进一步调整。六、总结ServletContextPropertySource虽只有二十余行代码却是 Spring Web 环境抽象的关键一环它完成了三件事适配将 Servlet 规范ServletContext的全局初始化参数context-param适配为PropertySource统一接口枚举通过继承EnumerablePropertySource并实现getPropertyNames()让全局参数可被遍历、聚合与绑定接入由StandardServletEnvironment在 Web 容器启动、WebApplicationContext刷新时注册与系统属性、环境变量、ServletConfig参数等共同构成完整的属性解析链。理解它就理解了「Web 容器配置如何流入 Spring 环境抽象」这条主链路也为进一步研究StandardServletEnvironment、ConfigurableWebEnvironment以及Value/ConfigurationProperties在 Web 应用中的取值机制打下基础。参考路径本文主角源码笔记docs/Spring/clazz/PropertySource/Spring-ServletContextPropertySource.md同类实现ServletConfigPropertySourcedocs/Spring/clazz/PropertySource/Spring-ServletConfigPropertySource.md父类EnumerablePropertySourcedocs/Spring/clazz/PropertySource/Spring-EnumerablePropertySource.mdPropertySource基类与StubPropertySource占位机制docs/Spring/clazz/Spring-PropertySources.md属性源集合MutablePropertySources的增删改查与排序docs/Spring/clazz/Spring-PropertySources.mdWeb 容器中 IoC 容器启动与web.xml配置docs/Spring/SpringMVC/IoC容器在Web环境中的启动.mdinitPropertySources调用时机docs/Spring/SpringMVC/SpringMVC的设计与实现.md【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考