Skip to content

Instantly share code, notes, and snippets.

@brettwooldridge
Created September 4, 2015 04:30
Show Gist options
  • Save brettwooldridge/7564827e45d67e1dc8dd to your computer and use it in GitHub Desktop.
Save brettwooldridge/7564827e45d67e1dc8dd to your computer and use it in GitHub Desktop.
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);
}
}
@brettwooldridge
Copy link
Author

This is a pretty crude fast-coding of something that intercepts DataSourceConnectionStatement for the purposes of logging Statement execution time and SQL.

Crude as it is, the HikariCP benchmark shows that with this code, you can create and execute ~20k statements per millisecond -- against stubs of course. Still, for all intents and purposes the overhead is transparent in comparison to the SQL being executed or other activities of the interceptor code itself.

Usage like this:

...
DataSource wrappedDS = InterceptorDataSource.wrapInterceptor(realDS);

HikariConfig config = new HikariConfig();
config.setDataSource(wrappedDS);
...

Other variations are having a DataSource that extends HikariDataSource, but then follows the dynamic proxy pattern above for Connection and Statements, etc.

@eirslett
Copy link

eirslett commented Sep 4, 2015

👏 Thanks, you made my day! 🌟

@brettwooldridge
Copy link
Author

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

@jnehlmeier
Copy link

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