jersey在 spring boot 添加 packages 扫描路径支持
最近公司内部系统要做数据对接,故使用 jersey 来做 restful webservice 接口设计。由于 spring boot 已经集成 jersey,估计直接导入 spring-boot-starter-jersey 就好了。
在测试时候除了遇到中文乱码之外花费了比较长的时间,其余暂时没遇到大的问题。然而发布的时候发现了一个坑。
public class JerseyConfig extends ResourceConfig { public JerseyConfig(){ //registerClasses(findAllClasses()); packages("com.xx"); // 注册日志 register(LoggingFeature.class); // 异常处理 register(ExceptionHandler.class); // 跨域过滤器注册 register(CorsFilter.class); } }
jersey 是使用 register(Class clazz) 或者 packages("packageName") 来注册或者扫描来加载所需的类到容器中的。然而在实际项目中,使用 register(Class clazz) 会写大量的注册类,依赖性太强,而且在maven多模块中更不好处理。
而使用 packages 来扫描,在开发测试的时候倒没发现什么问题,但是使用 spring boot 打成 jar 包运行时,会产生 FileNotFound 的错误,这是因为 使用 jersey 的 packages 扫描的机制是无法扫描到 jar 中的 class 文件的。
由于使用的是 spring boot,spring 的 component scan 是可以做到的。故参考 spring 扫描机制的源码,将 jar 中的 class 文件加载进来就好了。
代码如下:
import lombok.extern.slf4j.Slf4j; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.server.ResourceConfig; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.util.ClassUtils; import java.io.IOException; import java.util.HashSet; import java.util.Set; /** * @author gongtao * @version 2018-04-23 10:15 **/ @Configuration @Slf4j public class JerseyConfig extends ResourceConfig { public JerseyConfig(){ //扫描注册 registerClasses(findAllClasses()); // 注册日志 register(LoggingFeature.class); // 异常处理 register(ExceptionHandler.class); // 跨域过滤器注册 register(CorsFilter.class); } /** * 由于spring boot 打包为jar包,jersey packages 无法扫描jar对应的文件夹的文件,故自定义包扫描 * @return */ private Set<Class<?>> findAllClasses(){ String scanPackages = "com.xxx.eoms.innerinterface.interfaces.resource.*"; ClassLoader loader = this.getClass().getClassLoader(); Resource[] resources = new Resource[0]; try { resources = scan(loader, scanPackages); } catch (IOException e) { log.error("加载class异常",e); } return convert(loader, resources); } /** * 扫描 jar 包 * @param loader * @param packageName * @return * @throws IOException */ private Resource[] scan(ClassLoader loader, String packageName) throws IOException { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader); String pattern = "classpath*:" + ClassUtils.convertClassNameToResourcePath(packageName) + "/**/*.class"; return resolver.getResources(pattern); } /** * 加载 class * @param loader * @param resource * @return */ private Class<?> loadClass(ClassLoader loader,Resource resource) { try { CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(loader); MetadataReader reader = metadataReaderFactory.getMetadataReader(resource); return ClassUtils.forName(reader.getClassMetadata().getClassName(), loader); } catch (LinkageError | ClassNotFoundException e) { if (log.isDebugEnabled()) { log.debug("Ignoring candidate class resource " + resource + " due to " + e); } return null; } catch (Throwable e) { if (log.isWarnEnabled()) { log.warn("Unexpected failure when loading class resource " + resource, e); } return null; } } /** * resources 转换为 Set<Class> * @param loader * @param resources * @return */ private Set<Class<?>> convert(ClassLoader loader,Resource[] resources){ Set<Class<?>> classSet = new HashSet<>(resources.length); for (Resource resource : resources){ Class<?> clazz = loadClass(loader, resource); if (clazz != null){ classSet.add(clazz); } } return classSet; }