@@ -43,6 +43,7 @@ public final class ClassUtils {
4343 */
4444 private static final Logger LOGGER = LoggerFactory .getLogger (ClassUtils .class );
4545
46+ private static final ThreadLocal <ClassLoader > ADDITIONAL_CLASS_LOADER = new ThreadLocal <>();
4647
4748 /**
4849 * SHIRO-767: add a map to mapping primitive data type
@@ -75,13 +76,24 @@ protected ClassLoader doGetClassLoader() throws Throwable {
7576 /**
7677 * @since 1.0
7778 */
78- private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor () {
79+ private static final ClassLoaderAccessor CLASS_LANG_CL_ACCESSOR = new ExceptionIgnoringAccessor () {
7980 @ Override
8081 protected ClassLoader doGetClassLoader () throws Throwable {
8182 return ClassUtils .class .getClassLoader ();
8283 }
8384 };
8485
86+ /**
87+ * @since 2.0.4
88+ */
89+ private static final ClassLoaderAccessor ADDITIONAL_CL_ACCESSOR = new ExceptionIgnoringAccessor () {
90+ @ Override
91+ protected ClassLoader doGetClassLoader () throws Throwable {
92+ ClassLoader cl = ADDITIONAL_CLASS_LOADER .get ();
93+ return cl != null ? cl : ClassUtils .class .getClassLoader ();
94+ }
95+ };
96+
8597 /**
8698 * @since 1.0
8799 */
@@ -117,7 +129,15 @@ public static InputStream getResourceAsStream(String name) {
117129 LOGGER .trace ("Resource [" + name + "] was not found via the thread context ClassLoader. Trying the "
118130 + "current ClassLoader..." );
119131 }
120- is = CLASS_CL_ACCESSOR .getResourceStream (name );
132+ is = CLASS_LANG_CL_ACCESSOR .getResourceStream (name );
133+ }
134+
135+ if (is == null ) {
136+ if (LOGGER .isTraceEnabled ()) {
137+ LOGGER .trace ("Resource [" + name + "] was not found via the org.apache.shiro.lang ClassLoader. Trying the "
138+ + "additionally set ClassLoader..." );
139+ }
140+ is = ADDITIONAL_CL_ACCESSOR .getResourceStream (name );
121141 }
122142
123143 if (is == null ) {
@@ -157,7 +177,15 @@ public static <T> Class<T> forName(String fqcn) throws UnknownClassException {
157177 LOGGER .trace ("Unable to load class named [" + fqcn
158178 + "] from the thread context ClassLoader. Trying the current ClassLoader..." );
159179 }
160- clazz = CLASS_CL_ACCESSOR .loadClass (fqcn );
180+ clazz = CLASS_LANG_CL_ACCESSOR .loadClass (fqcn );
181+ }
182+
183+ if (clazz == null ) {
184+ if (LOGGER .isTraceEnabled ()) {
185+ LOGGER .trace ("Unable to load class named [" + fqcn
186+ + "] from the org.apache.shiro.lang ClassLoader. Trying the additionally set ClassLoader..." );
187+ }
188+ clazz = ADDITIONAL_CL_ACCESSOR .loadClass (fqcn );
161189 }
162190
163191 if (clazz == null ) {
@@ -259,6 +287,27 @@ public static List<Method> getAnnotatedMethods(final Class<?> type, final Class<
259287 return methods ;
260288 }
261289
290+ /**
291+ * Sets additional ClassLoader for {@link #getResourceAsStream(String)} and {@link #forName(String)} to use
292+ * It is used in addition to the thread context class loader and the system class loader.
293+
294+ * @param classLoader class loader to use
295+ * @since 2.0.4
296+ */
297+ public static void setAdditionalClassLoader (ClassLoader classLoader ) {
298+ ADDITIONAL_CLASS_LOADER .set (classLoader );
299+ }
300+
301+ /**
302+ * Removes the additional ClassLoader set by {@link #setAdditionalClassLoader(ClassLoader)}.
303+ * This must be called to avoid memory leaks.
304+ *
305+ * @since 2.0.4
306+ */
307+ public static void removeAdditionalClassLoader () {
308+ ADDITIONAL_CLASS_LOADER .remove ();
309+ }
310+
262311 /**
263312 * @since 1.0
264313 */
0 commit comments