001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.core;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.util.Collections;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.Set;
025:
026: import org.springframework.util.Assert;
027: import org.springframework.util.FileCopyUtils;
028:
029: /**
030: * <code>ClassLoader</code> that does <i>not</i> always delegate to the
031: * parent loader, as normal class loaders do. This enables, for example,
032: * instrumentation to be forced in the overriding ClassLoader, or a
033: * "throwaway" class loading behavior, where selected classes are
034: * temporarily loaded in the overriding ClassLoader, in order to load
035: * an instrumented version of the class in the parent ClassLoader later on.
036: *
037: * @author Rod Johnson
038: * @author Juergen Hoeller
039: * @since 2.0.1
040: */
041: public class OverridingClassLoader extends ClassLoader {
042:
043: /** Packages that are excluded by default */
044: public static final String[] DEFAULT_EXCLUDED_PACKAGES = new String[] {
045: "java.", "javax.", "sun." };
046:
047: private static final String CLASS_FILE_SUFFIX = ".class";
048:
049: private final Set excludedPackages = Collections
050: .synchronizedSet(new HashSet());
051:
052: private final Set excludedClasses = Collections
053: .synchronizedSet(new HashSet());
054:
055: /**
056: * Create a new OverridingClassLoader for the given class loader.
057: * @param parent the ClassLoader to build an overriding ClassLoader for
058: */
059: public OverridingClassLoader(ClassLoader parent) {
060: super (parent);
061: for (int i = 0; i < DEFAULT_EXCLUDED_PACKAGES.length; i++) {
062: this .excludedPackages.add(DEFAULT_EXCLUDED_PACKAGES[i]);
063: }
064: }
065:
066: /**
067: * Add a package name to exclude from overriding.
068: * <p>Any class whose fully-qualified name starts with the name registered
069: * here will be handled by the parent ClassLoader in the usual fashion.
070: * @param packageName the package name to exclude
071: */
072: public void excludePackage(String packageName) {
073: Assert.notNull(packageName, "Package name must not be null");
074: this .excludedPackages.add(packageName);
075: }
076:
077: /**
078: * Add a class name to exclude from overriding.
079: * <p>Any class name registered here will be handled by
080: * the parent ClassLoader in the usual fashion.
081: * @param className the class name to exclude
082: */
083: public void excludeClass(String className) {
084: Assert.notNull(className, "Class name must not be null");
085: this .excludedClasses.add(className);
086: }
087:
088: protected Class loadClass(String name, boolean resolve)
089: throws ClassNotFoundException {
090: Class result = null;
091: if (isEligibleForOverriding(name)) {
092: result = loadClassForOverriding(name);
093: }
094: if (result != null) {
095: if (resolve) {
096: resolveClass(result);
097: }
098: return result;
099: } else {
100: return super .loadClass(name, resolve);
101: }
102: }
103:
104: /**
105: * Determine whether the specified class is eligible for overriding
106: * by this class loader.
107: * <p>The default implementation checks against excluded packages and classes.
108: * @param className the class name to check
109: * @return whether the specified class is eligible
110: * @see #excludePackage
111: * @see #excludeClass
112: */
113: protected boolean isEligibleForOverriding(String className) {
114: if (this .excludedClasses.contains(className)) {
115: return false;
116: }
117: for (Iterator it = this .excludedPackages.iterator(); it
118: .hasNext();) {
119: String packageName = (String) it.next();
120: if (className.startsWith(packageName)) {
121: return false;
122: }
123: }
124: return true;
125: }
126:
127: /**
128: * Load the specified class for overriding purposes in this ClassLoader.
129: * <p>The default implementation delegates to {@link #findLoadedClass},
130: * {@link #loadBytesForClass} and {@link #defineClass}.
131: * @param name the name of the class
132: * @return the Class object, or <code>null</code> if no class defined for that name
133: * @throws ClassNotFoundException if the class for the given name couldn't be loaded
134: */
135: protected Class loadClassForOverriding(String name)
136: throws ClassNotFoundException {
137: Class result = findLoadedClass(name);
138: if (result == null) {
139: byte[] bytes = loadBytesForClass(name);
140: if (bytes != null) {
141: result = defineClass(name, bytes, 0, bytes.length);
142: }
143: }
144: return result;
145: }
146:
147: /**
148: * Load the defining bytes for the given class,
149: * to be turned into a Class object through a {@link #defineClass} call.
150: * <p>The default implementation delegates to {@link #openStreamForClass}
151: * and {@link #transformIfNecessary}.
152: * @param name the name of the class
153: * @return the byte content (with transformers already applied),
154: * or <code>null</code> if no class defined for that name
155: * @throws ClassNotFoundException if the class for the given name couldn't be loaded
156: */
157: protected byte[] loadBytesForClass(String name)
158: throws ClassNotFoundException {
159: InputStream is = openStreamForClass(name);
160: if (is == null) {
161: return null;
162: }
163: try {
164: // Load the raw bytes.
165: byte[] bytes = FileCopyUtils.copyToByteArray(is);
166: // Transform if necessary and use the potentially transformed bytes.
167: return transformIfNecessary(name, bytes);
168: } catch (IOException ex) {
169: throw new ClassNotFoundException(
170: "Cannot load resource for class [" + name + "]", ex);
171: }
172: }
173:
174: /**
175: * Open an InputStream for the specified class.
176: * <p>The default implementation loads a standard class file through
177: * the parent ClassLoader's <code>getResourceAsStream</code> method.
178: * @param name the name of the class
179: * @return the InputStream containing the byte code for the specified class
180: */
181: protected InputStream openStreamForClass(String name) {
182: String internalName = name.replace('.', '/')
183: + CLASS_FILE_SUFFIX;
184: return getParent().getResourceAsStream(internalName);
185: }
186:
187: /**
188: * Transformation hook to be implemented by subclasses.
189: * <p>The default implementation simply returns the given bytes as-is.
190: * @param name the fully-qualified name of the class being transformed
191: * @param bytes the raw bytes of the class
192: * @return the transformed bytes (never <code>null</code>;
193: * same as the input bytes if the transformation produced no changes)
194: */
195: protected byte[] transformIfNecessary(String name, byte[] bytes) {
196: return bytes;
197: }
198:
199: }
|