Enterprise Java

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:

flexypoolconnectionproxydecorator

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.

Vlad Mihalcea

Vlad Mihalcea is a software architect passionate about software integration, high scalability and concurrency challenges.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button