栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

记一次问题排查发现的spring-ldap的bug

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

记一次问题排查发现的spring-ldap的bug

spring验证xml文件文件的过程 获取xsd文件信息

spring中使用xsd文件定义bean标签元素,spring容器加载xml配置文件时,会先获取xsd文件去验证xml文件的正确性。

获取xsd文件可直接请求xsd文件的URL即可,但是在生产环境往往不可访问公网,或者由于网络的抖动导致请求的不可达,导致请求xsd文件出错,进而导致整个spring容器初始化失败;为避免这个问题,spring将对应的xsd文件放到本地jar中,容器初始化时优先从本地加载。

xsd文件定义一般定义在/meta-INF/spring.schemas文件中,例如spring-data-commons-2.1.10.RELEASE.jar中的spring.schemas提供了了根据xsd文件的url映射到对应的xsd文件的信息。

https://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd=org/springframework/data/repository/config/spring-repository-1.0.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.4.xsd=org/springframework/data/repository/config/spring-repository-1.4.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd=org/springframework/data/repository/config/spring-repository-1.5.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd=org/springframework/data/repository/config/spring-repository-1.6.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.8.xsd=org/springframework/data/repository/config/spring-repository-1.8.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.11.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd
https://www.springframework.org/schema/data/repository/spring-repository-2.1.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd
https://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd

这个spring在解析xml文件拿到对应的xsd的url后,就可以根据上面的信息找到xsd文件了,不用通过网络请求,然后就行可解析xsd文件元整xml文件的的正确性。

xsd文件的加载和寻找过程

查看springframwork源码PluggableSchemaResolver类定义了加载/meta-INF/spring.schemas文件的过程,加载全部jar中的/meta-INF/spring.schemas并将xsd的url和在jar中的路径保存在map中,在解析时直接根据xsd的url去map中寻找xsd文件的位置来获取。

PluggableSchemaResolver.java

public class PluggableSchemaResolver implements EntityResolver {

	public static final String DEFAULT_SCHEMA_MAPPINGS_LOCATION = "meta-INF/spring.schemas";

	private volatile Map schemaMappings;

  ...

	public PluggableSchemaResolver(ClassLoader classLoader) {
		this.classLoader = classLoader;
		this.schemaMappingsLocation = DEFAULT_SCHEMA_MAPPINGS_LOCATION;
	}

  ...

	public InputSource resolveEntity(String publicId, String systemId) throws IOException {
		if (logger.isTraceEnabled()) {
			logger.trace("Trying to resolve XML entity with public id [" + publicId +
					"] and system id [" + systemId + "]");
		}

		if (systemId != null) {
			String resourceLocation = getSchemaMappings().get(systemId);
			if (resourceLocation != null) {
				Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
				try {
					InputSource source = new InputSource(resource.getInputStream());
					source.setPublicId(publicId);
					source.setSystemId(systemId);
					if (logger.isDebugEnabled()) {
						logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
					}
					return source;
				}
				catch (FileNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
					}
				}
			}
		}
		return null;
	}

	
	private Map getSchemaMappings() {
		if (this.schemaMappings == null) {
			synchronized (this) {
				if (this.schemaMappings == null) {
					if (logger.isDebugEnabled()) {
						logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
					}
					try {
						Properties mappings =
								PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
						if (logger.isDebugEnabled()) {
							logger.debug("Loaded schema mappings: " + mappings);
						}
						Map schemaMappings = new ConcurrentHashMap(mappings.size());
						CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
						this.schemaMappings = schemaMappings;
					}
					catch (IOException ex) {
						throw new IllegalStateException(
								"Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
					}
				}
			}
		}
		return this.schemaMappings;
	}

  ...

}
问题描述 软件版本信息
  • spring framework:4.3.13.RELEASE
  • spring ldap:2.3.2.RELEASE
功能背景

ldap方式查询公司人员信息

主要实现代码

