Use a dynamically generated class instead of a reflection Proxy in NonClosableConnection

This commit is contained in:
Luck 2020-03-31 14:29:09 +01:00
parent 2028d65579
commit 9f0fe9e0cb
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B

View File

@ -25,15 +25,45 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.sql.Connection;
import java.sql.SQLException;
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.isFinal;
import static net.bytebuddy.matcher.ElementMatchers.not;
/**
* Represents a connection which cannot be closed using the standard {@link #close()} method.
* A wrapper around a {@link Connection} which blocks usage of the default {@link #close()} method.
*/
public interface NonClosableConnection extends Connection {
public abstract class NonClosableConnection implements Connection {
private static final MethodHandle CONSTRUCTOR;
static {
// construct an implementation of NonClosableConnection
Class<? extends NonClosableConnection> implClass = new ByteBuddy()
.subclass(NonClosableConnection.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING)
.name(NonClosableConnection.class.getName() + "Impl")
.method(not(isFinal()).and(not(isDeclaredBy(Object.class))))
.intercept(MethodDelegation.toField("delegate"))
.make()
.load(NonClosableConnection.class.getClassLoader())
.getLoaded();
try {
CONSTRUCTOR = MethodHandles.publicLookup().in(implClass)
.findConstructor(implClass, MethodType.methodType(void.class, Connection.class))
.asType(MethodType.methodType(NonClosableConnection.class, Connection.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* Creates a {@link NonClosableConnection} that delegates calls to the given {@link Connection}.
@ -42,40 +72,42 @@ public interface NonClosableConnection extends Connection {
* @return a non closable connection
*/
static NonClosableConnection wrap(Connection connection) {
return (NonClosableConnection) Proxy.newProxyInstance(
NonClosableConnection.class.getClassLoader(),
new Class[]{NonClosableConnection.class},
new Handler(connection)
);
try {
return (NonClosableConnection) CONSTRUCTOR.invokeExact(connection);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
protected final Connection delegate;
protected NonClosableConnection(Connection delegate) {
this.delegate = delegate;
}
/**
* Actually {@link #close() closes} the underlying connection.
*/
void shutdown();
public final void shutdown() throws SQLException {
this.delegate.close();
}
final class Handler implements InvocationHandler {
private final Connection connection;
@Override
public final void close() throws SQLException {
// do nothing
}
Handler(Connection connection) {
this.connection = connection;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// block calls directly to #close
if (method.getName().equals("close")) {
return null;
}
// proxy calls to #shutdown to the real #close method
if (method.getName().equals("shutdown")) {
this.connection.close();
return null;
}
// delegate all other calls
return method.invoke(this.connection, args);
@Override
public final boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface);
}
@SuppressWarnings("unchecked")
@Override
public final <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this.delegate)) {
return (T) this.delegate;
}
return this.delegate.unwrap(iface);
}
}