Back port build-time javassist proxy generation from 2.4.x to 2.3.x.

2.3.x
Brett Wooldridge 10 years ago
parent cdfd91d8c8
commit 33a4bbe54a

@ -49,31 +49,31 @@ public final class ProxyFactory
*/
public static IHikariConnectionProxy getProxyConnection(final HikariPool pool, final PoolBagEntry bagEntry, final LeakTask leakTask)
{
// Body is injected by JavassistProxyFactory
return null;
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
static Statement getProxyStatement(final ConnectionProxy connection, final Statement statement)
{
// Body is injected by JavassistProxyFactory
return null;
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
static CallableStatement getProxyCallableStatement(final ConnectionProxy connection, final CallableStatement statement)
{
// Body is injected by JavassistProxyFactory
return null;
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
static PreparedStatement getProxyPreparedStatement(final ConnectionProxy connection, final PreparedStatement statement)
{
// Body is injected by JavassistProxyFactory
return null;
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
static ResultSet getProxyResultSet(final ConnectionProxy connection, final ResultSet resultSet)
{
// Body is injected by JavassistProxyFactory
return null;
// Body is replaced (injected) by JavassistProxyFactory
throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
}
}

@ -14,8 +14,9 @@
* limitations under the License.
*/
package com.zaxxer.hikari.proxy;
package com.zaxxer.hikari.util;
import java.io.File;
import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
@ -23,10 +24,18 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.zaxxer.hikari.proxy.CallableStatementProxy;
import com.zaxxer.hikari.proxy.ConnectionProxy;
import com.zaxxer.hikari.proxy.PreparedStatementProxy;
import com.zaxxer.hikari.proxy.ProxyFactory;
import com.zaxxer.hikari.proxy.ResultSetProxy;
import com.zaxxer.hikari.proxy.StatementProxy;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
@ -35,8 +44,6 @@ import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import com.zaxxer.hikari.util.ClassLoaderUtils;
/**
* This class generates the proxy objects for {@link Connection}, {@link Statement},
* {@link PreparedStatement}, and {@link CallableStatement}. Additionally it injects
@ -47,98 +54,89 @@ import com.zaxxer.hikari.util.ClassLoaderUtils;
*/
public final class JavassistProxyFactory
{
private ClassPool classPool;
private static ClassPool classPool;
private static String outputPrefix;
static {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(JavassistProxyFactory.class.getClassLoader());
public static void main(String... args)
{
outputPrefix = args[0] + File.separator + "target" + File.separator + "classes";
JavassistProxyFactory proxyFactoryFactory = new JavassistProxyFactory();
proxyFactoryFactory.modifyProxyFactory();
classPool = new ClassPool();
classPool.importPackage("java.sql");
classPool.appendClassPath(new LoaderClassPath(JavassistProxyFactory.class.getClassLoader()));
try {
// Cast is not needed for these
String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(Connection.class, ConnectionProxy.class.getName(), methodBody);
generateProxyClass(Statement.class, StatementProxy.class.getName(), methodBody);
generateProxyClass(ResultSet.class, ResultSetProxy.class.getName(), methodBody);
// For these we have to cast the delegate
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class.getName(), methodBody);
generateProxyClass(CallableStatement.class, CallableStatementProxy.class.getName(), methodBody);
modifyProxyFactory();
}
catch (Exception e) {
throw new RuntimeException("Fatal exception during proxy generation", e);
}
finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
throw new RuntimeException(e);
}
}
/**
* Simply invoking this method causes the initialization of this class. All work
* by this class is performed in static initialization.
*/
public static void initialize()
private static void modifyProxyFactory() throws Exception
{
// no-op
}
System.out.println("Generating method bodies for com.zaxxer.hikari.proxy.ProxyFactory");
private JavassistProxyFactory() throws Exception {
classPool = new ClassPool();
classPool.importPackage("java.sql");
classPool.appendClassPath(new LoaderClassPath(this.getClass().getClassLoader()));
// Cast is not needed for these
String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(Connection.class, ConnectionProxy.class, methodBody);
generateProxyClass(Statement.class, StatementProxy.class, methodBody);
generateProxyClass(ResultSet.class, ResultSetProxy.class, methodBody);
// For these we have to cast the delegate
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class, methodBody);
generateProxyClass(CallableStatement.class, CallableStatementProxy.class, methodBody);
}
private void modifyProxyFactory() throws Exception
{
String packageName = JavassistProxyFactory.class.getPackage().getName();
String packageName = ConnectionProxy.class.getPackage().getName();
CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.proxy.ProxyFactory");
for (CtMethod method : proxyCt.getMethods()) {
String methodName = method.getName();
if ("getProxyConnection".equals(methodName)) {
method.setBody("{return new " + packageName + ".ConnectionJavassistProxy($$);}");
String name = method.getName();
if ("getProxyConnection".equals(name)) {
method.setBody("{return new " + packageName + ".HikariConnectionProxy($$);}");
}
else if ("getProxyStatement".equals(methodName)) {
method.setBody("{return new " + packageName + ".StatementJavassistProxy($$);}");
if ("getProxyStatement".equals(name)) {
method.setBody("{return new " + packageName + ".HikariStatementProxy($$);}");
}
else if ("getProxyPreparedStatement".equals(methodName)) {
method.setBody("{return new " + packageName + ".PreparedStatementJavassistProxy($$);}");
if ("getProxyPreparedStatement".equals(name)) {
method.setBody("{return new " + packageName + ".HikariPreparedStatementProxy($$);}");
}
else if ("getProxyCallableStatement".equals(methodName)) {
method.setBody("{return new " + packageName + ".CallableStatementJavassistProxy($$);}");
if ("getProxyCallableStatement".equals(name)) {
method.setBody("{return new " + packageName + ".HikariCallableStatementProxy($$);}");
}
else if ("getProxyResultSet".equals(methodName)) {
method.setBody("{return new " + packageName + ".ResultSetJavassistProxy($$);}");
if ("getProxyResultSet".equals(name)) {
method.setBody("{return new " + packageName + ".HikariResultSetProxy($$);}");
}
}
proxyCt.toClass(classPool.getClassLoader(), getClass().getProtectionDomain());
proxyCt.writeFile(outputPrefix);
}
/**
* Generate Javassist Proxy Classes
*/
@SuppressWarnings("unchecked")
private <T> Class<T> generateProxyClass(Class<T> primaryInterface, Class<?> superClass, String methodBody) throws Exception
private static <T> void generateProxyClass(Class<T> primaryInterface, String superClassName, String methodBody) throws Exception
{
// Make a new class that extends one of the JavaProxy classes (ie. superClass); use the name to XxxJavassistProxy instead of XxxProxy
String superClassName = superClass.getName();
CtClass superClassCt = classPool.getCtClass(superClassName);
CtClass targetCt = classPool.makeClass(superClassName.replace("Proxy", "JavassistProxy"), superClassCt);
targetCt.setModifiers(Modifier.FINAL);
String newClassName = superClassName.replaceAll("(.+)\\.(\\w+)", "$1.Hikari$2");
CtClass superCt = classPool.getCtClass(superClassName);
CtClass targetCt = classPool.makeClass(newClassName, superCt);
targetCt.setModifiers(Modifier.FINAL);
System.out.println("Generating " + newClassName);
targetCt.setModifiers(Modifier.PUBLIC);
// Make a set of method signatures we inherit implementation for, so we don't generate delegates for these
Set<String> superSigs = new HashSet<String>();
for (CtMethod method : superClassCt.getMethods()) {
for (CtMethod method : superCt.getMethods()) {
if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
superSigs.add(method.getName() + method.getSignature());
}
}
Set<String> methods = new HashSet<String>();
Set<Class<?>> interfaces = ClassLoaderUtils.getAllInterfaces(primaryInterface);
Set<Class<?>> interfaces = getAllInterfaces(primaryInterface);
for (Class<?> intf : interfaces) {
CtClass intfCt = classPool.getCtClass(intf.getName());
targetCt.addInterface(intfCt);
@ -169,7 +167,7 @@ public final class JavassistProxyFactory
String modifiedBody = methodBody;
// If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call
CtMethod superMethod = superClassCt.getMethod(intfMethod.getName(), intfMethod.getSignature());
CtMethod superMethod = superCt.getMethod(intfMethod.getName(), intfMethod.getSignature());
if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT) {
modifiedBody = modifiedBody.replace("((cast) ", "");
modifiedBody = modifiedBody.replace("delegate", "super");
@ -195,10 +193,10 @@ public final class JavassistProxyFactory
}
}
return targetCt.toClass(classPool.getClassLoader(), getClass().getProtectionDomain());
targetCt.writeFile(outputPrefix);
}
private boolean isThrowsSqlException(CtMethod method)
private static boolean isThrowsSqlException(CtMethod method)
{
try {
for (CtClass clazz : method.getExceptionTypes()) {
@ -214,7 +212,7 @@ public final class JavassistProxyFactory
return false;
}
private boolean isDefaultMethod(Class<?> intf, CtClass intfCt, CtMethod intfMethod) throws Exception
private static boolean isDefaultMethod(Class<?> intf, CtClass intfCt, CtMethod intfMethod) throws Exception
{
List<Class<?>> paramTypes = new ArrayList<Class<?>>();
@ -222,10 +220,30 @@ public final class JavassistProxyFactory
paramTypes.add(toJavaClass(pt));
}
return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[0])).toString().contains("default ");
return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[paramTypes.size()])).toString().contains("default ");
}
private static Set<Class<?>> getAllInterfaces(Class<?> clazz)
{
Set<Class<?>> interfaces = new HashSet<Class<?>>();
for (Class<?> intf : Arrays.asList(clazz.getInterfaces())) {
if (intf.getInterfaces().length > 0) {
interfaces.addAll(getAllInterfaces(intf));
}
interfaces.add(intf);
}
if (clazz.getSuperclass() != null) {
interfaces.addAll(getAllInterfaces(clazz.getSuperclass()));
}
if (clazz.isInterface()) {
interfaces.add(clazz);
}
return interfaces;
}
private Class<?> toJavaClass(CtClass cls) throws Exception
private static Class<?> toJavaClass(CtClass cls) throws Exception
{
if (cls.getName().endsWith("[]")) {
return Array.newInstance(toJavaClass(cls.getName().replace("[]", "")), 0).getClass();
@ -235,7 +253,7 @@ public final class JavassistProxyFactory
}
}
private Class<?> toJavaClass(String cn) throws Exception
private static Class<?> toJavaClass(String cn) throws Exception
{
if ("int".equals(cn)) {
return int.class;
@ -267,4 +285,4 @@ public final class JavassistProxyFactory
return Class.forName(cn);
}
}
}

@ -54,7 +54,28 @@
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<!-- Generate proxies -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<phase>compile</phase>
<!-- phase>generate-test-sources</phase -->
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.zaxxer.hikari.util.JavassistProxyFactory</mainClass>
<arguments>
<argument>hikaricp-java6</argument>
<argument>com.zaxxer.hikari.util.Java6ConcurrentBag</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>

@ -22,15 +22,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import com.zaxxer.hikari.proxy.JavassistProxyFactory;
import com.zaxxer.hikari.util.PropertyBeanSetter;
public class HikariConfig extends AbstractHikariConfig
{
static {
JavassistProxyFactory.initialize();
}
/**
* Default constructor
*/

@ -55,6 +55,27 @@
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<plugin>
<!-- Generate proxies -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<executions>
<execution>
<phase>compile</phase>
<!-- phase>generate-test-sources</phase -->
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.zaxxer.hikari.util.JavassistProxyFactory</mainClass>
<arguments>
<argument>hikaricp</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

@ -22,15 +22,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import com.zaxxer.hikari.proxy.JavassistProxyFactory;
import com.zaxxer.hikari.util.PropertyBeanSetter;
public class HikariConfig extends AbstractHikariConfig
{
static {
JavassistProxyFactory.initialize();
}
/**
* Default constructor
*/

@ -1,265 +0,0 @@
/*
* Copyright (C) 2013, 2014 Brett Wooldridge
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zaxxer.hikari.proxy;
import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.Modifier;
import javassist.NotFoundException;
import com.zaxxer.hikari.util.ClassLoaderUtils;
/**
* This class generates the proxy objects for {@link Connection}, {@link Statement},
* {@link PreparedStatement}, and {@link CallableStatement}. Additionally it injects
* method bodies into the {@link ProxyFactory} class methods that can instantiate
* instances of the generated proxies.
*
* @author Brett Wooldridge
*/
public final class JavassistProxyFactory
{
private ClassPool classPool;
static {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(JavassistProxyFactory.class.getClassLoader());
JavassistProxyFactory proxyFactoryFactory = new JavassistProxyFactory();
proxyFactoryFactory.modifyProxyFactory();
}
catch (Exception e) {
throw new RuntimeException("Fatal exception during proxy generation", e);
}
finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
}
/**
* Simply invoking this method causes the initialization of this class. All work
* by this class is performed in static initialization.
*/
public static void initialize()
{
// no-op
}
private JavassistProxyFactory() throws Exception
{
classPool = new ClassPool();
classPool.importPackage("java.sql");
classPool.appendClassPath(new LoaderClassPath(this.getClass().getClassLoader()));
// Cast is not needed for these
String methodBody = "{ try { return delegate.method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(Connection.class, ConnectionProxy.class, methodBody);
generateProxyClass(Statement.class, StatementProxy.class, methodBody);
generateProxyClass(ResultSet.class, ResultSetProxy.class, methodBody);
// For these we have to cast the delegate
methodBody = "{ try { return ((cast) delegate).method($$); } catch (SQLException e) { throw checkException(e); } }";
generateProxyClass(PreparedStatement.class, PreparedStatementProxy.class, methodBody);
generateProxyClass(CallableStatement.class, CallableStatementProxy.class, methodBody);
}
private void modifyProxyFactory() throws Exception
{
String packageName = JavassistProxyFactory.class.getPackage().getName();
CtClass proxyCt = classPool.getCtClass("com.zaxxer.hikari.proxy.ProxyFactory");
for (CtMethod method : proxyCt.getMethods()) {
switch (method.getName()) {
case "getProxyConnection":
method.setBody("{return new " + packageName + ".ConnectionJavassistProxy($$);}");
break;
case "getProxyStatement":
method.setBody("{return new " + packageName + ".StatementJavassistProxy($$);}");
break;
case "getProxyPreparedStatement":
method.setBody("{return new " + packageName + ".PreparedStatementJavassistProxy($$);}");
break;
case "getProxyCallableStatement":
method.setBody("{return new " + packageName + ".CallableStatementJavassistProxy($$);}");
break;
case "getProxyResultSet":
method.setBody("{return new " + packageName + ".ResultSetJavassistProxy($$);}");
break;
}
}
proxyCt.toClass(classPool.getClassLoader(), getClass().getProtectionDomain());
}
/**
* Generate Javassist Proxy Classes
*/
@SuppressWarnings("unchecked")
private <T> Class<T> generateProxyClass(Class<T> primaryInterface, Class<?> superClass, String methodBody) throws Exception
{
// Make a new class that extends one of the JavaProxy classes (ie. superClass); use the name to XxxJavassistProxy instead of XxxProxy
String superClassName = superClass.getName();
CtClass superClassCt = classPool.getCtClass(superClassName);
CtClass targetCt = classPool.makeClass(superClassName.replace("Proxy", "JavassistProxy"), superClassCt);
targetCt.setModifiers(Modifier.FINAL);
// Make a set of method signatures we inherit implementation for, so we don't generate delegates for these
Set<String> superSigs = new HashSet<String>();
for (CtMethod method : superClassCt.getMethods()) {
if ((method.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
superSigs.add(method.getName() + method.getSignature());
}
}
Set<String> methods = new HashSet<String>();
Set<Class<?>> interfaces = ClassLoaderUtils.getAllInterfaces(primaryInterface);
for (Class<?> intf : interfaces) {
CtClass intfCt = classPool.getCtClass(intf.getName());
targetCt.addInterface(intfCt);
for (CtMethod intfMethod : intfCt.getDeclaredMethods()) {
final String signature = intfMethod.getName() + intfMethod.getSignature();
// don't generate delegates for methods we override
if (superSigs.contains(signature)) {
continue;
}
// Ignore already added methods that come from other interfaces
if (methods.contains(signature)) {
continue;
}
// Ignore default methods (only for Jre8 or later)
if (isDefaultMethod(intf, intfCt, intfMethod)) {
continue;
}
// Track what methods we've added
methods.add(signature);
// Clone the method we want to inject into
CtMethod method = CtNewMethod.copy(intfMethod, targetCt, null);
String modifiedBody = methodBody;
// If the super-Proxy has concrete methods (non-abstract), transform the call into a simple super.method() call
CtMethod superMethod = superClassCt.getMethod(intfMethod.getName(), intfMethod.getSignature());
if ((superMethod.getModifiers() & Modifier.ABSTRACT) != Modifier.ABSTRACT) {
modifiedBody = modifiedBody.replace("((cast) ", "");
modifiedBody = modifiedBody.replace("delegate", "super");
modifiedBody = modifiedBody.replace("super)", "super");
}
modifiedBody = modifiedBody.replace("cast", primaryInterface.getName());
// Generate a method that simply invokes the same method on the delegate
if (isThrowsSqlException(intfMethod)) {
modifiedBody = modifiedBody.replace("method", method.getName());
}
else {
modifiedBody = "{ return ((cast) delegate).method($$); }".replace("method", method.getName()).replace("cast", primaryInterface.getName());
}
if (method.getReturnType() == CtClass.voidType) {
modifiedBody = modifiedBody.replace("return", "");
}
method.setBody(modifiedBody);
targetCt.addMethod(method);
}
}
return targetCt.toClass(classPool.getClassLoader(), getClass().getProtectionDomain());
}
private boolean isThrowsSqlException(CtMethod method)
{
try {
for (CtClass clazz : method.getExceptionTypes()) {
if (clazz.getSimpleName().equals("SQLException")) {
return true;
}
}
}
catch (NotFoundException e) {
// fall thru
}
return false;
}
private boolean isDefaultMethod(Class<?> intf, CtClass intfCt, CtMethod intfMethod) throws Exception
{
List<Class<?>> paramTypes = new ArrayList<Class<?>>();
for (CtClass pt : intfMethod.getParameterTypes()) {
paramTypes.add(toJavaClass(pt));
}
return intf.getDeclaredMethod(intfMethod.getName(), paramTypes.toArray(new Class[0])).toString().contains("default ");
}
private Class<?> toJavaClass(CtClass cls) throws Exception
{
if (cls.getName().endsWith("[]")) {
return Array.newInstance(toJavaClass(cls.getName().replace("[]", "")), 0).getClass();
}
else {
return toJavaClass(cls.getName());
}
}
private Class<?> toJavaClass(String cn) throws Exception
{
switch (cn) {
case "int":
return int.class;
case "long":
return long.class;
case "short":
return short.class;
case "byte":
return byte.class;
case "float":
return float.class;
case "double":
return double.class;
case "boolean":
return boolean.class;
case "char":
return char.class;
case "void":
return void.class;
default:
return Class.forName(cn);
}
}
}

@ -85,6 +85,7 @@
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>

Loading…
Cancel
Save