关于cxf:CXF动态调用WebService时Class缓存的实现
起因CXF 2.7版本和最新的3.5版本,当应用 动静调用WebService性能时,如果返回值联合应用Jackson转json性能,那么存在 非堆内存透露问题。详情参见:一波三折!记一次非堆内存透露(CXF+Jackson)的排查 透露起因CXF动静调用WebService,个别蕴含以下步骤: 下载WSDL文件将WSDL文件解析成Java代码模型生成Java源码编译源码->Class文件创立ClassLoader加载Class创立数据绑定&类型初始化应用这些Class实现WebService交互废除这些Class,期待JVM闲暇时GC回收然而,如果在对WebService返回值进行解决时,应用了Jackson的toJson性能,那么Jackson会持有CXF动静生成的Class,导致每次动静产生的Class无奈回收,Class数量一直增长,最终导致 非堆内存溢出。 解决办法解决办法有很多种,咱们这里提供将webservice动静调用时产生的class/classloader缓存起来复用,一则解决class透露问题,二则因为缩小了每次wsdl文件解析、java代码生成、java代码编译、class加载的过程,对webservice动静调用还有一些性能上的晋升。当然,为了避免wsdl变动引起缓存变成脏数据的问题,能够通过对wsdl的MD5来判断缓存有效性。 因为不确定CXF的ClientImpl对象是否为重型对象,不确定此对象是否适合进行缓存应用、以及在并发场合下应用,革新计划的次要缓存内容为ClassLoader。 参考实现/** * 动静调用webservice缓存内容 * * @author sswhsz * @CreateDate 2022-11-18 */public class CacheItem { /** * http://*?wsdl、或者 file:/*.wsdl 目前只起备用阐明作用,不作为唯一性辨认 */ private String wsdlLocation; /** * wsdl文档的MD5,用于wsdl的唯一性判断 */ private String wsdlMD5; /** * wsdl文档的长度,疾速判断用 */ private int wsdlSize; /** * wsdl解析的Java模型(仅办法原型、不含实现代码) */ private JavaModel javaModel; private S2JJAXBModel intermediateModel; private JAXBDataBinding databinding; /** * wsdl解析的定义文档 (因为不缓存Client,每次创立Client时均须要解析definition对象) */ private Definition definition; /** * 已加载 webservice class的classLoader,缓存备用; * * 另外,为了避免此classLoader应用平台bundle作为parent,从而阻止平台bundle的卸载/更新; * * 动静生成的classloader将应用 零碎classloader作为parent (ClassLoader.getSystemClassLoader) */ private ClassLoader classLoader; //getter、setter略}/** * 反对WebService动静调用缓存 * * @author sswhsz * @CreateDate 2022-11-18 */public class CacheDynamicClientFactory extends DynamicClientFactory { protected CacheDynamicClientFactory(Bus bus) { super(bus); } /** * * @param wsdlUrl * wsdl本地文件门路,例如:tempFile.getAbsolutePath() * @param cacheItem * 缓存内容,如果是第一次创立classloader/definition,创立的内容将写入 cacheItem,否则从cacheItem读取 * @return */ public Client createClientUseCache(String wsdlUrl, CacheItem cacheItem) { LOG.log(Level.FINE, "Creating client from WSDL " + wsdlUrl); Definition definition = cacheItem.getDefinition(); if (definition == null) { definition = createDefinition(wsdlUrl); cacheItem.setDefinition(definition); } Service svc = createService(definition); ClientImpl client = new ClientImpl(bus, svc, /*port*/null, getEndpointImplFactory()); ClassLoader classLoader = cacheItem.getClassLoader(); if (classLoader == null) { // our hashcode + timestamp ought to be enough. String stem = toString() + "-" + System.currentTimeMillis(); File src = new File(tmpdir, stem + "-src"); File classes = new File(tmpdir, stem + "-classes"); //1、生成java代码 Object[] models = generateJavaCode(wsdlUrl, svc, /*bindingFiles*/null, src); JCodeModel codeModel = (JCodeModel) models[0]; S2JJAXBModel intermediateModel = (S2JJAXBModel) models[1]; //2.编译 compilerJavaCode(src, classes); //3.创立classloader classLoader = createClassLoader(classes); //4.创立数据绑定 & 加载class JAXBDataBinding databinding = createDataBinding(codeModel, classLoader); svc.setDataBinding(databinding); //5. 类型初始化? 原始代码照搬-->没太看懂 typeClassInit(classLoader, client, intermediateModel); // delete the classes files FileUtils.removeDir(classes); cacheItem.setIntermediateModel(intermediateModel); cacheItem.setDatabinding(databinding); cacheItem.setClassLoader(classLoader); } else { JAXBDataBinding databinding = cacheItem.getDatabinding(); svc.setDataBinding(databinding); S2JJAXBModel intermediateModel = cacheItem.getIntermediateModel(); typeClassInit(classLoader, client, intermediateModel); } //设置上下文ClassLoader的目标是为了后续 invoke办法时,从上下文中获取加载的class //但此处 CXF设计不佳。 更正当的做法是 将classloader存储在ClientImpl上,在invoke办法前后 try{ 设置} finally {勾销设置} ClassLoaderUtils.setThreadContextClassloader(classLoader); return client; } /** * java代码生成 * * @param src * 生成Java代码的根目录 * @return Object[] 返回元素2:[0]=JCodeModel, [1]=intermediateModel */ protected Object[] generateJavaCode(String wsdlUrl, Service svc, List<String> bindingFiles, File src) { //all SI's should have the same schemas SchemaCollection schemas = svc.getServiceInfos().get(0).getXmlSchemaCollection(); SchemaCompiler compiler = JAXBUtils.createSchemaCompilerWithDefaultAllocator(new HashSet<String>()); InnerErrorListener listener = new InnerErrorListener(wsdlUrl); Object elForRun = ReflectionInvokationHandler.createProxyWrapper(listener, JAXBUtils.getParamClass(compiler, "setErrorListener")); compiler.setErrorListener(elForRun); OASISCatalogManager catalog = bus.getExtension(OASISCatalogManager.class); hackInNewInternalizationLogic(compiler, catalog); addSchemas(compiler.getOptions(), compiler, svc.getServiceInfos(), schemas); addBindingFiles(bindingFiles, compiler); S2JJAXBModel intermediateModel = compiler.bind(); listener.throwException(); JCodeModel codeModel = intermediateModel.generateCode(null, elForRun); if (!src.mkdir()) { throw new IllegalStateException("Unable to create working directory " + src.getPath()); } try { Object writer = JAXBUtils.createFileCodeWriter(src); codeModel.build(writer); } catch (Exception e) { throw new IllegalStateException("Unable to write generated Java files for schemas: " + e.getMessage(), e); } return new Object[] { codeModel, intermediateModel }; } protected void compilerJavaCode(File src, File classes) { if (!classes.mkdir()) { throw new IllegalStateException("Unable to create working directory " + classes.getPath()); } StringBuilder classPath = new StringBuilder(); try { ClassLoader parent = ClassLoader.getSystemClassLoader(); setupClasspath(classPath, parent); } catch (Exception ex) { throw new RuntimeException(ex); } List<File> srcFiles = FileUtils.getFilesRecurse(src, ".+\\.java$"); if (!compileJavaSrc(classPath.toString(), srcFiles, classes.toString())) { LOG.log(Level.SEVERE, new Message("COULD_NOT_COMPILE_SRC", LOG, src).toString()); } FileUtils.removeDir(src); } protected ClassLoader createClassLoader(File classes) { URL[] urls = null; try { urls = new URL[] { classes.toURI().toURL() }; } catch (MalformedURLException mue) { throw new IllegalStateException("Internal error; a directory returns a malformed URL: " + mue.getMessage(), mue); } //留神:parentClassLoader应用 system class loader, 防止无心中阻止 bundle class loader的卸载和更新 ClassLoader parent = ClassLoader.getSystemClassLoader(); ClassLoader cl = ClassLoaderUtils.getURLClassLoader(urls, parent); return cl; } protected void typeClassInit(ClassLoader classLoader, ClientImpl client, S2JJAXBModel intermediateModel) { //5. 应用线程上下文ClassLoader加载class ClassLoaderHolder holder = ClassLoaderUtils.setThreadContextClassloader(classLoader); try { ServiceInfo svcfo = client.getEndpoint().getEndpointInfo().getService(); TypeClassInitializer visitor = new TypeClassInitializer(svcfo, intermediateModel, allowWrapperOps()); visitor.walk(); } finally { holder.reset(); } } protected JAXBDataBinding createDataBinding(JCodeModel codeModel, ClassLoader cl) { JAXBContext context; Map<String, Object> contextProperties = jaxbContextProperties; if (contextProperties == null) { contextProperties = Collections.emptyMap(); } StringBuilder sb = new StringBuilder(); boolean firstnt = false; for (Iterator<JPackage> packages = codeModel.packages(); packages.hasNext();) { JPackage jpackage = packages.next(); if (!isValidPackage(jpackage)) { continue; } if (firstnt) { sb.append(':'); } else { firstnt = true; } sb.append(jpackage.name()); } JAXBUtils.logGeneratedClassNames(LOG, codeModel); String packageList = sb.toString(); try { if (StringUtils.isEmpty(packageList)) { context = JAXBContext.newInstance(new Class[0], contextProperties); } else { context = JAXBContext.newInstance(packageList, cl, contextProperties); } } catch (JAXBException jbe) { throw new IllegalStateException("Unable to create JAXBContext for generated packages: " + jbe.getMessage(), jbe); } JAXBDataBinding databinding = new JAXBDataBinding(); databinding.setContext(context); return databinding; } protected Definition createDefinition(String wsdlUrl) { try { // use wsdl manager to parse wsdl or get cached definition Definition definition = bus.getExtension(WSDLManager.class).getDefinition(wsdlUrl); return definition; } catch (WSDLException ex) { throw new ServiceConstructionException(new Message("SERVICE_CREATION_MSG", LOG), ex); } } protected Service createService(Definition definition) { WSDLServiceFactory sf = new WSDLServiceFactory(bus, definition); sf.setAllowElementRefs(allowRefs); Service svc = sf.create(); return svc; } public static CacheDynamicClientFactory newInstance() { Bus bus = CXFBusFactory.getThreadDefaultBus(); return new CacheDynamicClientFactory(bus); }}