Java 中的动态代理是一个简单但非常有用的特性。
通常我们创建一个接口实现,然后涉及到编译。使用动态代理,我们可以在运行时实现接口列表。将创建代理对象,当在该代理实例上调用方法时,调用的方法将转发到指定的调用处理程序。
这可以有多种用途。一个常见的用例是 java 接口,我们可以使用代理并拦截对所调用方法的调用。
假设我们有一个 JDBC 连接池,并且我们想要一个类似于千分尺计数器的东西。获得连接后,计数器会增加,因此我们可以确定应用程序中获得连接的速率。
我们首先使用 Docker Compose 为 Postgresql 添加一个 docker 容器。
version: '3.1' services: postgres: image: postgres restart: always environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432 |
以下命令运行
docker compose up |
让我们将我们的依赖项添加到我们的 Java 项目中:
<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> |
代码:
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"); } } } } |
Java的DataSource接口中有getConnection :
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; ... } |
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)); } } |
重新审视我们的mai方法:
。。。。 try(HikariDataSource ds = new HikariDataSource(config)) { DataSource dataSource = proxy(ds); try(Connection connection = dataSource.getConnection()) { System.out.println("Should be printed after the proxy"); } } |
我们用动态代理包装了 HikariDataSource,如果我们运行该程序,我们应该会看到以下输出:
Called before actual method Should be printed after the proxy |
我们创建了一个具有 DataSource 接口的代理:通过创建代理,我们提供了一个调用处理程序,该处理程序应在调用 getConnection 方法之前打印一条消息。getConnection 将由我们在 InvocationHandler 上指定的 DataSource 接口的实际实现调用。
可以在GitHub 上找到源代码。