今天配置XSS过滤文件的时候,发现在xss初始化失败,抛出空指针错误。
classpath:escapeCharacter.properties
无法加载需要转换的特殊字符列表
java.lang.NullPointerException
at com.iflytek.emco.web.filters.XSSFilter.loadEscapeCharacters(XSSFilter.java:232)
at com.iflytek.emco.web.filters.XSSFilter.init(XSSFilter.java:118)
at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:279)
at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:260)
at org.apache.catalina.core.ApplicationFilterConfig.(ApplicationFilterConfig.java:105)
at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4958)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5652)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1863)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:792)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:792)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1486)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:96)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1327)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1419)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:847)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
at sun.rmi.transport.Transport$1.run(Transport.java:177)
at sun.rmi.transport.Transport$1.run(Transport.java:174)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
于是打断点,开始查询原因。
发现代码中是这样来获取配置文件的路径的
XSSFilter.class.getClassLoader().getResource(filePath).getFile()
于是我将配置文件的路径设置为空,再运行一次,发现这时候得到的文件路径是这样的
/D:/demo/target/demo-1.0.0-SNAPSHOT/WEB-INF/classes/
获取到的绝对路径是我项目编译后的tartget下项目的WEB-INF/classes下目录
也就是说,项目中src/resources下的配置文件在编译后都会被放在WEB-INF/classes下
于是我将xml中的classpath:escapeCharacter.properties路径改为escapeCharacter.properties
再次运行,通过原来的读取配置文件方法就能读取到配置文件了。
/D:/demo/target/demo-1.0.0-SNAPSHOT/WEB-INF/classes/escapeCharacter.properties
问题到此为止,算是解决了,不过我还是不太明白classpath:xxx.xml和xxx.xml有什么区别,接下来我来研究一番。
用maven构建项目时候resource目录就是默认的classpath
classPath即为java文件编译之后的class文件的编译目录一般为/classes,src下的xml在编译时也会复制到classPath下.
class.getClassLoader().getResource和class.getResource()都是java自带的获取文件资源路径的方法。
System.out.println(Demo.class.getClassLoader().getResource(""));//空值
System.out.println(Demo.class.getClassLoader().getResource("/"));//根目录
System.out.println(Demo.class.getClassLoader().getResource("demo.properties"));//文件名
System.out.println("");
System.out.println(Demo.class.getResource(""));//空值
System.out.println(Demo.class.getResource("/"));//根目录
System.out.println(Demo.class.getResource("demo.properties"));//文件名
两种方式执行后获取的路径如下
file:/D:/workspace/demo/target/classes/
null
file:/D:/workspace/demo/target/classes/demo.properties
file:/D:/workspace/demo/target/classes/
file:/D:/workspace/demo/target/classes/
file:/D:/workspace/demo/target/classes/demo.properties
通过上面的路径不难看出,class.getResource和class.getClassLoader().getResource()两者共通之处在于两者都是在查找项目编译后的classes路径下的文件,区别在于class.getClassLoader().getResource()不能以"/"获取classpath根目录。
所以到这里我就知道了为什么我上面解决的问题里为什么文件路径要写成escapeCharacter.properties而不是classpath:escapeCharacter.properties,只要是在resources下的配置文件在项目编译之后都会被copy到classes目录下,然后通过class.getClassLoader().getResource(filePath).getFile()就能直接获取classes目录下该配置文件,而不需要添加classpath。
ClassLoader.getSystemResources();还有一种获取资源的方式,暂时放一放。
spring也有它自带的文件查找方式。在一般的ssm项目里的web.xml可以看到spring的配置。
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/springmvc.xml
1
springmvc
/
其中param-value的配置是classpath:spring/springmvc.xml,如果这个配置是用来告诉项目这个xml配置文件的路径的,那么这个classpath是干什么的呢???这也是困惑了我很久的一个问题。
在我发现我们项目dubbo项目启动类的代码后,我找了一个切入点,如下的上下文下载类也是加载文件路径前添加了classpath的文件路径。
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
让我们直接跳到关键源码部分看看。以下的所有的源码都是围绕着 Resource[] getResources(String locationPattern)进行展开的,所以学习过程中别忘了这个关键方法。
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith("classpath*:")) {
return this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
} else {
int prefixEnd = locationPattern.indexOf(":") + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
}
}
上面代码是org.springframework.core.io.support.PathMatchingResourcePatternResolver中的获取资源数组的方法,其中传入的locationPattern就是我们传入的classpath:applicationContext.xml,从这里可以看到还有一个是否文件路径以classpath*:的判断。嗯,又出来个classpath*,还是让我们先来看看classpath*吧。
this.getPathMatcher().isPattern(locationPattern.substring("classpath*:".length())) ? this.findPathMatchingResources(locationPattern) : this.findAllClassPathResources(locationPattern.substring("classpath*:".length()));
上面一段代码首先是判断以classpath*开头的路径字符串是否匹配某个条件。
这个isPattern中的逻辑就让我比较费解了。org.springframework.util.AntPathMatcher中isPattern()源码如下:
public boolean isPattern(String path) {
return path.indexOf(42) != -1 || path.indexOf(63) != -1;
}
居然去掉classpath*:,然后再在applicationContext.xml字符串查询42和63?
以前也没用过indexof(int),原来是用来查找指定的字符的在字符串中第一次出现的索引。而42和63分别指的是*和?
我想到了也有classpath*:applicationContext*.xml这样的文件配置,好像是用来匹配多个相同文件名开头的文件。
如果classpath*:后的字符串匹配到了*或者?的话,就执行this.findPathMatchingResources(locationPattern)
接下来看看org.springframework.core.io.support.findPathMatchingResources()方法,入参为classpath*:applicationContext*.xml
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//传入的locationPattern值为classpath*:applicationContext*.xml
String rootDirPath = this.determineRootDir(locationPattern);//获取到classpath*
String subPattern = locationPattern.substring(rootDirPath.length());//获取到applicationContext*.xml
Resource[] rootDirResources = this.getResources(rootDirPath);//获取项目classpath以及项目所依赖jar中的classpath中所有的文件资源,
Set result = new LinkedHashSet(16);
Resource[] var6 = rootDirResources;
int var7 = rootDirResources.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource rootDirResource = var6[var8];
rootDirResource = this.resolveRootDirResource(rootDirResource);
if (rootDirResource.getURL().getProtocol().startsWith("vfs")) {
result.addAll(PathMatchingResourcePatternResolver.VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, this.getPathMatcher()));
} else if (this.isJarResource(rootDirResource)) {
result.addAll(this.doFindPathMatchingJarResources(rootDirResource, subPattern));
} else {
result.addAll(this.doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
//返回查询到的资源
return (Resource[])result.toArray(new Resource[result.size()]);
}
上述方法的作用是获取对应路径下所有的文件资源。方法如果匹配到了*或上述代码中的第5行又会进入到getResources()方法里,然后因为找不到*或?,进入findAllClassPathResources方法,这时location入参为""
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
if (location.startsWith("/")) {//如果有反斜杠,把反斜杠去除
path = location.substring(1);
}
Set result = this.doFindAllClassPathResources(path);
return (Resource[])result.toArray(new Resource[result.size()]);
}
然后再进入方法doFindAllClassPathResources(),path入参为"",从字面意思上看是查找所有classpath路径下的文件资源。
protected Set doFindAllClassPathResources(String path) throws IOException {
Set result = new LinkedHashSet(16);//创建LinkedHashSet
ClassLoader cl = this.getClassLoader();//获取本类加载器,在这里我获取到的是AppClassLoader(系统类加载器)
Enumeration resourceUrls = cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path);//根据路径获取资源枚举对象
//循环存储资源集合
while(resourceUrls.hasMoreElements()) {
URL url = (URL)resourceUrls.nextElement();
result.add(this.convertClassLoaderURL(url));//根据url生成的urlResource实例并加入result
}
if ("".equals(path)) {
this.addAllClassLoaderJarRoots(cl, result);//如果是路径为空,则默认为根目录,添加所有的jar根目录文件资源
}
return result;
}
void addAllClassLoaderJarRoots(ClassLoader classLoader, Set result)方法源码如下
protected void addAllClassLoaderJarRoots(ClassLoader classLoader, Set result) {
if (classLoader instanceof URLClassLoader) {//如果类加载器是URLClassLoader或者URLClassLoader的子类,执行if中的代码
try {
URL[] var3 = ((URLClassLoader)classLoader).getURLs();
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {//循环URL数组
URL url = var3[var5];
if (ResourceUtils.isJarFileURL(url)) {//判断是否是Jar文件路径
try {
UrlResource jarResource = new UrlResource("jar:" + url.toString() + "!/");
if (jarResource.exists()) {//如果该jar文件资源存在,则存进result中
result.add(jarResource);
}
} catch (MalformedURLException var9) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot search for matching files underneath [" + url + "] because it cannot be converted to a valid 'jar:' URL: " + var9.getMessage());
}
}
}
}
} catch (Exception var10) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot introspect jar files since ClassLoader [" + classLoader + "] does not support 'getURLs()': " + var10);
}
}
}
//如果类加载器不为null,则继续使用当前类加载器的父类加载器来查询父类加载器路径下是否还有jar文件资源
if (classLoader != null) {
try {
this.addAllClassLoaderJarRoots(classLoader.getParent(), result);
} catch (Exception var8) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot introspect jar files in parent ClassLoader since [" + classLoader + "] does not support 'getParent()': " + var8);
}
}
}
}
上述方法中主要作用是获取所有ClassLoader或者ClassLoader子类路径下的所有Jar包文件资源,第一次执行该方法时传入的classLoader是系统类加载器,然后在方法最后的this.addAllClassLoaderJarRoots(classLoader.getParent(), result)中会获取系统类加载器的父类(扩展类加载器)路径下所有Jar包文件资源,最后到启动类加载器,但是启动类加载器为null,所以就跳出了方法。有关类加载器的知识请移步到我的另一篇博客。https://mp.csdn.net/postedit/82313612
这就是查询到的和项目有关的所有的资源,里面有项目classpath里的文件也有jdk和maven库所依赖中的jar包
接下来让我们动动小手,也页面移动到上面的findPathMatchingResources方法
for(int var8 = 0; var8 < var7; ++var8) {
Resource rootDirResource = var6[var8];
rootDirResource = this.resolveRootDirResource(rootDirResource);
if (rootDirResource.getURL().getProtocol().startsWith("vfs")) {
result.addAll(PathMatchingResourcePatternResolver.VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, this.getPathMatcher()));
} else if (this.isJarResource(rootDirResource)) {
result.addAll(this.doFindPathMatchingJarResources(rootDirResource, subPattern));
} else {
result.addAll(this.doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
其中的for循环上面获取到所有文件资源,然后从中匹配applicationContext*.xml,我本地匹配到了file [D:\workspace\demo\target\classes\applicationContext.xml]和URL [jar:file:/C:/Users/shenshuiyuan/.m2/repository/com/elephant/framework/1.0.2-M/framework-1.0.2-M.jar!/applicationContext_default.xml],一个是我本地classpath路径下的applicationContext.xml文件,另一个是maven库中jar包里的applicationContext_default.xml。
到这里我大概知道了,配置classpath*:applicationContext*.xml的含义就是需要在项目classpath路径以及项目所依赖的所有Jar包中查询和applicationContext*.xml匹配的文件,其中*的意思就是任何字符。
接下来继续学习classpath*:applicationContext.xml
classpath*:applicationContext.xml配置走的代码逻辑是被包含在classpath*:applicationContext*.xml配置走的代码逻辑中,不再累述,也是找项目classpath中的文件以及jdk和所依赖maven中的jar包classpath中的文件。
让我们回到当文件路径是classpath:applicationContext.xml时走的代码逻辑,还是在上面的Resource[] getResources(String locationPattern)方法中
如果locationPattern是classpath:applicationContext.xml的话,走的是如下逻辑
int prefixEnd = locationPattern.indexOf(":") + 1;
return this.getPathMatcher().isPattern(locationPattern.substring(prefixEnd)) ? this.findPathMatchingResources(locationPattern) : new Resource[]{this.getResourceLoader().getResource(locationPattern)};
如果路径中含有*或?,那么就执行this.findPathMatchingResources(locationPattern),这和上面走的代码逻辑是类似的,在this.findPathMatchingResources(locationPattern)中会执行new Resource[]{this.getResourceLoader().getResource(locationPattern)}方法。
首先this.getResourceLoader()获取到ClassPathXmlApplicationContext实例,再调用ClassPathXmlApplicationContext的org.springframework.core.io.getResource(locationPattern)方法,入参为classpath:applicationContext.xml
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return this.getResourceByPath(location);
} else if (location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()), this.getClassLoader());
} else {
try {
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException var3) {
return this.getResourceByPath(location);
}
}
}
若location以classpath:开头,就实例化ClassPathResource对象并且返回,ClassPathResource会查询项目classpath路径下是否存在classpath:applicationContext.xml文件并且返回该文件资源。
到此基本上就结束了,我来总结一下。
1:java自带的查询文件资源的方法这么三种,class.getClassLoader().getResource、class.getResource()和ClassLoader.getSystemResources()
2:classpath是maven项目编译后的classes下的路径,在spring环境中,配置路径一般都会带上classpath,这是用来匹配classpath项目路径,classpath*:是区别于classpath的,因为classpath是仅仅会在当前项目里的编译后classes文件路径查询文件,而classpath*:会在当前项目的classes路径下、jdk和项目所依赖的jar包中(classpath路径下)查询目标文件资源。