Spring XML 配置文件加载机制
by 董唯良 at 2016.5
项目启动不成功?
前些天在进行项目部署时发现启动不成功,看了日志是 applicationContext.xml 配置文件解析 namespace 时失败。 因此对 spring 的 xml 加载机制做一下分享。
我们项目中的配置文件有如下配置:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:kiev="http://www.meizu.com/kiev/schema/service"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.meizu.com/kiev/schema/service
http://www.meizu.com/kiev/schema/service.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd">
从中我们看下这个配置
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
其实我们也可以这样写
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
其中的区别也看到了,就是一个有版本号,一个没有,这其中有什么区别?
xml 文件解释
xml 的 schema 里有 namespace,可以给它起个别名。比如常见的 spring 的 namespace:
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:kiev="http://www.meizu.com/kiev/schema/service"
xsi:schemaLocation 配置的是 namespace 与 xsd 地址的映射,所以 xsi:schemaLocation 的配置都是成对出现。 前面的是 namespace 的 URI,后面的是 xsd 文件的 URI。
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.0.xsd
spring 如何加载 xml
从 xsd 的 URI 可以看出这是一个网络连接地址,需要连接网络才能访问。 但是在断网的情况下我们依然可以正常访问,因为 Spring 会默认先从本地加载 xsd 文件。 打开 spring-context-3.2.0.RELEASE.jar,可以看到里面有两个特别的文件:
spring.handler
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
spring.schema
http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
再看 xsd 文件

很明显,spring 把 xsd 文件都放在了本地,而且以前的版本也在里面。 而没有配置版本号呢?我们也可以看到其实使用了当前的版本:
http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
建议
我们经常对所引用的版本号不太在意,有时候从别的地方拷贝过来就直接使用了,这就有可能带来问题。 比较好的做法就是不写版本号,这样比较安全。
自定义 xsd
其实写一个自己的 xsd 很简单。首先实现自己的 NamespaceHandler,再配置 spring.handler 和 spring.schema 就可以了。 魅族有个自己的项目叫 Kiev,看下这个项目的 jar 包:

引申
之前项目中有出现某个 bean 加载不到,或者事务失效的情况。这些主要是因为 spring 与 spring mvc 容器之间可见性的问题导致的。 Spring 容器作为父容器是看不到 spring mvc 子容器加载的配置的。由于个人喜好问题,父子容器的加载每个项目配置也都不一样。为了省事,减少麻烦,可以只加载子容器而不加载父容器,这样就可以解决可见性问题,避免父容器的配置被子容器覆盖而导致相关问题。
可是有特殊情况,如 Shiro 的配置必须由父容器来进行加载,这样还是不能逃避这个问题。
原因: 子容器加载了父容器的 @Service
,而事务或者一些属性注入是在父容器中进行的,这样子容器加载的 @Service
就覆盖了父容器中已经增强过了的 bean。此时的 bean 是原生态的,因此会导致事务及属性注入的失效。 怎么办: 那就是各做各事,父容器不加载 Controller,子容器只加载 Controller,不多做事情。这里又体现了少做事情的好处了。 具体配置如下:applicationContext.xml
<context:component-scan base-package="com.meizu.tv.app.*">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
springmvc-servlet.xml
<context:component-scan base-package="com.meizu.tv.app.*"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
这里最后一个注意点是第 2 行的 use-default-filters="false"
,这个值默认为 true 的。如果不改成 false,容器还会加载 Controller 的相关子注解 @Service
、@Reposity
Last updated