appContext-ldap.xml




    

    

    


UserDao.java

package liuming.dao;

import java.util.List;

import liuming.po.User;


public interface UserDao {

    User getUser(String userId);

}

UserDaoImpl.java

package liuming.dao.impl;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import liuming.dao.UserDao;
import liuming.po.User;


@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private LdapTemplate ldapTemplate;

    @Override
    public User getUser(String userId) {
 List users = ldapTemplate.search(LdapQueryBuilder.query()
  .base("OU=user")
  .where("objectClass").is("person")
  .and("cn").is(userId),
     USER_CONTEXT_MAPPER);
 if (CollectionUtils.isEmpty(users)) {
     return null;
 }
 return users.get(0);
    }

    private final static ContextMapper USER_CONTEXT_MAPPER = new AbstractContextMapper() {
 @Override
 public User doMapFromContext(DirContextOperations context) {
     User user = new User();
     ...
     return user;
 }
    };
}

UserDaoTest.java

package liuming.dao;

import java.util.List;
import java.util.Optional;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import liuming.po.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:appContext-ldap.xml")
public class UserDaoTest {

    @Autowired
    private UserDao userDao;

    @Test
    public void getUser() {
 User user = userDao.getUser("liuming216448");
 System.out.println(user);
    }

}
问题描述

上面的程序在本地机器上运行正常,发布到开发测试机上,抛出异常。

异常堆栈
org.xml.sax.SAXParseException; systemId: http://www.springframework.org/schema/ldap/spring-ldap.xsd; lineNumber: 9; columnNumber: 112; schema_reference.4: 无法读取方案文档 'http://www.springframework.org/schema/data/repository/spring-repository.xsd', 原因为 1) 无法找到文档; 2) 无法读取文档; 3) 文档的根元素不是 。
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
	at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.warning(ErrorHandlerWrapper.java:99)
	at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:392)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaErr(XSDHandler.java:4154)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.reportSchemaWarning(XSDHandler.java:4149)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemadocument1(XSDHandler.java:2491)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemadocument(XSDHandler.java:2193)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.resolveSchema(XSDHandler.java:2084)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.constructTrees(XSDHandler.java:1014)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.parseSchema(XSDHandler.java:625)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaLoader.loadSchema(XMLSchemaLoader.java:610)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.findSchemaGrammar(XMLSchemaValidator.java:2447)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.handleStartElement(XMLSchemaValidator.java:1768)
	at com.sun.org.apache.xerces.internal.impl.xs.XMLSchemaValidator.emptyElement(XMLSchemaValidator.java:761)
	at com.sun.org.apache.xerces.internal.impl.XMLNSdocumentScannerImpl.scanStartElement(XMLNSdocumentScannerImpl.java:351)
	at com.sun.org.apache.xerces.internal.impl.XMLdocumentFragmentScannerImpl$FragmentContentDriver.next(XMLdocumentFragmentScannerImpl.java:2784)
	at com.sun.org.apache.xerces.internal.impl.XMLdocumentScannerImpl.next(XMLdocumentScannerImpl.java:602)
	at com.sun.org.apache.xerces.internal.impl.XMLNSdocumentScannerImpl.next(XMLNSdocumentScannerImpl.java:112)
	at com.sun.org.apache.xerces.internal.impl.XMLdocumentFragmentScannerImpl.scandocument(XMLdocumentFragmentScannerImpl.java:505)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771)
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:243)
	at com.sun.org.apache.xerces.internal.jaxp.documentBuilderImpl.parse(documentBuilderImpl.java:339)
	at org.springframework.beans.factory.xml.DefaultdocumentLoader.loaddocument(DefaultdocumentLoader.java:76)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoaddocument(XmlBeanDefinitionReader.java:429)
	at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391)
	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.test.context.support.AbstractGenericContextLoader.loadBeanDefinitions(AbstractGenericContextLoader.java:257)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:124)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:108)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:251)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.net.UnknownHostException: www.springframework.org
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at sun.net.NetworkClient.doConnect(NetworkClient.java:180)
	at sun.net.www.http.HttpClient.openServer(HttpClient.java:463)
	at sun.net.www.http.HttpClient.openServer(HttpClient.java:558)
	at sun.net.www.http.HttpClient.(HttpClient.java:242)
	at sun.net.www.http.HttpClient.New(HttpClient.java:339)
	at sun.net.www.http.HttpClient.New(HttpClient.java:357)
	at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1220)
	at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1156)
	at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1050)
	at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:984)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:647)
	at com.sun.org.apache.xerces.internal.impl.XMLVersionDetector.determineDocVersion(XMLVersionDetector.java:148)
	at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:583)
	at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaParsingConfig.parse(SchemaParsingConfig.java:686)
	at com.sun.org.apache.xerces.internal.impl.xs.opti.SchemaDOMParser.parse(SchemaDOMParser.java:530)
	at com.sun.org.apache.xerces.internal.impl.xs.traversers.XSDHandler.getSchemadocument(XSDHandler.java:2181)
	... 57 more
