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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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:

1
2
3
4
5
<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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button