How does FlexyPool support both Connection proxies and decorators
Proxies
FlexyPool monitors connection pool usage and so it needs to intercept the connection close method call.
For simplicity sake, the first version was relying on dynamic proxies for this purpose:
private static class ConnectionInvocationHandler implements InvocationHandler { public static final String CLOSE_METHOD_NAME = "close"; private final Connection target; private final ConnectionCallback callback; public ConnectionInvocationHandler( Connection target, ConnectionCallback callback) { this.target = target; this.callback = callback; } @Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { if (CLOSE_METHOD_NAME.equals(method.getName())) { callback.close(); } return method.invoke(target, args); } }
As straightforward as it may be, a proxy invocation is slower than a decorator, which calls the target method using a direct invocation.
Because all connection pools use proxies anyway, adding another proxy layer only adds more call-time overhead and so now FlexyPool supports connection decorators as well.
Decorators
A ConnectionDecorator wraps an underlying database connection, delegating all calls to the actual object instance. Just like its proxy counterpart, oOnly the close method is doing any extra logic:
public class ConnectionDecorator implements Connection { private final Connection target; private final ConnectionCallback callback; public ConnectionDecorator( Connection target, ConnectionCallback callback) { this.target = target; this.callback = callback; } public Connection getTarget() { return target; } public ConnectionCallback getCallback() { return callback; } @Override public Statement createStatement() throws SQLException { return target.createStatement(); } @Override public void close() throws SQLException { callback.close(); target.close(); } /** * More methods omitted for brevity sake */ public void setSchema(String schema) throws SQLException { ReflectionUtils.invoke( target, ReflectionUtils.getMethod( target, "setSchema", String.class ), schema ); } public String getSchema() throws SQLException { return ReflectionUtils.invoke( target, ReflectionUtils.getMethod( target, "getSchema" ) ); } public void abort(Executor executor) throws SQLException { ReflectionUtils.invoke( target, ReflectionUtils.getMethod( target, "abort", Executor.class ), executor ); } public void setNetworkTimeout( Executor executor, int milliseconds) throws SQLException { ReflectionUtils.invoke( target, ReflectionUtils.getMethod( target, "setNetworkTimeout", Executor.class, int.class ), executor, milliseconds ); } public int getNetworkTimeout() throws SQLException { return (Integer) ReflectionUtils.invoke( target, ReflectionUtils.getMethod( target, "getNetworkTimeout" ) ); } }
As you might have noticed already, some methods use Java Reflection instead of a direct method call:
These methods have been added to Java 1.7 and a direct call will fail when compiling the project with Java 1.6. Because Java 1.6 is the minimum requirement for most FlexyPool modules, these methods forward the incoming method call through a Java reflection invocation. Omitting these methods is not optional either because on a 1.7 JVM, the Connection decorator will not have these methods and a class loading error will be thrown.
On projects using at least Java 1.7, FlexyPool also offers the Java7ConnectionDecorator:
public class Java7ConnectionDecorator extends ConnectionDecorator { public Java7ConnectionDecorator( Connection target, ConnectionCallback callback) { super(target, callback); } @Override public void setSchema(String schema) throws SQLException { getTarget().setSchema(schema); } @Override public String getSchema() throws SQLException { return getTarget().getSchema(); } @Override public void abort(Executor executor) throws SQLException { getTarget().abort(executor); } @Override public void setNetworkTimeout( Executor executor, int milliseconds) throws SQLException { getTarget().setNetworkTimeout(executor, milliseconds); } @Override public int getNetworkTimeout() throws SQLException { return getTarget().getNetworkTimeout(); } }
This class is not part of the core library, being included in a separated Java 1.7 compliant module. To make use of it, you need to add the following Maven dependency:
<dependency> <groupId>com.vladmihalcea.flexy-pool</groupId> <artifactId>flexy-pool-core-java7</artifactId> <version>${flexy-pool.version}</version> </dependency>
The service discovery mechanism
From the very beginning, FlexyPool has offered support for configuring the ConnectionProxyFactory instance, so switching to decorators didn’t require any intensive code refactoring.
Prior to release 1.2.4 the default connection provider was the JdkConnectionProxyFactory, which uses dynamic proxies.
Since 1.2.4, FlexyPool uses connection decorators as the default connection intercepting mechanism.
The actual decorator version is resolved at runtime and the loading mechanism is built out of the following components:
The actual connection decorator factory is resolved by the following method:
public ConnectionDecoratorFactory resolve() { int loadingIndex = Integer.MIN_VALUE; ConnectionDecoratorFactory connectionDecoratorFactory = null; Iterator<ConnectionDecoratorFactoryService> connectionDecoratorFactoryServiceIterator = serviceLoader.iterator(); while (connectionDecoratorFactoryServiceIterator.hasNext()) { try { ConnectionDecoratorFactoryService connectionDecoratorFactoryService = connectionDecoratorFactoryServiceIterator.next(); int currentLoadingIndex = connectionDecoratorFactoryService.loadingIndex(); if (currentLoadingIndex > loadingIndex) { ConnectionDecoratorFactory currentConnectionDecoratorFactory = connectionDecoratorFactoryService.load(); if (currentConnectionDecoratorFactory != null) { connectionDecoratorFactory = currentConnectionDecoratorFactory; loadingIndex = currentLoadingIndex; } } } catch (LinkageError e) { LOGGER.info("Couldn't load ConnectionDecoratorFactoryService on the current JVM", e); } } if (connectionDecoratorFactory != null) { return connectionDecoratorFactory; } throw new IllegalStateException("No ConnectionDecoratorFactory could be loaded!"); }
Just like the MetricsFactory, each connection decorator factory has an associated Service Provider. Multiple such Service Providers can be loaded at runtime (the default Java 1.6 connection decorator service or the Java 1.7 one). The selection is done based on the index (latest Java version take precedence) and the current JVM JDBC version support (the Java 1.7 connection decorator won’t be resolved on a Java 1.6 runtime environment).
Conclusion
Decorators incur more configuration overhead than a proxy but if you want to squeeze the last performance drop it’s worth considering the direct method call advantage.
Reference: | How does FlexyPool support both Connection proxies and decorators from our JCG partner Vlad Mihalcea at the Vlad Mihalcea’s Blog blog. |