[CXF-7934]use ClassValue for ProxyClassLoader cache
diff --git a/core/src/main/java/org/apache/cxf/common/util/ProxyClassLoaderCache.java b/core/src/main/java/org/apache/cxf/common/util/ProxyClassLoaderCache.java new file mode 100644 index 0000000..dd69a13 --- /dev/null +++ b/core/src/main/java/org/apache/cxf/common/util/ProxyClassLoaderCache.java
@@ -0,0 +1,117 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.common.util; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; + +public class ProxyClassLoaderCache { + + private static final Logger LOG = LogUtils.getL7dLogger(ProxyClassLoaderCache.class); + private static final ThreadLocal<ClassLoader> PARENT_CLASSLOADER = new ThreadLocal<>(); + private static final ThreadLocal<Class<?>[]> PROXY_INTERFACES = new ThreadLocal<>(); + + + + private final ClassValue<ClassLoader> backend = new ClassValue<ClassLoader>() { + @Override + protected ClassLoader computeValue(Class<?> proxyInterface) { + LOG.log(Level.FINE, "can't find ProxyClassLoader from ClassValue Cache, " + + "will create a new one"); + LOG.log(Level.FINE, "interface for new created ProxyClassLoader is " + + proxyInterface.getName()); + LOG.log(Level.FINE, "interface's classloader for new created ProxyClassLoader is " + + proxyInterface.getClassLoader()); + return createProxyClassLoader(proxyInterface); + } + + }; + + private ClassLoader createProxyClassLoader(Class<?> proxyInterface) { + final SecurityManager sm = System.getSecurityManager(); + ProxyClassLoader ret = null; + if (sm == null) { + ret = new ProxyClassLoader(PARENT_CLASSLOADER.get(), PROXY_INTERFACES.get()); + } else { + ret = AccessController.doPrivileged(new PrivilegedAction<ProxyClassLoader>() { + @Override + public ProxyClassLoader run() { + return new ProxyClassLoader(PARENT_CLASSLOADER.get(), PROXY_INTERFACES.get()); + } + }); + } + for (Class<?> currentInterface : PROXY_INTERFACES.get()) { + ret.addLoader(getClassLoader(currentInterface)); + LOG.log(Level.FINE, "interface for new created ProxyClassLoader is " + + currentInterface.getName()); + LOG.log(Level.FINE, "interface's classloader for new created ProxyClassLoader is " + + currentInterface.getClassLoader()); + } + return ret; + } + + + public ClassLoader getProxyClassLoader(ClassLoader parent, Class<?>[] proxyInterfaces) { + try { + PARENT_CLASSLOADER.set(parent); + PROXY_INTERFACES.set(proxyInterfaces); + for (Class<?> currentInterface : proxyInterfaces) { + String ifName = currentInterface.getName(); + LOG.log(Level.FINE, "the interface we are checking is " + currentInterface.getName()); + LOG.log(Level.FINE, "the interface' classloader we are checking is " + + currentInterface.getClassLoader()); + if (!ifName.startsWith("org.apache.cxf") && !ifName.startsWith("java")) { + // cache and retrieve customer interface + LOG.log(Level.FINE, "the customer interface is " + currentInterface.getName() + + ". Will try to fetch it from Cache"); + return backend.get(currentInterface); + } + } + LOG.log(Level.FINE, "Non of interfaces are customer interface, " + + "retrive the last interface as key:" + + proxyInterfaces[proxyInterfaces.length - 1].getName()); + //the last interface is the variable type + return backend.get(proxyInterfaces[proxyInterfaces.length - 1]); + } finally { + PARENT_CLASSLOADER.remove(); + PROXY_INTERFACES.remove(); + } + } + + public void removeStaleProxyClassLoader(Class<?> proxyInterface) { + backend.remove(proxyInterface); + } + + private static ClassLoader getClassLoader(final Class<?> clazz) { + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + public ClassLoader run() { + return clazz.getClassLoader(); + } + }); + } + return clazz.getClassLoader(); + } +}
diff --git a/core/src/main/java/org/apache/cxf/common/util/ProxyHelper.java b/core/src/main/java/org/apache/cxf/common/util/ProxyHelper.java index ad83ad9..04e4ee2 100644 --- a/core/src/main/java/org/apache/cxf/common/util/ProxyHelper.java +++ b/core/src/main/java/org/apache/cxf/common/util/ProxyHelper.java
@@ -22,11 +22,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,11 +44,9 @@ private static final Logger LOG = LogUtils.getL7dLogger(ProxyHelper.class); - protected Map<String, ClassLoader> proxyClassLoaderCache = - Collections.synchronizedMap(new HashMap<String, ClassLoader>()); - protected int cacheSize = - Integer.parseInt(System.getProperty("org.apache.cxf.proxy.classloader.size", "3000")); - + protected ProxyClassLoaderCache proxyClassLoaderCache = + new ProxyClassLoaderCache(); + protected ProxyHelper() { } @@ -77,46 +70,28 @@ return loader; } String sortedNameFromInterfaceArray = getSortedNameFromInterfaceArray(interfaces); - ClassLoader cachedLoader = proxyClassLoaderCache.get(sortedNameFromInterfaceArray); - if (cachedLoader != null) { - if (canSeeAllInterfaces(cachedLoader, interfaces)) { - //found cached loader - LOG.log(Level.FINE, "find required loader from ProxyClassLoader cache with key" - + sortedNameFromInterfaceArray); - return cachedLoader; - } else { - //found cached loader somehow can't see all interfaces - LOG.log(Level.FINE, "find a loader from ProxyClassLoader cache with key " - + sortedNameFromInterfaceArray - + " but can't see all interfaces"); + ClassLoader cachedLoader = proxyClassLoaderCache.getProxyClassLoader(loader, interfaces); + if (canSeeAllInterfaces(cachedLoader, interfaces)) { + LOG.log(Level.FINE, "find required loader from ProxyClassLoader cache with key" + + sortedNameFromInterfaceArray); + return cachedLoader; + } else { + LOG.log(Level.FINE, "find a loader from ProxyClassLoader cache with interfaces " + + sortedNameFromInterfaceArray + + " but can't see all interfaces"); + for (Class<?> currentInterface : interfaces) { + String ifName = currentInterface.getName(); + + if (!ifName.startsWith("org.apache.cxf") && !ifName.startsWith("java")) { + // remove the stale ProxyClassLoader and recreate one + proxyClassLoaderCache.removeStaleProxyClassLoader(currentInterface); + cachedLoader = proxyClassLoaderCache.getProxyClassLoader(loader, interfaces); + + } } } - ProxyClassLoader combined; - LOG.log(Level.FINE, "can't find required ProxyClassLoader from cache, create a new one with parent " + loader); - final SecurityManager sm = System.getSecurityManager(); - if (sm == null) { - combined = new ProxyClassLoader(loader, interfaces); - } else { - combined = AccessController.doPrivileged(new PrivilegedAction<ProxyClassLoader>() { - @Override - public ProxyClassLoader run() { - return new ProxyClassLoader(loader, interfaces); - } - }); - } - for (Class<?> currentInterface : interfaces) { - combined.addLoader(getClassLoader(currentInterface)); - LOG.log(Level.FINE, "interface for new created ProxyClassLoader is " - + currentInterface.getName()); - LOG.log(Level.FINE, "interface's classloader for new created ProxyClassLoader is " - + currentInterface.getClassLoader()); - } - if (proxyClassLoaderCache.size() >= cacheSize) { - LOG.log(Level.FINE, "proxyClassLoaderCache is full, need clear it"); - proxyClassLoaderCache.clear(); - } - proxyClassLoaderCache.put(sortedNameFromInterfaceArray, combined); - return combined; + + return cachedLoader; } private String getSortedNameFromInterfaceArray(Class<?>[] interfaces) { @@ -127,17 +102,6 @@ return arraySet.toString(); } - private static ClassLoader getClassLoader(final Class<?> clazz) { - final SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { - public ClassLoader run() { - return clazz.getClassLoader(); - } - }); - } - return clazz.getClassLoader(); - } private boolean canSeeAllInterfaces(ClassLoader loader, Class<?>[] interfaces) { for (Class<?> currentInterface : interfaces) {
diff --git a/core/src/test/java/org/apache/cxf/common/util/ProxyClassLoaderCacheTest.java b/core/src/test/java/org/apache/cxf/common/util/ProxyClassLoaderCacheTest.java new file mode 100644 index 0000000..dd7d974 --- /dev/null +++ b/core/src/test/java/org/apache/cxf/common/util/ProxyClassLoaderCacheTest.java
@@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cxf.common.util; + + + +import java.io.Closeable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import org.apache.cxf.endpoint.Client; + +import org.junit.Assert; +import org.junit.Test; + +public class ProxyClassLoaderCacheTest extends Assert { + + private ProxyClassLoaderCache cache; + + @Test + public void testClassLoaderIdentical() throws Exception { + cache = new ProxyClassLoaderCache(); + ClassLoader cl1 = cache.getProxyClassLoader( + this.getClass().getClassLoader(), + new Class<?>[]{Closeable.class, Client.class, HelloWorld.class}); + ClassLoader cl2 = cache.getProxyClassLoader( + this.getClass().getClassLoader(), + new Class<?>[]{Closeable.class, Client.class, HelloWorld.class}); + assertTrue(cl1 == cl2); + } + + @Test + public void testClassLoaderIdenticalWithMultipleThreads() throws Exception { + cache = new ProxyClassLoaderCache(); + Set<ClassLoader> clSet = Collections.synchronizedSet(new HashSet<>()); + CountDownLatch countDownLatch = new CountDownLatch(50); + for (int i = 0; i < 50; i++) { + new Thread(new HelloWorker(clSet, countDownLatch)).start(); + } + countDownLatch.await(); + assertTrue(clSet.size() == 1); + } + + interface HelloWorld { + void sayHello(); + } + + class HelloWorker implements Runnable { + + private Set<ClassLoader> classLoaderSet; + + private CountDownLatch doneSignal; + HelloWorker(Set<ClassLoader> classLoaderSet, + CountDownLatch doneSignal) { + this.classLoaderSet = classLoaderSet; + this.doneSignal = doneSignal; + } + + public void run() { + + + try { + this.classLoaderSet.add(cache.getProxyClassLoader( + this.getClass().getClassLoader(), + new Class<?>[]{Closeable.class, + Client.class, + HelloWorld.class})); + doneSignal.countDown(); + + } catch (RuntimeException ex) { + ex.printStackTrace(); + + } + + } + + } +}
diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java index e2c80e1..fd83ad6 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
@@ -72,7 +72,7 @@ import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.ClassHelper; import org.apache.cxf.common.util.PrimitiveUtils; -import org.apache.cxf.common.util.ProxyClassLoader; +import org.apache.cxf.common.util.ProxyClassLoaderCache; import org.apache.cxf.common.util.ReflectionUtil; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.helpers.CastUtils; @@ -138,11 +138,8 @@ private static final String IGNORE_MATRIX_PARAMETERS = "ignore.matrix.parameters"; - private static Map<String, ProxyClassLoader> proxyClassLoaderCache = - Collections.synchronizedMap(new HashMap<String, ProxyClassLoader>()); - - private static int cacheSize = - Integer.parseInt(System.getProperty("org.apache.cxf.proxy.classloader.size", "3000")); + private static ProxyClassLoaderCache proxyClassLoaderCache = + new ProxyClassLoaderCache(); private InjectionUtils() { @@ -1082,24 +1079,19 @@ proxy = createThreadLocalServletApiContext(type.getName()); } if (proxy == null) { - ProxyClassLoader loader - = proxyClassLoaderCache.get(type.getName() + type.getClassLoader()); - if (loader == null - || !canSeeAllClasses(loader, new Class<?>[]{Proxy.class, type, ThreadLocalProxy.class})) { - // to avoid creating too much ProxyClassLoader to save Metaspace usage - LOG.log(Level.FINE, "can't find required ProxyClassLoader for type " + type.getName()); + ClassLoader loader + = proxyClassLoaderCache.getProxyClassLoader(Proxy.class.getClassLoader(), + new Class<?>[]{Proxy.class, ThreadLocalProxy.class, type}); + if (!canSeeAllClasses(loader, new Class<?>[]{Proxy.class, ThreadLocalProxy.class, type})) { + LOG.log(Level.FINE, "find a loader from ProxyClassLoader cache," + + " but can't see all interfaces"); + LOG.log(Level.FINE, "create a new one with parent " + Proxy.class.getClassLoader()); - loader = new ProxyClassLoader(Proxy.class.getClassLoader()); - loader.addLoader(type.getClassLoader()); - LOG.log(Level.FINE, "type classloader is " + type.getClassLoader()); - loader.addLoader(ThreadLocalProxy.class.getClassLoader()); - LOG.log(Level.FINE, "ThreadLocalProxy classloader is " - + ThreadLocalProxy.class.getClassLoader().getClass().getName()); - if (proxyClassLoaderCache.size() >= cacheSize) { - LOG.log(Level.FINE, "proxyClassLoaderCache is full, need clear it"); - proxyClassLoaderCache.clear(); - } - proxyClassLoaderCache.put(type.getName() + type.getClassLoader(), loader); + proxyClassLoaderCache.removeStaleProxyClassLoader(type); + proxyClassLoaderCache.getProxyClassLoader(Proxy.class.getClassLoader(), + new Class<?>[]{Proxy.class, ThreadLocalProxy.class, type}); + + } return (ThreadLocalProxy<T>)Proxy.newProxyInstance(loader, new Class[] {type, ThreadLocalProxy.class },
diff --git a/rt/frontend/simple/src/main/java/org/apache/cxf/frontend/ClientProxyFactoryBean.java b/rt/frontend/simple/src/main/java/org/apache/cxf/frontend/ClientProxyFactoryBean.java index 6a2c69a..d7b2977 100644 --- a/rt/frontend/simple/src/main/java/org/apache/cxf/frontend/ClientProxyFactoryBean.java +++ b/rt/frontend/simple/src/main/java/org/apache/cxf/frontend/ClientProxyFactoryBean.java
@@ -201,7 +201,7 @@ protected Class<?>[] getImplementingClasses() { Class<?> cls = clientFactoryBean.getServiceClass(); - return new Class[] {cls, Closeable.class, Client.class}; + return new Class[] {Closeable.class, Client.class, cls}; } protected ClientProxy clientClientProxy(Client c) {