Skip to content

Commit ff9fd96

Browse files
hellyguojeremiahjstacey
authored andcommitted
enhance: improve the performance of ObjFactory (#491)
* add jmh to benchmark * enhance: cache class and method to avoid reading each time * add comments for new methods in ObjFactory * enhance: create the exception once and use many times
1 parent 9a42290 commit ff9fd96

File tree

3 files changed

+142
-16
lines changed

3 files changed

+142
-16
lines changed

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,18 @@
381381
</exclusion>
382382
</exclusions>
383383
</dependency>
384+
<dependency>
385+
<groupId>org.openjdk.jmh</groupId>
386+
<artifactId>jmh-core</artifactId>
387+
<version>1.21</version>
388+
<scope>test</scope>
389+
</dependency>
390+
<dependency>
391+
<groupId>org.openjdk.jmh</groupId>
392+
<artifactId>jmh-generator-annprocess</artifactId>
393+
<version>1.21</version>
394+
<scope>test</scope>
395+
</dependency>
384396
</dependencies>
385397

386398
<build>

src/main/java/org/owasp/esapi/util/ObjFactory.java

Lines changed: 104 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import java.lang.reflect.Method;
1515
import java.lang.reflect.Modifier;
16+
import java.util.concurrent.ConcurrentHashMap;
1617

1718
/**
1819
* A generic object factory to create an object of class T. T must be a concrete
@@ -44,6 +45,11 @@
4445
*/
4546
public class ObjFactory {
4647

48+
private static final int CACHE_INITIAL_CAPACITY = 4096;
49+
private static final float CACHE_LOAD_FACTOR = 0.75F;
50+
private static final ConcurrentHashMap<String,Class<?>> CLASSES_CACHE = new ConcurrentHashMap<>(CACHE_INITIAL_CAPACITY, CACHE_LOAD_FACTOR);
51+
private static final ConcurrentHashMap<String,MethodWrappedInfo> METHODS_CACHE = new ConcurrentHashMap<>(CACHE_INITIAL_CAPACITY, CACHE_LOAD_FACTOR);
52+
4753
/**
4854
* Create an object based on the <code>className</code> parameter.
4955
*
@@ -70,20 +76,13 @@ public static <T> T make(String className, String typeName) throws Configuration
7076
// No big deal...just use "[unknown?]" for this as it's only for an err msg.
7177
typeName = "[unknown?]"; // CHECKME: Any better suggestions?
7278
}
73-
74-
Class<?> theClass = Class.forName(className);
75-
76-
try {
77-
Method singleton = theClass.getMethod( "getInstance" );
78-
79-
// If the implementation class contains a getInstance method that is not static, this is an invalid
80-
// object configuration and a ConfigurationException will be thrown.
81-
if ( !Modifier.isStatic( singleton.getModifiers() ) )
82-
{
83-
throw new ConfigurationException( "Class [" + className + "] contains a non-static getInstance method." );
84-
}
85-
86-
obj = singleton.invoke( null );
79+
80+
Class<?> theClass = loadClassByStringName(className);
81+
82+
try {
83+
Method singleton = findSingletonCreateMethod(className, theClass);
84+
85+
obj = singleton.invoke( null );
8786
} catch (NoSuchMethodException e) {
8887
// This is a no-error exception, if this is caught we will continue on assuming the implementation was
8988
// not meant to be used as a singleton.
@@ -97,7 +96,7 @@ public static <T> T make(String className, String typeName) throws Configuration
9796
}
9897

9998
return (T)obj; // Eclipse warning here if @SupressWarnings omitted.
100-
99+
101100
// Issue 66 - Removed System.out calls as we are throwing an exception in each of these cases
102101
// anyhow.
103102
} catch( IllegalArgumentException ex ) {
@@ -130,9 +129,98 @@ public static <T> T make(String className, String typeName) throws Configuration
130129
}
131130
// DISCUSS: Should we also catch ExceptionInInitializerError here? See Google Issue #61 comments.
132131
}
133-
132+
133+
/**
134+
* Load the class in cache, or load by the classloader and cache it
135+
*
136+
* @param className The name of the class to construct. Should be a fully qualified name
137+
* @return The target class
138+
* @throws ClassNotFoundException Failed to load class by the className
139+
*/
140+
private static Class<?> loadClassByStringName(String className) throws ClassNotFoundException {
141+
Class<?> clazz;
142+
if (CLASSES_CACHE.containsKey(className)) {
143+
clazz = CLASSES_CACHE.get(className);
144+
} else {
145+
clazz = Class.forName(className);
146+
CLASSES_CACHE.putIfAbsent(className, clazz);
147+
}
148+
return clazz;
149+
}
150+
134151
/**
152+
* Find the method to create a singleton object
153+
*
154+
* @param className The name of the class to construct. Should be a fully qualified name
155+
* @param theClass The class loaded in prior
156+
* @return The method to create a singleton object
157+
* @throws NoSuchMethodException Failed to find the target method
158+
*/
159+
private static Method findSingletonCreateMethod(String className, Class<?> theClass) throws NoSuchMethodException {
160+
MethodWrappedInfo singleton = loadMethodByStringName(className,theClass);
161+
162+
// If the implementation class contains a getInstance method that is not static, this is an invalid
163+
// object configuration and a ConfigurationException will be thrown.
164+
if (!singleton.isStaticMethod()) {
165+
throw singleton.getNonStaticEx();
166+
}
167+
return singleton.getMethod();
168+
}
169+
170+
/**
171+
*
172+
* @param className The name of the class to construct. Should be a fully qualified name
173+
* @param theClass The class loaded in prior
174+
* @return Wrapped data, contains the method object and the method is static method or not
175+
* @throws NoSuchMethodException Failed to find the target method
176+
*/
177+
private static MethodWrappedInfo loadMethodByStringName(String className, Class<?> theClass) throws NoSuchMethodException {
178+
String methodName = className + "getInstance";
179+
MethodWrappedInfo methodInfo;
180+
if (METHODS_CACHE.containsKey(methodName)) {
181+
methodInfo = METHODS_CACHE.get(methodName);
182+
} else {
183+
Method method = theClass.getMethod("getInstance");
184+
boolean staticMethod = Modifier.isStatic(method.getModifiers());
185+
ConfigurationException nonStaticEx = staticMethod ? null :
186+
new ConfigurationException("Class [" + className + "] contains a non-static getInstance method.");
187+
methodInfo = new MethodWrappedInfo(method, staticMethod, nonStaticEx);
188+
METHODS_CACHE.putIfAbsent(methodName, methodInfo);
189+
}
190+
return methodInfo;
191+
}
192+
193+
/**
135194
* Not instantiable
136195
*/
137196
private ObjFactory() { }
197+
198+
/**
199+
* Wrapped data, contains the method object and the method is static method or not.<br>
200+
* The goal to store the boolean value in field staticMethod is reduce the check times: check once, use many times.<br>
201+
* The goal to store the exception in field nonStaticException is reduce the cost of new Exception(): create once, use many times.
202+
*/
203+
private static class MethodWrappedInfo {
204+
private Method method;
205+
private boolean staticMethod;
206+
private ConfigurationException nonStaticEx;
207+
208+
MethodWrappedInfo(Method method, boolean staticMethod, ConfigurationException nonStaticEx) {
209+
this.method = method;
210+
this.staticMethod = staticMethod;
211+
this.nonStaticEx = nonStaticEx;
212+
}
213+
214+
Method getMethod() {
215+
return method;
216+
}
217+
218+
boolean isStaticMethod() {
219+
return staticMethod;
220+
}
221+
222+
ConfigurationException getNonStaticEx() {
223+
return nonStaticEx;
224+
}
225+
}
138226
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.owasp.esapi.util;
2+
3+
import org.openjdk.jmh.annotations.*;
4+
import org.openjdk.jmh.infra.Blackhole;
5+
6+
import javax.crypto.NullCipher;
7+
8+
@State(Scope.Benchmark)
9+
@Fork(value = 3)
10+
@Warmup(iterations = 5, time = 1)
11+
@Measurement(iterations = 10, time = 1)
12+
public class ObjFactoryBenchmark {
13+
14+
@Benchmark
15+
public void benchmark1(Blackhole hole) {
16+
NullCipher nullCipher = ObjFactory.make("javax.crypto.NullCipher", "NullCipher");
17+
hole.consume(nullCipher);
18+
}
19+
20+
@Benchmark
21+
public void benchmark2(Blackhole hole) {
22+
String key = ObjFactory.make("java.lang.String", "testMakeNotASubclass");
23+
hole.consume(key);
24+
}
25+
26+
}

0 commit comments

Comments
 (0)