背景
近期,由于工作需要研究了下truelicense,给xxx添加一个证书,限制产品的使用期限。前期将truelicense整合到spring-boot项目中很容易,过程中没有出现问题,但是同样的代码在整合到osgi容器中的时候,出现了异常,异常如下:
de.schlichtherle.xml.PersistenceServiceException: java.lang.reflect.UndeclaredThrowableException
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:397)[311:truelicense-xml:1.33.0]
at de.schlichtherle.license.PrivacyGuard.key2cert(PrivacyGuard.java:174)[312:truelicense-core:1.33.0]
at com.monk.core.license.CustomLicenseManager.install(CustomLicenseManager.java:48)[306:com.monk.core:1.0.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:406)[312:truelicense-core:1.33.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:382)[312:truelicense-core:1.33.0]
at com.monk.core.license.LicenseVerify.installLicense(LicenseVerify.java:80)[306:com.monk.core:1.0.0]
at com.monk.core.CoreBundleActivator.start(CoreBundleActivator.java:95)[306:com.monk.core:1.0.0]
at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.activateBundle(Felix.java:2146)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.startBundle(Felix.java:2064)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:955)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundle(DirectoryWatcher.java:1245)[7:org.apache.felix.fileinstall:3.5.0]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.startBundles(DirectoryWatcher.java:1217)[7:org.apache.felix.fileinstall:3.5.0]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.startAllBundles(DirectoryWatcher.java:1207)[7:org.apache.felix.fileinstall:3.5.0]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.doProcess(DirectoryWatcher.java:504)[7:org.apache.felix.fileinstall:3.5.0]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.process(DirectoryWatcher.java:358)[7:org.apache.felix.fileinstall:3.5.0]
at org.apache.felix.fileinstall.internal.DirectoryWatcher.run(DirectoryWatcher.java:310)[7:org.apache.felix.fileinstall:3.5.0]
Caused by: java.lang.reflect.UndeclaredThrowableException
at de.schlichtherle.xml.PersistenceService$1.exceptionThrown(PersistenceService.java:79)
at com.sun.beans.decoder.DocumentHandler.handleException(DocumentHandler.java:359)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:388)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:372)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler.parse(DocumentHandler.java:372)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:201)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:199)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.beans.XMLDecoder.parsingComplete(XMLDecoder.java:199)[:1.8.0_212]
at java.beans.XMLDecoder.close(XMLDecoder.java:174)[:1.8.0_212]
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:395)[311:truelicense-xml:1.33.0]
... 16 more
Caused by: java.io.IOException: Stream closed
at java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:170)[:1.8.0_212]
at java.io.BufferedInputStream.fill(BufferedInputStream.java:214)[:1.8.0_212]
at java.io.BufferedInputStream.read(BufferedInputStream.java:265)[:1.8.0_212]
at org.apache.xerces.impl.XMLEntityManager$RewindableInputStream.readAndBuffer(Unknown Source)[:]
at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)[:]
at org.apache.xerces.impl.XMLVersionDetector.determineDocVersion(Unknown Source)[:]
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)[:]
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)[:]
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)[:]
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)[:]
at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)[:]
at org.apache.xerces.jaxp.SAXParserImpl.parse(Unknown Source)[:]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:375)[:1.8.0_212]
... 26 more
异常分析:
首先从错误堆栈信息来看,能看出来问题是出在解析XML的时候,java.beans.XMLDecoder.close()方法在关闭文件流的时候发现文件流已经被关闭了。于是就可以带着下面的几个问题去跟一下代码
- 为什么会解析XML? 在哪里解析的XML —> 我生成的证书是个加密文件也不是XML呀,莫非这个加密文件在经过truelicense解密后就是个xml文件,毕竟用xml来存储配置信息也很常见。
- 如果是解析XML,解析XML的方式有多种,有没有可能出现了jar包冲突
既然有这两个问题,那么truelicense又是开源的,先把源码down下来,刨一下truelicense的源码,肯定可以验证第一个疑问的猜想,以及第一个疑问答案在哪里解析的XML
源码分析
在安装证书的时候,需要传给LicenseManager这个类其中一个参数就是证书的位置,LicenseManager将证书读成byte[]数组,经过一系列的解析(加解密就略过了,不是重点)最终得到一个InputStream,将这个流打印出来发现就是一个XML,解析XML的方式是通过SAXParser去解析的。(解开了第一个疑问)
// 入参key就是证书文件的byte[] 该方法在de.schlichtherle.license.LicenseManager中
protected synchronized LicenseContent install(final byte[] key, final LicenseNotary notary) throws Exception {
// 这一行就是将证书文件解析成GenericCertificate对象
final GenericCertificate certificate = getPrivacyGuard().key2cert(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) certificate.getContent();
validate(content);
setLicenseKey(key);
setCertificate(certificate);
return content;
}
// 这个方法的作用就是将证书的字节流解析转换成GenericCertificate对象(凭证信息) 该方法在de.schlichtherle.license.PrivacyGuard中
public GenericCertificate key2cert(final byte[] key) throws Exception {
// 这一行姑且理解为将证书文件解密之后读成一个InputSteam(XML形式)
final InputStream in = new GZIPInputStream(new ByteArrayInputStream(getCipher4Decryption().doFinal(key)));
final GenericCertificate certificate;
try {
// 再经过这个方法将XML解析成GenericCertificate对象
// PersistenceService.load(in)方法返回的是一个Object对象,再强转成GenericCertificate对象
certificate = (GenericCertificate) PersistenceService.load(in);
}
finally {
try { in.close(); }
catch (IOException weDontCare) { }
}
return certificate;
}
// 解析XML的具体方法 在de.schlichtherle.xml.PersistenceService类中
public static Object load(InputStream xmlIn) throws PersistenceServiceException {
if (null == xmlIn) throw new NullPointerException();
XMLDecoder decoder = null;
try {
// Note that the constructor already loads the complete object
// graph into memory. If anything goes wrong, an unchecked
// exception is thrown already HERE!
decoder = new XMLDecoder(
new BufferedInputStream(xmlIn, BUFSIZE),
null,
createExceptionListener());
// decoder.readObject()方法就是解析XML的具体方法,前面日志中打印出来的close()方法就是在这个方法中调用的
return decoder.readObject();
} catch (final UndeclaredThrowableException ex) {
throw new PersistenceServiceException(ex.getCause()); // unwrap cause
} catch (final Throwable ex) {
throw new PersistenceServiceException(ex);
} finally {
if (null != decoder) {
try {
decoder.close(); // Could throw e.g. OutOfMemoryError (again)!
} catch (final Throwable paranoid) {
throw new PersistenceServiceException(paranoid);
}
}
}
}
从XMLDecoder.readObject()这个方法一路跟jdk的源码,源码的调用链如下:
java.beans.XMLDecoder#readObject --> com.sun.beans.decoder.DocumentHandler#parse --> com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl#newSAXParserImpl
// 解析XML的真实实现类 在com.sun.beans.decoder.DocumentHandler中
public void parse(final InputSource var1) {
if (this.acc == null && null != System.getSecurityManager()) {
throw new SecurityException("AccessControlContext is not set");
} else {
AccessControlContext var2 = AccessController.getContext();
SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
public Void run() {
try {
SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this);
} catch (ParserConfigurationException var3) {
DocumentHandler.this.handleException(var3);
} catch (SAXException var4) {
Object var2 = var4.getException();
if (var2 == null) {
var2 = var4;
}
DocumentHandler.this.handleException((Exception)var2);
} catch (IOException var5) {
DocumentHandler.this.handleException(var5);
}
return null;
}
}, var2, this.acc);
}
}
// 这个方法在com.sun.beans.decoder.DocumentHandler中
public static SAXParserFactory newInstance() {
return FactoryFinder.find(
/* The default property name according to the JAXP spec */
SAXParserFactory.class,
/* The fallback implementation class name */
"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
}
通过上面一段源码可以看出,jdk在默认解析xml时,默认会使用自己jre/lib/rt.jar中的类去解析xml,但是我们的osgi容器中恰恰存在xerces的jar包,这个jar包通过SPI机制,重写了解析xml的方式,自己去实现解析xml。
那么看到这里,我就在思考一个问题:osgi中已经存在的xerces这个jar的版本是2.9.1,但是rt.jar中jdk自己整合进去的解析xml的实现类是什么版本并不知道(这里通过包名来看,只能猜想jdk是自己将xerces的逻辑整合进去了),但是用的是哪个版本的并不知道,所以我开始怀疑是不是存在jar冲突。(解开第二个疑问)
既然这个xml通过SPI的机制重写这个类,那我就只能通过修改启动参数的方式去重写这个解析xml的具体实现类了,因为启动参数的优先级是最高的
通过指定解析XML的具体实现类,让osgi容器不要使用org.apache.xerces.jaxp.SAXParserImpl来解析这个xml
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl -Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl -Dorg.apache.xerces.xni.parser.XMLParserConfiguration=com.sun.org.apache.xerces.internal.parsers.XML11Configuration
在指定了解析XML的具体实现类之后,在安装证书的时候,依然会报错,不过错误信息换了一个,说是文件提前结束。异常信息如下:
de.schlichtherle.xml.PersistenceServiceException: java.lang.reflect.UndeclaredThrowableException
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:397)[311:truelicense-xml:1.33.0]
at de.schlichtherle.license.PrivacyGuard.key2cert(PrivacyGuard.java:174)[312:truelicense-core:1.33.0]
at com.monk.core.license.CustomLicenseManager.install(CustomLicenseManager.java:73)[306:com.monk.core:1.0.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:406)[312:truelicense-core:1.33.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:382)[312:truelicense-core:1.33.0]
at com.monk.core.license.LicenseVerify.installLicense(LicenseVerify.java:80)[306:com.monk.core:1.0.0]
at com.monk.core.license.LicenseAdapter.installLicense(LicenseAdapter.java:35)[306:com.monk.core:1.0.0]
at com.monk.core.CoreBundleActivator.start(CoreBundleActivator.java:95)[306:com.monk.core:1.0.0]
at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.activateBundle(Felix.java:2146)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.startBundle(Felix.java:2064)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1291)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:304)[org.apache.felix.framework-4.2.1.jar:]
at java.lang.Thread.run(Thread.java:748)[:1.8.0_212]
Caused by: java.lang.reflect.UndeclaredThrowableException
at de.schlichtherle.xml.PersistenceService$1.exceptionThrown(PersistenceService.java:79)
at com.sun.beans.decoder.DocumentHandler.handleException(DocumentHandler.java:359)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:385)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:372)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler.parse(DocumentHandler.java:372)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:201)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:199)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.beans.XMLDecoder.parsingComplete(XMLDecoder.java:199)[:1.8.0_212]
at java.beans.XMLDecoder.close(XMLDecoder.java:174)[:1.8.0_212]
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:395)[311:truelicense-xml:1.33.0]
... 13 more
Caused by: org.xml.sax.SAXParseException: Premature end of file.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:400)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:327)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1472)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:1014)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:602)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:505)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:842)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:771)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:643)[:1.8.0_212]
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:327)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:375)[:1.8.0_212]
... 23 more
通过异常信息可以看出来,这里已经使用了jdk自己整合的rt.jar中的类去解析mxl,但是依然报错,那就不是jar冲突的问题,那么导致这个问题的最终原因是什么呢?我就开始有点纳闷了。.
那就只能换个思路去解决问题了。 导致stream close 和 premature end of file异常的原因有哪些呢?
- 编码格式
- xml中存在非法字符
- xml格式错误,文件确实过早结束
既然不确定这个文件里是不是有问题,那么就把这个流打印出来看看就知道了。避免有看不见的特殊字符,将流输出到文件中,用两种方式去解析xml,看看会不会报错。
由于这个过程都是truelicense和jdk自己去实现的,那就只能重写源码了。 重写部分见下面的红色部分(既然重写了源码,那我就在代码中不同地方都打印了日志)
// 这个方法的作用就是将证书的字节流解析转换成GenericCertificate对象(凭证信息) 该方法在de.schlichtherle.license.PrivacyGuard中
public GenericCertificate key2cert(final byte[] key) throws Exception {
//输出解密后证书文件到license.xml文件 (这段代码是在笔记中凭理解在地铁上码出来的,没有验证,如果有错,就自行修改下)
InputStream in2 = new GZIPInputStream(
new ByteArrayInputStream(
getCipher4Decryption().doFinal(key)));
File file = new File("/home/monk/temp/license.xml");
OutputStream out = new FileOutputStream(file);
try {
int len = 0 ;
byte[] bys = new byte[1024];
while ((len = in2.read(bys)) != -1) {
out.write(bys, 0, len);
}
out.flush();
out.close();
}catch(Exception e) {
e.printStackTrace();
}finally {
in2.close();
}
//输出解密后证书文件到license.xml文件END
// 这一行姑且理解为将证书文件解密之后读成一个InputSteam(XML形式)
final InputStream in = new GZIPInputStream(new ByteArrayInputStream(getCipher4Decryption().doFinal(key)));
final GenericCertificate certificate;
try {
// 再经过这个方法将XML解析成GenericCertificate对象
// PersistenceService.load(in)方法返回的是一个Object对象,再强转成GenericCertificate对象
certificate = (GenericCertificate) PersistenceService.load(in);
}
finally {
try { in.close(); }
catch (IOException weDontCare) { }
}
return certificate;
}
通过去解析这个输出的xml文件,发现用两种方式去解析这个xml都没有问题。都可以正常解析出来这个xm流并没有特殊字符、乱码、提前结束的现象。
通过上述步骤可以得出,证书的license是没有问题的,确定没有出现文件过早结束、乱码、特殊字符这些情况。
那就只能继续重写解析xml的方法了,将de.schlichtherle.xml.PersistenceService这个类的完整路径copy到自己的项目代码里来,把load方法加上日志,看看代码这个load方法到底是哪里出了问题。重写的load方法如下所示:(标红的为重写部分)
public static Object load(InputStream xmlIn)
throws PersistenceServiceException {
logger.info("override PersistenceService.load()");
if (null == xmlIn) throw new NullPointerException();
XMLDecoder decoder = null;
try {
// Note that the constructor already loads the complete object
// graph into memory. If anything goes wrong, an unchecked
// exception is thrown already HERE!
decoder = new XMLDecoder(
new BufferedInputStream(xmlIn, BUFSIZE),
null,
createExceptionListener(), CustomLicenseManager.class.getClassLoader());
logger.info("override PersistenceService.load() decoder:{}", decoder);
return decoder.readObject();
} catch (final UndeclaredThrowableException ex) {
logger.info("UndeclaredThrowableException");
throw new PersistenceServiceException(ex.getCause()); // unwrap cause
} catch (final Throwable ex) {
logger.info("Throwable");
throw new PersistenceServiceException(ex);
} finally {
if (null != decoder) {
try {
logger.info("close()");
decoder = null;
//decoder.close(); // Could throw e.g. OutOfMemoryError (again)!
} catch (final Throwable paranoid) {
throw new PersistenceServiceException(paranoid);
}
}
}
}
重写的日志部署到osgi容器中,就发现了一个新的异常信息-----> ClassNotFoundException 如下所示:
2021-06-12 11:54:02,361 | INFO | FelixStartLevel | LicenseAdapter | 306 - com.monk.core - 1.0.0 | load license.config.properties .
2021-06-12 11:54:02,382 | INFO | FelixStartLevel | CustomLicenseManager | 306 - com.monk.core - 1.0.0 | csutom licensemanager install method --1
2021-06-12 11:54:02,405 | INFO | FelixStartLevel | PersistenceService | 306 - com.monk.core - 1.0.0 | override PersistenceService.load()
2021-06-12 11:54:02,408 | INFO | FelixStartLevel | PersistenceService | 306 - com.monk.core - 1.0.0 | override PersistenceService.load() decoder:java.beans.XMLDecoder@62128a2f
2021-06-12 11:54:02,415 | INFO | FelixStartLevel | PersistenceService | 306 - com.monk.core - 1.0.0 | UndeclaredThrowableException
2021-06-12 11:54:02,415 | INFO | FelixStartLevel | PersistenceService | 306 - com.monk.core - 1.0.0 | close()
2021-06-12 11:54:02,417 | ERROR | FelixStartLevel | LicenseVerify | 306 - com.monk.core - 1.0.0 | ------------------------------- 证书安装失败 -------------------------------
2021-06-12 11:54:02,418 | ERROR | FelixStartLevel | LicenseVerify | 306 - com.monk.core - 1.0.0 | java.lang.ClassNotFoundException: de/schlichtherle/xml/GenericCertificate
de.schlichtherle.xml.PersistenceServiceException: java.lang.ClassNotFoundException: de/schlichtherle/xml/GenericCertificate
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:417)[306:com.monk.core:1.0.0]
at de.schlichtherle.license.PrivacyGuard.key2cert(PrivacyGuard.java:174)[312:truelicense-core:1.33.0]
at com.monk.core.license.CustomLicenseManager.install(CustomLicenseManager.java:54)[306:com.monk.core:1.0.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:406)[312:truelicense-core:1.33.0]
at de.schlichtherle.license.LicenseManager.install(LicenseManager.java:382)[312:truelicense-core:1.33.0]
at com.monk.core.license.LicenseVerify.installLicense(LicenseVerify.java:80)[306:com.monk.core:1.0.0]
at com.monk.core.license.LicenseAdapter.installLicense(LicenseAdapter.java:35)[306:com.monk.core:1.0.0]
at com.monk.core.CoreBundleActivator.start(CoreBundleActivator.java:95)[306:com.monk.core:1.0.0]
at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.activateBundle(Felix.java:2146)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.startBundle(Felix.java:2064)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.Felix.setActiveStartLevel(Felix.java:1291)[org.apache.felix.framework-4.2.1.jar:]
at org.apache.felix.framework.FrameworkStartLevelImpl.run(FrameworkStartLevelImpl.java:304)[org.apache.felix.framework-4.2.1.jar:]
at java.lang.Thread.run(Thread.java:748)[:1.8.0_212]
Caused by: java.lang.ClassNotFoundException: de/schlichtherle/xml/GenericCertificate
at java.lang.Class.forName0(Native Method)[:1.8.0_212]
at java.lang.Class.forName(Class.java:264)[:1.8.0_212]
at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:75)[:1.8.0_212]
at com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:110)[:1.8.0_212]
at com.sun.beans.finder.ClassFinder.resolveClass(ClassFinder.java:171)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler.findClass(DocumentHandler.java:404)[:1.8.0_212]
at com.sun.beans.decoder.NewElementHandler.addAttribute(NewElementHandler.java:80)[:1.8.0_212]
at com.sun.beans.decoder.ObjectElementHandler.addAttribute(ObjectElementHandler.java:102)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler.startElement(DocumentHandler.java:294)[:1.8.0_212]
at org.apache.xerces.parsers.AbstractSAXParser.startElement(Unknown Source)[:]
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanStartElement(Unknown Source)[:]
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)[:]
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)[:]
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)[:]
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)[:]
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)[:]
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)[:]
at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)[:]
at org.apache.xerces.jaxp.SAXParserImpl.parse(Unknown Source)[:]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:375)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler$1.run(DocumentHandler.java:372)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)[:1.8.0_212]
at com.sun.beans.decoder.DocumentHandler.parse(DocumentHandler.java:372)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:201)[:1.8.0_212]
at java.beans.XMLDecoder$1.run(XMLDecoder.java:199)[:1.8.0_212]
at java.security.AccessController.doPrivileged(Native Method)[:1.8.0_212]
at java.beans.XMLDecoder.parsingComplete(XMLDecoder.java:199)[:1.8.0_212]
at java.beans.XMLDecoder.readObject(XMLDecoder.java:250)[:1.8.0_212]
at de.schlichtherle.xml.PersistenceService.load(PersistenceService.java:414)[306:com.monk.core:1.0.0]
... 13 more
可以很明确的是,truelicense的jar包我肯定部署到osgi容器中了,怎么会找不到这个类呢,只能说是类加载这块儿有问题了。于是在网上找到相关的资料是这么描述的
osgi特点:
没有对Java的底层实现如类库和Java虚拟机等进行修改,OSGi实现的模块间引用与隔离、模块的动态启用与停用的关键在于它扩展的类加载架构。
OSGi的类加载架构并未遵循Java所推荐的双亲委派模型(Parents Delegation Model),它的类加载器通过严谨定义的规则从Bundle的一个子集中加载类。
每一个被正确解析的Bundle都有一个独立的类加载器支持,这Bundle类加载器:每个Bundle都有自己独立的类加载器,用于加载本Bundle中的类和资源。当一个Bundle去请求加载另一个Bundle导出的Package中的类时,要把加载请求委派给导出类的那个Bundle的加载器处理,而无法自己去加载其他Bundle的类。些类加载器之间互相协作形成了一个类加载的代理网络架构,因此OSGi中采用的是网状的类加载架构,而不是Java传统的树状类加载架构。
osgi中的类加载器:
父类加载器:由Java平台直接提供,最典型的场景包括启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader),它们用于加载以“java.”开头的类以及在父类委派清单中声明为要委派给父类加载器加载的类。
Bundle类加载器:每个Bundle都有自己独立的类加载器,用于加载本Bundle中的类和资源。当一个Bundle去请求加载另一个Bundle导出的Package中的类时,要把加载请求委派给导出类的那个Bundle的加载器处理,而无法自己去加载其他Bundle的类。
最终原因
我部署到osgi容器中的truelicense-core.jar,而这个truelicense-core的jar又依赖于truelicense-xml.jar,而我自己的应用程序A也是单独的部署在osgi容器中,所以无法访问到truelicense-xml.jar中的类,或者就重写jar包,将truelicense-xml.jar中的GenericCertificate类export出来。显然这样更麻烦,我还不如直接重写下自己的CustomLicenseManager类,将这个解析xml的方式重写下也能达到同样的效果。而重写的方式有多种,我这里尝试了两种都是可以的,代码如下:
解决方案
方案一
接着上面的来,在自己的工程里新增一个GenericCertificate类,类全限定名和truelicense-xml中的GenericCertificate类路径完全一致。然后重写这个类的load方法,指定xmlDecoder的类加载器,让解析xml的时候使用这个类加载器去加载xml。
这里只列出了这一个方法,也只需要改动标红的这个一个地方,其他的地方完全copy源码过来即可。
public static Object load(InputStream xmlIn)
throws PersistenceServiceException {
if (null == xmlIn) throw new NullPointerException();
XMLDecoder decoder = null;
try {
// Note that the constructor already loads the complete object
// graph into memory. If anything goes wrong, an unchecked
// exception is thrown already HERE!
decoder = new XMLDecoder(
new BufferedInputStream(xmlIn, BUFSIZE),
null,
createExceptionListener(), CustomLicenseManager.class.getClassLoader());
return decoder.readObject();
} catch (final UndeclaredThrowableException ex) {
throw new PersistenceServiceException(ex.getCause()); // unwrap cause
} catch (final Throwable ex) {
throw new PersistenceServiceException(ex);
} finally {
if (null != decoder) {
try {
decoder.close(); // Could throw e.g. OutOfMemoryError (again)!
} catch (final Throwable paranoid) {
throw new PersistenceServiceException(paranoid);
}
}
}
}
方案二:
在不新增任何类的情况下,重写CustomLicenseManager类中的解析xml方法,代码如下:(改动有点大,故下代码将整个类都贴出来)
package com.monk.license;
import java.beans.ExceptionListener;
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.schlichtherle.license.CipherParam;
import de.schlichtherle.license.LicenseContent;
import de.schlichtherle.license.LicenseContentException;
import de.schlichtherle.license.LicenseManager;
import de.schlichtherle.license.LicenseNotary;
import de.schlichtherle.license.LicenseParam;
import de.schlichtherle.license.NoLicenseInstalledException;
import de.schlichtherle.util.ObfuscatedString;
import de.schlichtherle.xml.GenericCertificate;
import de.schlichtherle.xml.XMLConstants;
/**
* 自定义LicenseManager,用于增加额外的信息校验(除了LicenseManager的校验,我们还可以在这个类里面添加额外的校验信息)
*/
public class CustomLicenseManager extends LicenseManager {
private static Logger logger = LoggerFactory.getLogger(CustomLicenseManager.class);
private static final String PBE_WITH_MD5_AND_DES = new ObfuscatedString(
new long[] { 0x27B2E8783E47F1ABL, 0x45CF8AD4390DC9D8L, 0xAB320350966BC9BFL })
.toString(); /* => "PBEWithMD5AndDES" */
private CipherParam param;
private Cipher cipher;
private SecretKey key;
private AlgorithmParameterSpec algoParamSpec;
public CustomLicenseManager(LicenseParam param) {
super(param);
}
/**
* 复写create方法
*/
@Override
protected synchronized byte[] create(LicenseContent content, LicenseNotary notary) throws Exception {
initialize(content);
this.validateCreate(content);
final GenericCertificate certificate = notary.sign(content);
return getPrivacyGuard().cert2key(certificate);
}
/**
* 复写install方法,其中validate方法调用本类中的validate方法,校验IP地址、Mac地址等其他信息
*/
@Override
protected LicenseContent install(final byte[] key, final LicenseNotary notary) throws Exception {
param = getPrivacyGuard().getCipherParam();
GenericCertificate certificate = loadKey(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
this.validate(content);
setLicenseKey(key);
setCertificate(certificate);
return content;
}
protected Cipher getCipher4Decryption() {
Cipher cipher = getCipher();
try {
cipher.init(Cipher.DECRYPT_MODE, key, algoParamSpec);
} catch (InvalidKeyException cannotHappen) {
throw new AssertionError(cannotHappen);
} catch (InvalidAlgorithmParameterException cannotHappen) {
throw new AssertionError(cannotHappen);
}
return cipher;
}
protected Cipher getCipher() {
algoParamSpec = new PBEParameterSpec(new byte[] { (byte) 0xce, (byte) 0xfb, (byte) 0xde, (byte) 0xac,
(byte) 0x05, (byte) 0x02, (byte) 0x19, (byte) 0x71 }, 2005);
try {
KeySpec keySpec = new PBEKeySpec(param.getKeyPwd().toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(PBE_WITH_MD5_AND_DES);
key = keyFac.generateSecret(keySpec);
cipher = Cipher.getInstance(PBE_WITH_MD5_AND_DES);
} catch (NoSuchAlgorithmException cannotHappen) {
throw new AssertionError(cannotHappen);
} catch (InvalidKeySpecException cannotHappen) {
throw new AssertionError(cannotHappen);
} catch (NoSuchPaddingException cannotHappen) {
throw new AssertionError(cannotHappen);
}
return cipher;
}
/**
* 复写verify方法,调用本类中的validate方法,校验IP地址、Mac地址等其他信息
*/
@Override
protected synchronized LicenseContent verify(final LicenseNotary notary) throws Exception {
final byte[] key = getLicenseKey();
if (null == key) {
throw new NoLicenseInstalledException(getLicenseParam().getSubject());
}
GenericCertificate certificate = loadKey(key);
notary.verify(certificate);
final LicenseContent content = (LicenseContent) this.load(certificate.getEncoded());
this.validate(content);
setCertificate(certificate);
return content;
}
private GenericCertificate loadKey(final byte[] key)
throws IOException, IllegalBlockSizeException, BadPaddingException {
InputStream in = new GZIPInputStream(new ByteArrayInputStream(getCipher4Decryption().doFinal(key)));
GenericCertificate certificate = null;
XMLDecoder decoder = null;
try {
decoder = new XMLDecoder(new BufferedInputStream(in, 10240), null, null,
CustomLicenseManager.class.getClassLoader());
decoder.setExceptionListener(new ExceptionListener() {
@Override
public void exceptionThrown(Exception e) {
logger.error(e.getMessage(), e);
}
});
certificate = (GenericCertificate) decoder.readObject();
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
if (decoder != null) {
try {
decoder.close();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
return certificate;
}
/**
* 校验生成证书的参数信息
*/
protected synchronized void validateCreate(final LicenseContent content) throws LicenseContentException {
// final LicenseParam param = getLicenseParam();
final Date now = new Date();
final Date notBefore = content.getNotBefore();
final Date notAfter = content.getNotAfter();
if (null != notAfter && now.after(notAfter)) {
throw new LicenseContentException("证书失效时间不能早于当前时间");
}
if (null != notBefore && null != notAfter && notAfter.before(notBefore)) {
throw new LicenseContentException("证书生效时间不能晚于证书失效时间");
}
final String consumerType = content.getConsumerType();
if (null == consumerType) {
throw new LicenseContentException("用户类型不能为空");
}
}
/**
* 重写XMLDecoder解析XML
*/
private Object load(String encoded) {
BufferedInputStream inputStream = null;
XMLDecoder decoder = null;
try {
inputStream = new BufferedInputStream(
new ByteArrayInputStream(encoded.getBytes(XMLConstants.XML_CHARSET)));
decoder = new XMLDecoder(new BufferedInputStream(inputStream, XMLConstants.DEFAULT_BUFSIZE), null,
null, CustomLicenseManager.class.getClassLoader());
return decoder.readObject();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} finally {
try {
if (decoder != null) {
decoder.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}