这周写的文档实在太多,都没时间看看技术的东西。前几天把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));
这样打包上传私库。
处理完成后,再拿试点项目测试,一切都正常了!终于有丢掉了一件事情,下周正式推广!