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; package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import java.lang.reflect.InvocationHandler; import net.bytebuddy.ByteBuddy;
import java.lang.reflect.Method; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import java.lang.reflect.Proxy; 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.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}. * 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 * @return a non closable connection
*/ */
static NonClosableConnection wrap(Connection connection) { static NonClosableConnection wrap(Connection connection) {
return (NonClosableConnection) Proxy.newProxyInstance( try {
NonClosableConnection.class.getClassLoader(), return (NonClosableConnection) CONSTRUCTOR.invokeExact(connection);
new Class[]{NonClosableConnection.class}, } catch (Throwable e) {
new Handler(connection) throw new RuntimeException(e);
); }
}
protected final Connection delegate;
protected NonClosableConnection(Connection delegate) {
this.delegate = delegate;
} }
/** /**
* Actually {@link #close() closes} the underlying connection. * 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;
Handler(Connection connection) {
this.connection = connection;
} }
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { public final void close() throws SQLException {
// block calls directly to #close // do nothing
if (method.getName().equals("close")) {
return null;
} }
// proxy calls to #shutdown to the real #close method @Override
if (method.getName().equals("shutdown")) { public final boolean isWrapperFor(Class<?> iface) throws SQLException {
this.connection.close(); return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface);
return null;
} }
// delegate all other calls @SuppressWarnings("unchecked")
return method.invoke(this.connection, args); @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);
} }
} }