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));
}
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;
}
if (!Modifier.isStatic(mods)) {
continue;
}
try {
field.setAccessible(true);
if (Modifier.isFinal(mods)) {
if (!((field.getType().getName().startsWith("java."))
|| (field.getType().getName().startsWith("javax.")))) {
nullInstance(field.get(null));
}
} else {
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) {
}
}
オブジェクトのフィールドのうち、このクラスローダでロードしたクラスのインスタンスが設定されているものを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);
if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
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");
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) {
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) {
}
}
}
}