JdbcDriverClassLoader

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 動的にJDBCのドライバを切替えるためのクラスローダ。
 * TomcatのWebappClassLoaderを流用。
 * JDBCドライバ用というわけではなく、任意のjarの読み込みに使えるはず。
 */
public class JdbcDriverClassLoader extends URLClassLoader {

    /**
     * このクラスローダでロードしたクラス
     */
    private List loadedClass = new ArrayList();

    public JdbcDriverClassLoader(URL[] aurl, ClassLoader classloader, URLStreamHandlerFactory urlstreamhandlerfactory) {
        super(aurl, classloader, urlstreamhandlerfactory);
    }

    public JdbcDriverClassLoader(URL[] aurl, ClassLoader classloader) { 
        super(aurl, classloader);
    }

    public JdbcDriverClassLoader(URL[] aurl) {
        super(aurl);
    }

    protected Class findClass(String name) throws ClassNotFoundException {
        // ロードしたクラスをリストに追加
        Class ret = super.findClass(name);
        if (ret != null) {
            if (ret.getClassLoader() == this) {
                if (!loadedClass.contains(ret)) {
                    loadedClass.add(ret);
                }
            }
        }
        return ret;
    }

    /**
     * ロードしたクラスのstaticフィールドにnullを設定
     */
    private void clearReferences() {
        // よくわからないが、フィールドを参照した時にクラスがロードされることがある
        // クラスのロードは事前にすべて済ませる
        for (int i = 0; i < loadedClass.size(); i++) {
            loadFieldReferenceClass((Class) loadedClass.get(i));
        }
        // クラスのstaticフィールドにnullを設定する
        for (Iterator i = loadedClass.iterator(); i.hasNext(); ) {
            nullInstance((Class) i.next());
        }
    }

    private void nullInstance(Class c) {
        try {
            Field[] fields = c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                int mods = field.getModifiers();
                // プリミティブであればオブジェクトの参照は有り得ないので無視
                if (field.getType().isPrimitive()
                        || (field.getName().indexOf("$") != -1)) {
                    continue;
                }
                // staticでなければインスタンスと一緒に消えるので無視
                if (!Modifier.isStatic(mods)) {
                    continue;
                }
                try {
                    field.setAccessible(true);
                    if (Modifier.isFinal(mods)) {
                        // finalであれば値を変更できないので、格納されている値のクラスを再帰する
                        if (!((field.getType().getName().startsWith("java."))
                                || (field.getType().getName().startsWith("javax.")))) {
                            nullInstance(field.get(null));
                        }
                    } else {
                        // finalでなければnullを設定する
                        field.set(null, null);
                    }
                } catch (Throwable t) {
                }
            }
        } catch (Throwable t) {
        }
    }

    /**
     * クラスのstaticフィールドを参照して必要なクラスをロードする
     * @param c
     */
    private void loadFieldReferenceClass(Class c) {
        try {
            Field[] fields = c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                if(Modifier.isStatic(fields[i].getModifiers())) {
                    fields[i].get(null);
                    return;
                }
            }
        } catch(Throwable t) {
            // Ignore
        }
    }

    /**
     * オブジェクトのフィールドのうち、このクラスローダでロードしたクラスのインスタンスが設定されているものをnullに設定する
     * @param instance
     */
    protected void nullInstance(Object instance) {
        if (instance == null) {
            return;
        }
        Field[] fields = instance.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            int mods = field.getModifiers();
            // プリミティブであればオブジェクトの参照は有り得ないので無視
            if (field.getType().isPrimitive()
                    || (field.getName().indexOf("$") != -1)) {
                continue;
            }
            try {
                field.setAccessible(true);
                // staticかつfinalのフィールドは無限ループにハマる可能性があるので無視(nullInstance(Class)を参照)
                if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
                    // Doing something recursively is too risky
                    continue;
                } else {
                    Object value = field.get(instance);
                    if (null != value) {
                        Class valueClass = value.getClass();
                        if (!loadedByThisOrChild(valueClass)) {
                        } else {
                            field.set(instance, null);
                        }
                    }
                }
            } catch (Throwable t) {
            }
        }
    }

    protected boolean loadedByThisOrChild(Class clazz)
    {
        boolean result = false;
        for (ClassLoader classLoader = clazz.getClassLoader();
                null != classLoader; classLoader = classLoader.getParent()) {
            if (classLoader.equals(this)) {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * DriverManagerからロードしたJDBCドライバを削除する。
     * DriverManagerは、DriverクラスのクラスローダとderegisterDriverメソッドの呼び出し元のクラスローダが一致していないとエラーを起こす。
     * 単純に{@link java.sql.DriverManager#deregisterDriver(java.sql.Driver)}を呼び出すと、このクラス自身はシステムクラスローダでロードされているためNG。
     * 単純にJdbcLeakPreventionクラスをロードして使用しても、クラスローダの依存関係によりJdbcLeakPreventionクラスもシステムクラスローダでロードされているためNG。
     * 無理やり自分自身でJdbcLeakPreventionクラスをロードするため、リソースとしてJdbcLeakPreventionクラスのバイナリを読み込み、自分の管理下でクラスを作成して使用する。
     */
    private void deregisterDriver() {
        InputStream is = getResourceAsStream(
        "JdbcLeakPrevention.class");
        // We know roughly how big the class will be (~ 1K) so allow 2k as a
        // starting point
        byte[] classBytes = new byte[2048];
        int offset = 0;
        try {
            int read = is.read(classBytes, offset, classBytes.length-offset);
            while (read > -1) {
                offset += read;
                if (offset == classBytes.length) {
                    // Buffer full - double size
                    byte[] tmp = new byte[classBytes.length * 2];
                    System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
                    classBytes = tmp;
                }
                read = is.read(classBytes, offset, classBytes.length-offset);
            }
            Class lpClass =
                defineClass("JdbcLeakPrevention",
                    classBytes, 0, offset);
            Object obj = lpClass.newInstance();
            obj.getClass().getMethod(
                    "clearJdbcDriverRegistrations", null).invoke(obj, null);
        } catch (Exception e) {
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException ioe) {
                }
            }
        }
    }

    /**
     * 自分がロードしたクラスの参照を削除し、自分がガベージコレクションの対象になるようにする
     */
    public void clear() {
        clearReferences();
        deregisterDriver();
    }

}
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

/**
 * JDBCドライバを破棄するためのクラス
 * 使用方法については{@link JdbcDriverClassLoader#deregisterDriver()}を参照
 * 適当なパッケージ名を付けて使用すること
 */
public class JdbcLeakPrevention {

    public void clearJdbcDriverRegistrations() {
        for (Enumeration e = DriverManager.getDrivers(); e.hasMoreElements(); ) {
            try {
                DriverManager.deregisterDriver((Driver) e.nextElement());
            } catch (SQLException e1) {
            }
        }
    }

}