结论
原因有两个:
-
context、aop及其他spring相关的xml命名空间需要特殊Jar包才能解析。必须确保pom文件中依赖了对应的spring jar包。context对应spring-context.jar包。aop对应spring-aop.jar包。
-
如果配置文件中定义了2个及以上的spring xml命名空间,那么要防止jar包中配置文件相互覆盖。spring系列jar包中配置文件的命名及路径完全相同,默认情况下这些配置文件会相互覆盖。使用maven-shade-plugin来修改同名配置文件的处理方法,将覆盖改为合并。
场景还原
这是一个maven项目,项目框架采用的是Spring。项目的结构如下所示。
+- performance
+- pom.xml //maven配置文件
+- target //编译生成文件
| +- performance-1.0.0-SNAPSHOT.jar //可运行的jar文件
| +- original-performance-1.0.0-SNAPSHOT.jar
| \- classes //编译项目源文件生成class文件
+- src //源码文件
+- test
+- main
+- java
| \- concert
| +- Audience.java
| +- Main.java
| +- Performance.java
| \- PerformanceImpl.java
+- resources
\- performance.xml
-
在项目根目录performance中执行命令`mvn clean package`就可以生成可执行的jar文件`performance-1.0.0-SNAPSHOT.jar`。
-
生成jar文件后,执行命令`jar -jar target/performance-1.0.0-SNAPSHOT.jar`就可以运行jar文件。
Spring配置文件
-
xml文件中定义了两个命名空间context及aop。
-
context命名空间用于设定自动扫描包名,用于发现bean。
-
aop用于配置aspectJ注解的解析。
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="concert" />
<aop:aspectj-autoproxy />
<bean class="concert.Audience" />
<!--<bean class="concert.PerformanceImpl" />-->
</beans>
pom配置文件
-
aop命名空间需要org.springframework:spring-aop:4.3.14.RELEASE解析。
-
context命名空间需要org.springframework:spring-context:4.3.14.RELEASE解析。
<!-- ... -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
<!-- ... -->
</dependencies>
<!-- ... -->
程序入口源码文件
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("performance.xml");
Performance p = context.getBean(Performance.class);
p.perform();
context.close();
}
}
生成可运行的jar文件
mvn clean package
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.059 s
[INFO] Finished at: 2018-06-25T16:04:51+08:00
[INFO] ------------------------------------------------------------------------
运行jar文件
java -jar target/performance-1.0.0-SNAPSHOT.jar
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context]
Offending resource: class path resource [performance.xml]
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:70)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:118)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:110)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.error(BeanDefinitionParserDelegate.java:301)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1407)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1400)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:172)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:142)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:94)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:508)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127)
at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:614)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:515)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at concert.Main.main(Main.java:7)
问题分析方法
-
百度问题Unable to locate Spring NamespaceHandler for XML schem原因。
有一种解释说,context及aop都属于自定义命名空间。这些命名空间的规范描述需要特殊的方法来解析。context规范描述需要spring-context来解析,aop规范描述需要spring-aop来解析。如果没有将解析代码spring-context和spring-aop打包进最终生成的performance-1.0.0-SNAPSHOT.jar文件,那么运行时就会出现这个问题。
检查是否将spring-context及spring-aop添加到依赖列表。 检查是否正确书写了context及aop的规范描述URL。
-
对比正常运行项目
正常项目
|
错误项目
| |
pom配置文件
|
依赖spring-context及spring-aop。
|
依赖spring-context及spring-aop。
|
spring配置文件
|
只引用了一个自定义命名空间aop
|
引用了两个命名空间,aop和context。
|
取消使用context命名空间。context命名空间用于设定自动扫描目录,自动识别bean对象。可以通过显式声命bean来取消context命名空间。即使用<bean id="" class="" />声明bean文件。
重新运行mvn clean package生成jar文件,执行jar文件,可以正常运行,不再抛出异常。
初步结论:spring配置文件不能定义2个及2个以上的命名空间。
-
自定义classPath运行concert.Main
还原context命名空间,使用IntelliJ运行concert.Main,竟然正常运行!
IntelliJ本质是自定义classPath运行程序的。在terminal中自定义classpath运行程序,是可以正确运行程序的,并未抛出BeanDefinitionParsingException异常。详情如下所示。
around--before
Silencing cell phones
Taking seats
performanceImpl perform...
around--after
CLAP CLAP CLAP!!!
推翻之前结论:“spring配置文件不能定义2个及2个以上的命名空间“
-
google无法解析Spring命名空间问题
一种解释是,spring-context及spring-aop中有同名的配置文件,这些文件在最后打包进performance-1.0.0-SNAPSHOT.jar时,会出现覆盖问题。因此,就会有一个模块(aop或者context)无法正常发挥作用。
如下图所示。
解决方法
在maven-shade-plugin插件中通过Resource Transformers将所有的spring.schemas文件中的内容都合并到一起。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- 不覆盖同名文件,而是追加合并同名文件 -->
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.tooling</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>concert.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
重新打包运行,可以正常运行。