Java and dynamic Proxies
Dynamic proxies in Java is a simple and very useful feature.
Usually we create an interface implementation and then compilation is involved. With dynamic proxies we can implement a list of interfaces at runtime. A proxy object will be created, when a method is invoked on that proxy instance, the methods invoked will be forwarded to an invocation handler specified.
This can have various usages. A common use case would be for a java interface which we can use a proxy and intercept the calls to the methods invoked.
Supposing we have a JDBC connection pool and we want to have something like a micrometer counter. On getting a connection the counter will increase, thus we can identify the rate of acquiring connections in our application.
We shall use a proxy for that.
Let’s first add a docker container for Postgresql using Docker Compose.
version: '3.1' services: postgres: image: postgres restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432
We can run with the following command
docker compose up
You can find more on Compose on the Developers Essential Guide to Docker Compose.
Let’s add our dependencies to our Java project
<dependencies> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.5.1</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>4.0.3</version> <scope>compile</scope> </dependency> </dependencies>
Now will also add a small snippet.
package com.example.gkatzioura.proxy; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class Application { public static void main(String[] args) throws SQLException { Properties props = new Properties(); props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource"); props.setProperty("dataSource.user", "postgres"); props.setProperty("dataSource.password", "postgres"); props.setProperty("dataSource.databaseName", "postgres"); props.put("dataSource.logWriter", new PrintWriter(System.out)); HikariConfig config = new HikariConfig(props); try(HikariDataSource ds = new HikariDataSource(config)) { try(Connection connection = ds.getConnection()) { System.out.println("Should be printed after the proxy"); } } } }
We can examine the DataSource interface of Java on the source code. We can see the method of interest, getConnection, present.
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; ... }
Instead of creating an interface implementation and having to implement all those methods and then delegate them to the actual DataSource instance, we shall instead use a proxy and add actions only to the method of interest, in our case getConnection.
We shall implement an InvocationHandler.
package com.example.gkatzioura.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import javax.sql.DataSource; public class GetConnectionHandler implements InvocationHandler { private final DataSource dataSource; public GetConnectionHandler(DataSource dataSource) { this.dataSource = dataSource; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getConnection")) { System.out.println("Called before actual method"); } return method.invoke(dataSource, args); } public static DataSource proxy(DataSource dataSource) { return (DataSource) Proxy.newProxyInstance(DataSource.class.getClassLoader(), new Class[]{DataSource.class}, new GetConnectionHandler(dataSource)); } }
Let’s break it down.
The invocation handler will be called for the method of an interface specified. When an interface method is invoked it is our choice how we shall handle it. In our case we shall print a simple message and then we shall execute the corresponding method to our target instance.
Also we have a static factory specified which shall proxy the object implementing the interface of interest. A new proxy instance will be created, it will implement the interfaces provided and the calls towards the proxy instance will be passed to the handler we provided.
Let’s revisit our main method.
package com.example.gkatzioura.proxy; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import static com.example.gkatzioura.proxy.GetConnectionHandler.proxy; public class Application { public static void main(String[] args) throws SQLException { Properties props = new Properties(); props.setProperty("dataSourceClassName", "org.postgresql.ds.PGSimpleDataSource"); props.setProperty("dataSource.user", "postgres"); props.setProperty("dataSource.password", "postgres"); props.setProperty("dataSource.databaseName", "postgres"); props.put("dataSource.logWriter", new PrintWriter(System.out)); HikariConfig config = new HikariConfig(props); try(HikariDataSource ds = new HikariDataSource(config)) { DataSource dataSource = proxy(ds); try(Connection connection = dataSource.getConnection()) { System.out.println("Should be printed after the proxy"); } } } }
We wrapped the HikariDataSource with a Dynamic Proxy and if we run the program we should see the following output.
Called before actual method Should be printed after the proxy
If we break it down, we created a proxy with the DataSource interface. By creating the proxy we provided an invocation handler which shall print a message before the getConnection method is invoked. The getConnection will be invoked by the actual implementation of the DataSource interface that we specified on the InvocationHandler.
You can find the source code on GitHub.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Java and dynamic Proxies Opinions expressed by Java Code Geeks contributors are their own. |