这周写的文档实在太多,都没时间看看技术的东西。前几天把Swagger跟Dubbox结合完成后,心想可以在项目中推广使用了。然而,事情有点出乎意料!当我拿着第一个项目做试点的时候,发现实际项目中Disconf的org.reflections依赖包跟Swagger的org.reflections版本冲突!Disconf依赖它的版本是0.9.9-RC1,Swagger(1.5.8)依赖它的版本是0.9.10。如果强制采用0.9.10的版本,系统都无法启动。为了维持现有系统的稳定,我只好把Swagger依赖的版本降低。
然而Swagger对这块的兼容也比较差,降低版本后系统启动也会失败,一直提示:Property 'scan' threw exception; nested exception is java.lang.NoSuchMethodError:org.reflections.util.ClasspathHelper.forPackage(Ljava/lang/String;[Ljava/lang/ClassLoader;)Ljava/util/Collection。在社区上找了一圈也没有提供什么解决方案。没办法只好自己啃骨头了。
要解决这个问题,如果一直瞄准它这个提示信息,你永远也找不到方案,提示说这个类没有这个方法,但是实际上又存在。开启调试,跟进代码发现其实在调用ClasspathHelper.forPackage时,这个类是一个抽象类,spring在初始化bean的时候根本没法创建其实例对象。问题根源找到了我们在说解决方案。
具体解决方案如下:
第一、把BeanConfig的scan属性删除,新增一个init方法,在初始化bean时配置一个init-method属性。代码如下:
/**删除这些代码*/
// public boolean getScan() {
// return true;
// }
//
// public void setScan(boolean shouldScan) {
// scanAndRead();
// new SwaggerContextService()
// .withConfigId(configId)
// .withScannerId(scannerId)
// .withContextId(contextId)
// .withServletConfig(servletConfig)
// .withSwaggerConfig(this)
// .withScanner(this)
// .initConfig()
// .initScanner();
// }
//
// public void setScan() {
// setScan(true);
// }
/**新增该方法:*/
public void init() {
scanAndRead();
new SwaggerContextService()
.withConfigId(configId)
.withScannerId(scannerId)
.withContextId(contextId)
.withServletConfig(servletConfig)
.withSwaggerConfig(this)
.withScanner(this)
.initConfig()
.initScanner();
}
第二步、把org.reflections包的ClasspathHelper类代码拷贝新创一个普通类
package io.swagger.jaxrs.config;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import javax.servlet.ServletContext;
import org.reflections.Reflections;
import com.google.common.collect.Sets;
public class MyReflections{
/** returns {@code Thread.currentThread().getContextClassLoader()} */
public static ClassLoader contextClassLoader() { return Thread.currentThread().getContextClassLoader(); }
/** returns {@code Reflections.class.getClassLoader()} */
public static ClassLoader staticClassLoader() { return Reflections.class.getClassLoader(); }
/** returns given classLoaders, if not null, otherwise defaults to both {@link #contextClassLoader()} and {@link #staticClassLoader()} */
public ClassLoader[] classLoaders(ClassLoader... classLoaders) {
if (classLoaders != null && classLoaders.length != 0) {
return classLoaders;
} else {
ClassLoader contextClassLoader = contextClassLoader(), staticClassLoader = staticClassLoader();
return contextClassLoader != null ?
staticClassLoader != null && contextClassLoader != staticClassLoader ?
new ClassLoader[]{contextClassLoader, staticClassLoader} :
new ClassLoader[]{contextClassLoader} :
new ClassLoader[] {};
}
}
/** returns urls with resources of package starting with given name, using {@link ClassLoader#getResources(String)}
* that is, forPackage("org.reflections") effectively returns urls from classpath with packages starting with {@code org.reflections}
*
if optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()} and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}
*/
public Set forPackage(String name, ClassLoader... classLoaders) {
return forResource(resourceName(name), classLoaders);
}
/** returns urls with resources of given @{code resourceName}, using {@link ClassLoader#getResources(String)} */
public Set forResource(String resourceName, ClassLoader... classLoaders) {
final Set result = Sets.newHashSet();
final ClassLoader[] loaders = classLoaders(classLoaders);
for (ClassLoader classLoader : loaders) {
try {
final Enumeration urls = classLoader.getResources(resourceName);
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
int index = url.toExternalForm().lastIndexOf(resourceName);
if (index != -1) {
result.add(new URL(url.toExternalForm().substring(0, index)));
} else {
result.add(url); //whatever
}
}
} catch (IOException e) {
if (Reflections.log != null) {
Reflections.log.error("error getting resources for " + resourceName, e);
}
}
}
return result;
}
/** returns the url that contains the given class, using {@link ClassLoader#getResource(String)}
* if optional {@link ClassLoader}s are not specified, then either {@link #contextClassLoader()} or {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}
* */
public URL forClass(Class> aClass, ClassLoader... classLoaders) {
final ClassLoader[] loaders = classLoaders(classLoaders);
final String resourceName = aClass.getName().replace(".", "/") + ".class";
for (ClassLoader classLoader : loaders) {
try {
final URL url = classLoader.getResource(resourceName);
if (url != null) {
final String normalizedUrl = url.toExternalForm().substring(0, url.toExternalForm().lastIndexOf(aClass.getPackage().getName().replace(".", "/")));
return new URL(normalizedUrl);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
return null;
}
/** returns urls using {@link java.net.URLClassLoader#getURLs()} up the default classloaders parent hierarchy
*
using {@link #classLoaders(ClassLoader...)} to get default classloaders
**/
public Set forClassLoader() {
return forClassLoader(classLoaders());
}
/** returns urls using {@link java.net.URLClassLoader#getURLs()} up the classloader parent hierarchy
* if optional {@link ClassLoader}s are not specified, then both {@link #contextClassLoader()} and {@link #staticClassLoader()} are used for {@link ClassLoader#getResources(String)}
* */
public Set forClassLoader(ClassLoader... classLoaders) {
final Set result = Sets.newHashSet();
final ClassLoader[] loaders = classLoaders(classLoaders);
for (ClassLoader classLoader : loaders) {
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) classLoader).getURLs();
if (urls != null) {
result.addAll(Sets.newHashSet(urls));
}
}
classLoader = classLoader.getParent();
}
}
return result;
}
/** returns urls using {@code java.class.path} system property */
public Set forJavaClassPath() {
Set urls = Sets.newHashSet();
String javaClassPath = System.getProperty("java.class.path");
if (javaClassPath != null) {
for (String path : javaClassPath.split(File.pathSeparator)) {
try { urls.add(new File(path).toURI().toURL()); }
catch (Exception e) { e.printStackTrace(); }
}
}
return urls;
}
/** returns urls using {@link ServletContext} in resource path WEB-INF/lib */
public Set forWebInfLib(final ServletContext servletContext) {
final Set urls = Sets.newHashSet();
for (Object urlString : servletContext.getResourcePaths("/WEB-INF/lib")) {
try { urls.add(servletContext.getResource((String) urlString)); }
catch (MalformedURLException e) { /*fuck off*/ }
}
return urls;
}
/** returns url using {@link ServletContext} in resource path WEB-INF/classes */
public URL forWebInfClasses(final ServletContext servletContext) {
try {
final String path = servletContext.getRealPath("/WEB-INF/classes");
if (path != null) {
final File file = new File(path);
if (file.exists())
return file.toURL();
} else {
return servletContext.getResource("/WEB-INF/classes");
}
}
catch (MalformedURLException e) { /*fuck off*/ }
return null;
}
/** return urls that are in the current class path.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public Set forManifest() {
return forManifest(forClassLoader());
}
/** get the urls that are specified in the manifest of the given url for a jar file.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public Set forManifest(final URL url) {
final Set result = Sets.newHashSet();
result.add(url);
try {
final String part = cleanPath(url);
File jarFile = new File(part);
JarFile myJar = new JarFile(part);
URL validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), part);
if (validUrl != null) { result.add(validUrl); }
final Manifest manifest = myJar.getManifest();
if (manifest != null) {
final String classPath = manifest.getMainAttributes().getValue(new Attributes.Name("Class-Path"));
if (classPath != null) {
for (String jar : classPath.split(" ")) {
validUrl = tryToGetValidUrl(jarFile.getPath(), new File(part).getParent(), jar);
if (validUrl != null) { result.add(validUrl); }
}
}
}
} catch (IOException e) {
// don't do anything, we're going on the assumption it is a jar, which could be wrong
}
return result;
}
/** get the urls that are specified in the manifest of the given urls.
* attempts to load the jar manifest, if any, and adds to the result any dependencies it finds. */
public Set forManifest(final Iterable urls) {
Set result = Sets.newHashSet();
// determine if any of the URLs are JARs, and get any dependencies
for (URL url : urls) {
result.addAll(forManifest(url));
}
return result;
}
//
//a little bit cryptic...
URL tryToGetValidUrl(String workingDir, String path, String filename) {
try {
if (new File(filename).exists())
return new File(filename).toURI().toURL();
if (new File(path + File.separator + filename).exists())
return new File(path + File.separator + filename).toURI().toURL();
if (new File(workingDir + File.separator + filename).exists())
return new File(workingDir + File.separator + filename).toURI().toURL();
if (new File(new URL(filename).getFile()).exists())
return new File(new URL(filename).getFile()).toURI().toURL();
} catch (MalformedURLException e) {
// don't do anything, we're going on the assumption it is a jar, which could be wrong
}
return null;
}
public String cleanPath(final URL url) {
String path = url.getPath();
try {
path = URLDecoder.decode(path, "UTF-8");
} catch (UnsupportedEncodingException e) { /**/ }
if (path.startsWith("jar:")) {
path = path.substring("jar:".length());
}
if (path.startsWith("file:")) {
path = path.substring("file:".length());
}
if (path.endsWith("!/")) {
path = path.substring(0, path.lastIndexOf("!/")) + "/";
}
return path;
}
private String resourceName(String name) {
if (name != null) {
String resourceName = name.replace(".", "/");
resourceName = resourceName.replace("\\", "/");
if (resourceName.startsWith("/")) {
resourceName = resourceName.substring(1);
}
return resourceName;
} else {
return name;
}
}
}
第三步、把swagger的BeanConfig类中的classes()方法里的代码替换一下
//config.addUrls(ClasspathHelper.forPackage(pkg));
MyReflections ref =new MyReflections();
config.addUrls(ref.forPackage(pkg));
这样打包上传私库。
处理完成后,再拿试点项目测试,一切都正常了!终于有丢掉了一件事情,下周正式推广!