问题排查和定位
  1. 从异常信息看,是获取和解析spring-repository.xsd文件出错,但是为什么会需要spring-repository.xsd呢?

于是想到在spring容器初始化的过程中,需要解析xsd文件以验证xml文件格式的正确与否,获取和解析spring-repository.xsd出错应该是在这个过程中出现的。

  1. 项目中并没有使用到spring-repository.xsd中定义的任何标签,为什么会加载它呢?

初步怀疑是存在jar包依赖,但是检查后并不存在包含spring-repository.xsd文件的spring-data-commons的jar包;但是打开spring-ldap.xsd文件发现有如下代码:


到此恍然大悟,原来是spring-ldap.xsd中又引用到spring-repository.xsd中的标签定义,所以解析时才回去找spring-repository.xsd文件,但本地没有spring-data-commons的jar包,所以才会尝试请求http://www.springframework.org/schema/data/repository/spring-repository.xsd 获取文件内容,但是开发环境的机器不能链接公网,而本地机器可以,所以才出现了上面的问题。

于是,想着只要在在pom.xml中添加spring-data-commons的jar包依赖,问题就应该解决了吧。然而,并没有,依旧是本地机器可以,开发测试环境的机器依旧报上面的异常。

  1. 查看spring-data-commons的jar包中的/meta-INF/spring.schemas定义,如下:
https://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd=org/springframework/data/repository/config/spring-repository-1.0.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.4.xsd=org/springframework/data/repository/config/spring-repository-1.4.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd=org/springframework/data/repository/config/spring-repository-1.5.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd=org/springframework/data/repository/config/spring-repository-1.6.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.8.xsd=org/springframework/data/repository/config/spring-repository-1.8.xsd
https://www.springframework.org/schema/data/repository/spring-repository-1.11.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd
https://www.springframework.org/schema/data/repository/spring-repository-2.1.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd
https://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd

发现,spring.schemas中的xsd文件url使用的是https,而在spring-ldap.xsd文件中引用的spring-repository.xsd文件的schemaLocation是http的,二者不一致,导致了去根据http的xsd文件url到map中找xsd文件路径时找不到。本地找不到之后,spring便尝试网络请求获取,而恰好网络请求不可达。

经过debug后,进一步确认了我的问题定位:

到此,定位到该问题为spring-ldap项目的bug,spring-ldap.xsd文件中引用的spring-repository.xsd文件的schemaLocation应该是https的。目前该问题已经在GitHub的spring-ldap上提出了issue等待解决。

问题解决方法

不使用spring自定义的ldap:标签元素,改为最基础的bean标签,这样就不会加载和解析spring-ldap.xsd文件了,最终的xml文件内用如下:




    
 
 
 
 
    

    
 
    

    


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/236070.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号