Created
September 4, 2015 04:30
-
-
Save brettwooldridge/7564827e45d67e1dc8dd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class InterceptorDataSource implements InvocationHandler { | |
private final DataSource delegate; | |
private InterceptorDataSource(final DataSource delegate) { | |
this.delegate = delegate; | |
} | |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |
return (method.getName().equals("getConnection")) ? getConnection() : method.invoke(delegate, args); | |
} | |
protected Connection getConnection() throws SQLException { | |
return InterceptorConnection.wrapInterceptor(delegate.getConnection()); | |
} | |
public static DataSource wrapInterceptor(DataSource delegate) { | |
InterceptorDataSource instance = new InterceptorDataSource(delegate); | |
return (DataSource) Proxy.newProxyInstance(InterceptorDataSource.class.getClassLoader(), new Class[] { DataSource.class }, instance); | |
} | |
} | |
public class InterceptorConnection implements InvocationHandler | |
{ | |
private final Connection delegate; | |
private InterceptorConnection(final Connection delegate) { | |
this.delegate = delegate; | |
} | |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |
switch (method.getName()) { | |
case "createStatement": { | |
Statement stmt = (Statement) method.invoke(delegate, args); | |
return InterceptorStatement.wrapInterceptor(stmt, Statement.class); | |
} | |
case "prepareStatement": { | |
// First argument is always SQL | |
PreparedStatement stmt = (PreparedStatement) method.invoke(delegate, args); | |
return InterceptorStatement.wrapInterceptor(stmt, (String) args[0], PreparedStatement.class); | |
} | |
default: { | |
return method.invoke(delegate, args); | |
} | |
} | |
} | |
public static Connection wrapInterceptor(Connection delegate) { | |
InterceptorConnection instance = new InterceptorConnection(delegate); | |
return (Connection) Proxy.newProxyInstance(InterceptorConnection.class.getClassLoader(), new Class[] { Connection.class }, instance); | |
} | |
} | |
public class InterceptorStatement<T extends Statement> implements InvocationHandler | |
{ | |
private final Logger LOGGER = LoggerFactory.getLogger(InterceptorStatement.class); | |
private final T delegate; | |
private final String sql; | |
private InterceptorStatement(final T delegate) { | |
this.delegate = delegate; | |
this.sql = null; | |
} | |
private InterceptorStatement(final T delegate, final String sql) { | |
this.delegate = delegate; | |
this.sql = sql; | |
} | |
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { | |
if (method.getName().startsWith("exec")) { | |
final long startTime = System.nanoTime(); | |
try { | |
return method.invoke(delegate, args); | |
} | |
finally { | |
if (LOGGER.isDebugEnabled()) { | |
LOGGER.debug("Execution of SQL ({}) took {}", (sql != null ? sql : args[0]), TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)); | |
} | |
} | |
} | |
return method.invoke(delegate, args); | |
} | |
@SuppressWarnings("unchecked") | |
public static <T extends Statement> T wrapInterceptor(final T delegate, final Class<? extends Statement> clazz) { | |
InterceptorStatement<T> instance = new InterceptorStatement<>(delegate); | |
return (T) Proxy.newProxyInstance(instance.getClass().getClassLoader(), new Class[] { clazz }, instance); | |
} | |
@SuppressWarnings("unchecked") | |
public static <T extends Statement> T wrapInterceptor(final T delegate, String sql, final Class<? extends Statement> clazz) { | |
InterceptorStatement<T> instance = new InterceptorStatement<>(delegate, sql); | |
return (T) Proxy.newProxyInstance(instance.getClass().getClassLoader(), new Class[] { clazz }, instance); | |
} | |
} |
If you want it a little cleaner, you can:
- delete constructor at line 61 (61-65)
- delete wrapInterceptor at line 88 (88-93)
- then change call at line 35 to pass
null
as the second argument
But now everything is executed via reflection and thus is theoretically a bit slower and generates more pressure on PermGen / Metaspace. The JVM will do reflection calls via JNI but if these calls happen a lot the JVM will generate classes/byte code for it (by default after 15 calls I think). That process is called inflation. So you should keep an eye on your PermGen/MetaSpace when using the above proxies and maybe adjust inflation settings of your JVM.
I think it is still superior to implement DataSource, Connection, Statement once using the delegation pattern and then you can use these "forwarding" classes to selectively introduce new behavior.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
๐ Thanks, you made my day! ๐