помещение
У автора давно была мысль: обратиться к существующему мейнстримуORMконструкция каркаса, изготовлениеORMКолесо, исходя из того, что в основном не меняет пользовательский опыт, удаляет большое количество дизайнов отражений, от которых зависит фреймворк.APIСоздание компонентов для использования"динамическая компиляция"Загруженный экземпляр для замены, чтобы вы могли приблизиться к использованию собственногоJDBCпредставление. Итак, имея в виду эту идею, изучите подробноJavaдинамическая компиляция. При написании этой статьи авторJDK11.
Фундаментальный
Следующая очень знакомая картина взята из главы о компиляции и оптимизации внешнего интерфейса книги «Углубленное понимание виртуальной машины Java», в которой в основном описывается процесс компиляции:
Кажется, что на приведенном выше рисунке всего три шага, но на самом деле каждый шаг имеет большое количество шагов.Следующее изображение пытается описать конкретные шаги в относительной детализации (изображение относительно большое и его трудно разделить, поэтому поместите оригинал фото прямо):
Фактически, только для процесса компиляции разработчикам или пользователям не нужно полностью разбираться в деталях.JDKпредоставляет инструментарийjavax.toolsРазрешить пользователям использовать простыеAPIКомпиляция (на самом деле в большинстве случаев разработчики разрабатывают бизнес-функции, а такие детали, как компиляция и упаковка, как правило, напрямую контролируются средствами разработки,Maven,GradleДождитесь окончания работы инструмента):
Процесс специального использования включает в себя:
- получить один
javax.tools.JavaCompilerпример. - на основе
JavaФайловый объект инициализирует задачу компиляцииjavax.tools.JavaCompiler$CompilationTaskпример. -
CompilationTaskРезультат выполнения экземпляра представляет собой успех или неудачу процесса компиляции.
❝мы знаем
❞javacКомпилятор на самом делеJavaCompilerРеализация интерфейса, вJDK1.6+, соответствующий класс реализацииcom.sun.tools.javac.api.JavacTool.
так какJVMвнутриClassоснован наClassLoaderИзолирован, поэтому после успешной компиляции соответствующий экземпляр класса может быть загружен через пользовательский загрузчик классов, а затем может быть применено отражение.APIСоздание экземпляра и последующие вызовы.
динамическая компиляция JDK
JDKШаги динамической компиляции были четко объяснены в предыдущем разделе, вот простой сценарий. Предположим, что есть следующий интерфейс:
package club.throwable.compile;
public interface HelloService {
void sayHello(String name);
}
// 默认实现
package club.throwable.compile;
public class DefaultHelloService implements HelloService {
@Override
public void sayHello(String name) {
System.out.println(String.format("%s say hello [by default]", name));
}
}
Мы можем передать строкуSOURCE_CODEОпределите класс:
static String SOURCE_CODE = "package club.throwable.compile;\n" +
"\n" +
"public class JdkDynamicCompileHelloService implements HelloService{\n" +
"\n" +
" @Override\n" +
" public void sayHello(String name) {\n" +
" System.out.println(String.format(\"%s say hello [by jdk dynamic compile]\", name));\n" +
" }\n" +
"}";
// 这里不需要定义类文件,还原类文件内容如下
package club.throwable.compile;
public class JdkDynamicCompileHelloService implements HelloService{
@Override
public void sayHello(String name) {
System.out.println(String.format("%s say hello [by jdk dynamic compile]", name));
}
}
Перед сборкой экземпляра задачи компиляции необходимо сделать еще несколько вещей:
- Встроенный
JavaFileObjectСтандартная реализацияSimpleJavaFileObjectОн ориентирован на исходный файл класса.Поскольку строка содержимого исходного файла класса вводится во время динамической компиляции, его необходимо реализовать отдельно.JavaFileObject. - Встроенный
JavaFileManagerориентирован на путь к классамJavaЗагружается файл с исходным кодом, который также нужно реализовать самостоятельно.JavaFileManager. - нужно настроить один
ClassLoaderinstance для загрузки скомпилированного динамического класса.
Реализовать JavaFileObject
реализовать один самостоятельноJavaFileObject, на самом деле вы можете просто наследовать напрямуюSimpleJavaFileObject, вы можете переопределить методы, которые вам нужно использовать:
public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
public static final String CLASS_EXTENSION = ".class";
public static final String JAVA_EXTENSION = ".java";
private static URI fromClassName(String className) {
try {
return new URI(className);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(className, e);
}
}
private ByteArrayOutputStream byteCode;
private final CharSequence sourceCode;
public CharSequenceJavaFileObject(String className, CharSequence sourceCode) {
super(fromClassName(className + JAVA_EXTENSION), Kind.SOURCE);
this.sourceCode = sourceCode;
}
public CharSequenceJavaFileObject(String fullClassName, Kind kind) {
super(fromClassName(fullClassName), kind);
this.sourceCode = null;
}
public CharSequenceJavaFileObject(URI uri, Kind kind) {
super(uri, kind);
this.sourceCode = null;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return sourceCode;
}
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(getByteCode());
}
// 注意这个方法是编译结果回调的OutputStream,回调成功后就能通过下面的getByteCode()方法获取目标类编译后的字节码字节数组
@Override
public OutputStream openOutputStream() {
return byteCode = new ByteArrayOutputStream();
}
public byte[] getByteCode() {
return byteCode.toByteArray();
}
}
Если компиляция прошла успешно, напрямую через самодобавляемыйCharSequenceJavaFileObject#getByteCode()метод для получения массива байтов (двоичного содержимого), соответствующего скомпилированному байт-коду целевого класса. здесьCharSequenceJavaFileObjectНесколько конструкторов зарезервированы для совместимости с исходным методом компиляции.
Реализовать загрузчик классов
Простое наследованиеClassLoaderВот и все, ключ перезаписать оригиналClassLoader#findClass()способ поиска пользовательскихJavaFileObjectНапример, чтобы извлечь соответствующий байт-массив байт-кода для загрузки.Для этого можно добавить хеш-таблицу в качестве кеша, а ключ-значение - это псевдоним полного имени класса (xx.yy.MyClassформа, неURIрежим) и соответствующий целевой классJavaFileObjectпример.
public class JdkDynamicCompileClassLoader extends ClassLoader {
public static final String CLASS_EXTENSION = ".class";
private final Map<String, JavaFileObject> javaFileObjectMap = Maps.newConcurrentMap();
public JdkDynamicCompileClassLoader(ClassLoader parentClassLoader) {
super(parentClassLoader);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
JavaFileObject javaFileObject = javaFileObjectMap.get(name);
if (null != javaFileObject) {
CharSequenceJavaFileObject charSequenceJavaFileObject = (CharSequenceJavaFileObject) javaFileObject;
byte[] byteCode = charSequenceJavaFileObject.getByteCode();
return defineClass(name, byteCode, 0, byteCode.length);
}
return super.findClass(name);
}
@Nullable
@Override
public InputStream getResourceAsStream(String name) {
if (name.endsWith(CLASS_EXTENSION)) {
String qualifiedClassName = name.substring(0, name.length() - CLASS_EXTENSION.length()).replace('/', '.');
CharSequenceJavaFileObject javaFileObject = (CharSequenceJavaFileObject) javaFileObjectMap.get(qualifiedClassName);
if (null != javaFileObject && null != javaFileObject.getByteCode()) {
return new ByteArrayInputStream(javaFileObject.getByteCode());
}
}
return super.getResourceAsStream(name);
}
// 暂时存放编译的源文件对象,key为全类名的别名(非URI模式),如club.throwable.compile.HelloService
void addJavaFileObject(String qualifiedClassName, JavaFileObject javaFileObject) {
javaFileObjectMap.put(qualifiedClassName, javaFileObject);
}
Collection<JavaFileObject> listJavaFileObject() {
return Collections.unmodifiableCollection(javaFileObjectMap.values());
}
}
Реализовать JavaFileManager
JavaFileManagerдаJavaАбстрактный менеджер файлов, который используется для управления обычнымиJavaфайлы, но не только файлы, может также управлять другими источникамиJavaданные файла класса. Реализуя пользовательскийJavaFileManagerИсходный код для управления типами строк. Для простоты можно напрямую наследовать существующийForwardingJavaFileManager:
public class JdkDynamicCompileJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private final JdkDynamicCompileClassLoader classLoader;
private final Map<URI, JavaFileObject> javaFileObjectMap = Maps.newConcurrentMap();
public JdkDynamicCompileJavaFileManager(JavaFileManager fileManager, JdkDynamicCompileClassLoader classLoader) {
super(fileManager);
this.classLoader = classLoader;
}
private static URI fromLocation(Location location, String packageName, String relativeName) {
try {
return new URI(location.getName() + '/' + packageName + '/' + relativeName);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
JavaFileObject javaFileObject = javaFileObjectMap.get(fromLocation(location, packageName, relativeName));
if (null != javaFileObject) {
return javaFileObject;
}
return super.getFileForInput(location, packageName, relativeName);
}
// 这里是编译器返回的同(源)Java文件对象,替换为CharSequenceJavaFileObject实现
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
JavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, kind);
classLoader.addJavaFileObject(className, javaFileObject);
return javaFileObject;
}
// 这里覆盖原来的类加载器
@Override
public ClassLoader getClassLoader(Location location) {
return classLoader;
}
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
if (file instanceof CharSequenceJavaFileObject) {
return file.getName();
}
return super.inferBinaryName(location, file);
}
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
Iterable<JavaFileObject> superResult = super.list(location, packageName, kinds, recurse);
List<JavaFileObject> result = Lists.newArrayList();
// 这里要区分编译的Location以及编译的Kind
if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
// .class文件以及classPath下
for (JavaFileObject file : javaFileObjectMap.values()) {
if (file.getKind() == JavaFileObject.Kind.CLASS && file.getName().startsWith(packageName)) {
result.add(file);
}
}
// 这里需要额外添加类加载器加载的所有Java文件对象
result.addAll(classLoader.listJavaFileObject());
} else if (location == StandardLocation.SOURCE_PATH && kinds.contains(JavaFileObject.Kind.SOURCE)) {
// .java文件以及编译路径下
for (JavaFileObject file : javaFileObjectMap.values()) {
if (file.getKind() == JavaFileObject.Kind.SOURCE && file.getName().startsWith(packageName)) {
result.add(file);
}
}
}
for (JavaFileObject javaFileObject : superResult) {
result.add(javaFileObject);
}
return result;
}
// 自定义方法,用于添加和缓存待编译的源文件对象
public void addJavaFileObject(Location location, String packageName, String relativeName, JavaFileObject javaFileObject) {
javaFileObjectMap.put(fromLocation(location, packageName, relativeName), javaFileObject);
}
}
Обратите внимание на введение пользовательского загрузчика классов в этом классе.JdkDynamicCompileClassLoader, целью является достижениеJavaFileObjectСовместное использование экземпляров и предоставление экземпляров загрузчика классов файловым менеджерам.
Компилировать и запускать динамически
Подготовительные работы завершены, можно переходитьJavaCompilerЧтобы скомпилировать вышеупомянутую строку, для лучшей совместимости байт-кода, вы можете указать немного меньшее значение при компиляцииJDKпример версии1.6:
public class Client {
static String SOURCE_CODE = "package club.throwable.compile;\n" +
"\n" +
"public class JdkDynamicCompileHelloService implements HelloService{\n" +
"\n" +
" @Override\n" +
" public void sayHello(String name) {\n" +
" System.out.println(String.format(\"%s say hello [by jdk dynamic compile]\", name));\n" +
" }\n" +
"}";
// 编译诊断收集器
static DiagnosticCollector<JavaFileObject> DIAGNOSTIC_COLLECTOR = new DiagnosticCollector<>();
public static void main(String[] args) throws Exception {
// 获取系统编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 设置编译参数 - 指定编译版本为JDK1.6以提高兼容性
List<String> options = new ArrayList<>();
options.add("-source");
options.add("1.6");
options.add("-target");
options.add("1.6");
// 获取标准的Java文件管理器实例
StandardJavaFileManager manager = compiler.getStandardFileManager(DIAGNOSTIC_COLLECTOR, null, null);
// 初始化自定义类加载器
JdkDynamicCompileClassLoader classLoader = new JdkDynamicCompileClassLoader(Thread.currentThread().getContextClassLoader());
// 初始化自定义Java文件管理器实例
JdkDynamicCompileJavaFileManager fileManager = new JdkDynamicCompileJavaFileManager(manager, classLoader);
String packageName = "club.throwable.compile";
String className = "JdkDynamicCompileHelloService";
String qualifiedName = packageName + "." + className;
// 构建Java源文件实例
CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, SOURCE_CODE);
// 添加Java源文件实例到自定义Java文件管理器实例中
fileManager.addJavaFileObject(
StandardLocation.SOURCE_PATH,
packageName,
className + CharSequenceJavaFileObject.JAVA_EXTENSION,
javaFileObject
);
// 初始化一个编译任务实例
JavaCompiler.CompilationTask compilationTask = compiler.getTask(
null,
fileManager,
DIAGNOSTIC_COLLECTOR,
options,
null,
Lists.newArrayList(javaFileObject)
);
// 执行编译任务
Boolean result = compilationTask.call();
System.out.println(String.format("编译[%s]结果:%s", qualifiedName, result));
Class<?> klass = classLoader.loadClass(qualifiedName);
HelloService instance = (HelloService) klass.getDeclaredConstructor().newInstance();
instance.sayHello("throwable");
}
}
Результат выглядит следующим образом:
编译[club.throwable.compile.JdkDynamicCompileHelloService]结果:true
throwable say hello [by jdk dynamic compile]
Видно, что передается исходный код класса строки и реализуются динамическая компиляция, загрузка класса, создание экземпляра отражения и окончательный вызов метода. Кроме того, диагностическая информация для процесса компиляции может быть доступна черезDiagnosticCollectorприобретение экземпляра. Для повторного использования здесь может бытьJDKПроцесс динамической компиляции вынесен в метод:
public final class JdkCompiler {
static DiagnosticCollector<JavaFileObject> DIAGNOSTIC_COLLECTOR = new DiagnosticCollector<>();
@SuppressWarnings("unchecked")
public static <T> T compile(String packageName,
String className,
String sourceCode,
Class<?>[] constructorParamTypes,
Object[] constructorParams) throws Exception {
// 获取系统编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 设置编译参数
List<String> options = new ArrayList<>();
options.add("-source");
options.add("1.6");
options.add("-target");
options.add("1.6");
// 获取标准的Java文件管理器实例
StandardJavaFileManager manager = compiler.getStandardFileManager(DIAGNOSTIC_COLLECTOR, null, null);
// 初始化自定义类加载器
JdkDynamicCompileClassLoader classLoader = new JdkDynamicCompileClassLoader(Thread.currentThread().getContextClassLoader());
// 初始化自定义Java文件管理器实例
JdkDynamicCompileJavaFileManager fileManager = new JdkDynamicCompileJavaFileManager(manager, classLoader);
String qualifiedName = packageName + "." + className;
// 构建Java源文件实例
CharSequenceJavaFileObject javaFileObject = new CharSequenceJavaFileObject(className, sourceCode);
// 添加Java源文件实例到自定义Java文件管理器实例中
fileManager.addJavaFileObject(
StandardLocation.SOURCE_PATH,
packageName,
className + CharSequenceJavaFileObject.JAVA_EXTENSION,
javaFileObject
);
// 初始化一个编译任务实例
JavaCompiler.CompilationTask compilationTask = compiler.getTask(
null,
fileManager,
DIAGNOSTIC_COLLECTOR,
options,
null,
Lists.newArrayList(javaFileObject)
);
Boolean result = compilationTask.call();
System.out.println(String.format("编译[%s]结果:%s", qualifiedName, result));
Class<?> klass = classLoader.loadClass(qualifiedName);
return (T) klass.getDeclaredConstructor(constructorParamTypes).newInstance(constructorParams);
}
}
Динамическая компиляция Javassist
Так как естьJDKПочему динамическая компиляция все еще существует?JavassistТакой инструмент улучшения байт-кода? Независимо от аспектов производительности или эффективности,JDKДинамическая компиляция имеет серьезные ограничения.Очевидным из них является то, что инструментарий байт-кода не может быть завершен, другими словами, он не может быть изменен или расширен на основе исходных классов и методов, ноJavassistсможет сделать. Более того,Javassistкоторый предоставилAPIиJDKОтраженныйAPIОчень похоже, если отражение обычно используется более умело,JavassistНачать становится проще. Вот лишь список улучшений, упомянутых ранееDefaultHelloServiceНапример, сначала введите зависимости:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
Кодировка следующая:
public class JavassistClient {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("club.throwable.compile.DefaultHelloService");
CtMethod ctMethod = cc.getDeclaredMethod("sayHello", new CtClass[]{pool.get("java.lang.String")});
ctMethod.insertBefore("System.out.println(\"insert before by Javassist\");");
ctMethod.insertAfter("System.out.println(\"insert after by Javassist\");");
Class<?> klass = cc.toClass();
System.out.println(klass.getName());
HelloService helloService = (HelloService) klass.getDeclaredConstructor().newInstance();
helloService.sayHello("throwable");
}
}
Результат выглядит следующим образом:
club.throwable.compile.DefaultHelloService
insert before by Javassist
throwable say hello [by default]
insert after by Javassist
JavaassistСлово на самом делеJavaиAssistдва слова, соединенные вместе, что означаетJavaпомощник, аJavaБиблиотека классов улучшения байт-кода:
- Усовершенствования байт-кода могут быть выполнены на основе существующих классов, например, путем изменения существующих методов, переменных или даже добавления новых методов непосредственно в исходные классы.
- Совершенно новый класс может быть динамически описан точно так же, как сращивание строительных блоков.
в отличиеASM(ASMКривая обучения относительно крутая, и она относится к относительно низкоуровневой библиотеке классов операций с байт-кодом.Конечно, с точки зрения производительностиASMЭффективность улучшения байт-кода намного выше, чем у других сред инкапсуляции высокого уровня), что требует хорошего понимания программирования байт-кода,JavaassistУменьшена сложность начала работы с улучшениями байт-кода.
Расширенный пример
Теперь определите интерфейсMysqlInfoMapper, который используется для динамического выполнения известногоSQL, очень просто, просто запросMySQLсистемная таблицаmysqlинформация о пользователе внутриSELECT Host,User FROM mysql.user:
@Data
public class MysqlUser {
private String host;
private String user;
}
public interface MysqlInfoMapper {
List<MysqlUser> selectAllMysqlUsers();
}
Предположим теперь, что только одинMySQLпакет драйверов (mysql:mysql-connector-java:jar:8.0.20), временно не может полагаться на какую-либо высокоуровневую структуру, которая будет реализована динамически.MysqlInfoMapperИнтерфейс, расставьте приоритеты необходимых компонентов:
- Требуется диспетчер соединений для управления
MySQLСвязь. - нужен один
SQLExecutor используется для выполнения запросаSQL. - Обработчик результатов требуется для извлечения и преобразования результатов запроса.
Для простоты автор реализует его через синглтон в интерфейсе при определении интерфейсов трех компонентов (часть конфигурации полностью жестко запрограммирована):
// 连接管理器
public interface ConnectionManager {
String USER_NAME = "root";
String PASS_WORD = "root";
String URL = "jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false";
Connection newConnection() throws SQLException;
void closeConnection(Connection connection);
ConnectionManager X = new ConnectionManager() {
@Override
public Connection newConnection() throws SQLException {
return DriverManager.getConnection(URL, USER_NAME, PASS_WORD);
}
@Override
public void closeConnection(Connection connection) {
try {
connection.close();
} catch (Exception ignore) {
}
}
};
}
// 执行器
public interface SqlExecutor {
ResultSet execute(Connection connection, String sql) throws SQLException;
SqlExecutor X = new SqlExecutor() {
@Override
public ResultSet execute(Connection connection, String sql) throws SQLException {
Statement statement = connection.createStatement();
statement.execute(sql);
return statement.getResultSet();
}
};
}
// 结果处理器
public interface ResultHandler<T> {
T handleResultSet(ResultSet resultSet) throws SQLException;
ResultHandler<List<MysqlUser>> X = new ResultHandler<List<MysqlUser>>() {
@Override
public List<MysqlUser> handleResultSet(ResultSet resultSet) throws SQLException {
try {
List<MysqlUser> result = Lists.newArrayList();
while (resultSet.next()) {
MysqlUser item = new MysqlUser();
item.setHost(resultSet.getString("Host"));
item.setUser(resultSet.getString("User"));
result.add(item);
}
return result;
} finally {
resultSet.close();
}
}
};
}
Тогда нужна динамическая компиляцияMysqlInfoMapperкласса реализации, строковое содержимое его исходного файла выглядит следующим образом (будьте осторожны, чтобы не создать новый файл в пути к классамDefaultMysqlInfoMapperсвоего рода):
package club.throwable.compile;
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.List;
public class DefaultMysqlInfoMapper implements MysqlInfoMapper {
private final ConnectionManager connectionManager;
private final SqlExecutor sqlExecutor;
private final ResultHandler resultHandler;
private final String sql;
public DefaultMysqlInfoMapper(ConnectionManager connectionManager,
SqlExecutor sqlExecutor,
ResultHandler resultHandler,
String sql) {
this.connectionManager = connectionManager;
this.sqlExecutor = sqlExecutor;
this.resultHandler = resultHandler;
this.sql = sql;
}
@Override
public List<MysqlUser> selectAllMysqlUsers() {
try {
Connection connection = connectionManager.newConnection();
try {
ResultSet resultSet = sqlExecutor.execute(connection, sql);
return (List<MysqlUser>) resultHandler.handleResultSet(resultSet);
} finally {
connectionManager.closeConnection(connection);
}
} catch (Exception e) {
// 暂时忽略异常处理,统一封装为IllegalStateException
throw new IllegalStateException(e);
}
}
}
Затем напишите клиент для динамической компиляции и выполнения:
public class MysqlInfoClient {
static String SOURCE_CODE = "package club.throwable.compile;\n" +
"import java.sql.Connection;\n" +
"import java.sql.ResultSet;\n" +
"import java.util.List;\n" +
"\n" +
"public class DefaultMysqlInfoMapper implements MysqlInfoMapper {\n" +
"\n" +
" private final ConnectionManager connectionManager;\n" +
" private final SqlExecutor sqlExecutor;\n" +
" private final ResultHandler resultHandler;\n" +
" private final String sql;\n" +
"\n" +
" public DefaultMysqlInfoMapper(ConnectionManager connectionManager,\n" +
" SqlExecutor sqlExecutor,\n" +
" ResultHandler resultHandler,\n" +
" String sql) {\n" +
" this.connectionManager = connectionManager;\n" +
" this.sqlExecutor = sqlExecutor;\n" +
" this.resultHandler = resultHandler;\n" +
" this.sql = sql;\n" +
" }\n" +
"\n" +
" @Override\n" +
" public List<MysqlUser> selectAllMysqlUsers() {\n" +
" try {\n" +
" Connection connection = connectionManager.newConnection();\n" +
" try {\n" +
" ResultSet resultSet = sqlExecutor.execute(connection, sql);\n" +
" return (List<MysqlUser>) resultHandler.handleResultSet(resultSet);\n" +
" } finally {\n" +
" connectionManager.closeConnection(connection);\n" +
" }\n" +
" } catch (Exception e) {\n" +
" // 暂时忽略异常处理,统一封装为IllegalStateException\n" +
" throw new IllegalStateException(e);\n" +
" }\n" +
" }\n" +
"}\n";
static String SQL = "SELECT Host,User FROM mysql.user";
public static void main(String[] args) throws Exception {
MysqlInfoMapper mysqlInfoMapper = JdkCompiler.compile(
"club.throwable.compile",
"DefaultMysqlInfoMapper",
SOURCE_CODE,
new Class[]{ConnectionManager.class, SqlExecutor.class, ResultHandler.class, String.class},
new Object[]{ConnectionManager.X, SqlExecutor.X, ResultHandler.X, SQL});
System.out.println(JSON.toJSONString(mysqlInfoMapper.selectAllMysqlUsers()));
}
}
Конечный результат:
编译[club.throwable.compile.DefaultMysqlInfoMapper]结果:true
[{
"host": "%",
"user": "canal"
}, {
"host": "%",
"user": "doge"
}, {
"host": "localhost",
"user": "mysql.infoschema"
}, {
"host": "localhost",
"user": "mysql.session"
}, {
"host": "localhost",
"user": "mysql.sys"
}, {
"host": "localhost",
"user": "root"
}]
Затем я просматриваю локально установленныйMySQL, проверьте правильность результатов запроса.
Здесь, чтобы упростить весь пример, автор неMysqlInfoMapper#selectAllMysqlUsers()Добавьте параметры запроса в метод, вы можете попробовать запросSQLдаSELECT Host,User FROM mysql.user WHERE User = 'xxx'Реализация кодирования в сценариях.
❝Если динамическая реализация
❞DefaultMysqlInfoMapperзарегистрироваться наIOCконтейнер, можноMysqlInfoMapperАвтопровод по типу. если поставитьSQLи обработку параметров можно вынести в отдельный файл, и реализовать соответствующий парсер файлов, то файл класса иSQLизолировать,MybatisиHibernateВсе делают это.
резюме
Динамическая компиляция или низкоуровневое программирование, ориентированное на байт-код, на самом деле является очень сложной областью, которая может создать бесконечные возможности, и эта статья лишь кратко анализирует ее.JavaПроцесс компиляции исходного кода и имитация динамической компиляции на простых примерах еще далеки от использования в практических приложениях, и потребуется больше времени для анализа знаний в смежных областях.
Использованная литература:
-
JDK11Часть исходного кода - «Углубленное понимание виртуальной машины Java — 3 место»
- Javassist
(Конец этой статьи c-4-d e-a-20200606 0:23 r-a-20200718 Обложка из "Дракона и Тигра")
В этой статье используетсяmdniceнабор текста