曹工说Spring Boot源码(19)-- Spring 带给我们的工具利器,创建代理不用愁(ProxyFactory) (4)

如果要让它实施代理的工作,可以这样做:

@Test public void createJdkDynamicProxyManual() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Performer performer = new Performer(); MyCustomInvocationHandler myCustomInvocationHandler = new MyCustomInvocationHandler(performer); ClassLoader loader = Thread.currentThread().getContextClassLoader(); Object generatedProxy = Proxy.newProxyInstance(loader, new Class[]{Perform.class}, myCustomInvocationHandler); ((Perform)generatedProxy).sing(); } public static class MyCustomInvocationHandler implements InvocationHandler { Performer performer; public MyCustomInvocationHandler(Performer performer) { this.performer = performer; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是一个称职的代理"); return method.invoke(performer,args); } }

上面这个代码,就没问题了。会输出如下:

我是一个称职的代理

男孩在唱歌

jdk动态代理实现思路的案例代码

我们上面说了怎么样正确地实现代理的思路,就是要把target/原始bean,在new invocationHandler的时候,传递给它,后续在invoke里再使用。我们看看框架对invocationHandler的其他实现,是怎么做的吧?

我在project里找了下InvocationHandler的实现类,发现了jdbc中的一个实现类。

org.springframework.jdbc.datasource.ConnectionProxy public interface ConnectionProxy extends Connection { /** * Return the target Connection of this proxy. * <p>This will typically be the native driver Connection * or a wrapper from a connection pool. * @return the underlying Connection (never {@code null}) */ Connection getTargetConnection(); }

这个是Connection的子接口,通过这个接口,获取真正的数据库连接。我们看看其代理实现:

org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy#getConnection(java.lang.String, java.lang.String) public Connection getConnection(String username, String password) throws SQLException { return (Connection) Proxy.newProxyInstance( ConnectionProxy.class.getClassLoader(), new Class[] {ConnectionProxy.class}, new LazyConnectionInvocationHandler(username, password)); }

这个代理实现,主要是延迟获取数据库连接,等到使用的时候,才去获取连接;而不是启动时,即建立连接池。

private class LazyConnectionInvocationHandler implements InvocationHandler { private String username; private String password; private Boolean readOnly = Boolean.FALSE; private Integer transactionIsolation; private Boolean autoCommit; private boolean closed = false; private Connection target; public LazyConnectionInvocationHandler() { this.autoCommit = defaultAutoCommit(); this.transactionIsolation = defaultTransactionIsolation(); } public LazyConnectionInvocationHandler(String username, String password) { this(); this.username = username; this.password = password; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on ConnectionProxy interface coming in... if (method.getName().equals("equals")) { // We must avoid fetching a target Connection for "equals". // Only consider equal when proxies are identical. return (proxy == args[0]); } ... else if (method.getName().equals("getTargetConnection")) { // Handle getTargetConnection method: return underlying connection. return getTargetConnection(method); } ... }

这里呢,如果方法为getTargetConnection,则调用了以下方法:

private Connection getTargetConnection(Method operation) throws SQLException { if (this.target == null) { // No target Connection held -> fetch one. if (logger.isDebugEnabled()) { logger.debug("Connecting to database for operation '" + operation.getName() + "'"); } // 通过用户名,密码去获取数据库连接 this.target = (this.username != null) ? getTargetDataSource().getConnection(this.username, this.password) : getTargetDataSource().getConnection(); // If we still lack default connection properties, check them now. checkDefaultConnectionProperties(this.target); // Apply kept transaction settings, if any. if (this.readOnly) { try { this.target.setReadOnly(this.readOnly); } catch (Exception ex) { // "read-only not supported" -> ignore, it's just a hint anyway logger.debug("Could not set JDBC Connection read-only", ex); } } if (this.transactionIsolation != null && !this.transactionIsolation.equals(defaultTransactionIsolation())) { this.target.setTransactionIsolation(this.transactionIsolation); } if (this.autoCommit != null && this.autoCommit != this.target.getAutoCommit()) { this.target.setAutoCommit(this.autoCommit); } } return this.target; } }

大家从上面代码,可以看到,是有通过用户名密码去获取数据库连接的。

所以,看来,正确的实现代理的思路就是,在构造proxy的时候,把你需要的东西,都通过构造函数或setter,传递给invocationHandler。然后再在invoke方法内去使用这些东西,来完成你的逻辑。

Spring提供给我们的强大工具类:ProxyFactory

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywjjf.html