AOT — это новая функция, представленная в Spring 6.0, компиляции Ahead of Time.
JIT (Just-in-time) динамическая компиляция, JIT-компиляция, то есть компиляция во время работы, то есть, когда программа работает, код генерируется динамически. Запуск происходит относительно медленно, и для компиляции требуются ресурсы времени выполнения. .
AOT, Ahead Of Time, относится к компиляции перед запуском. Предварительная компиляция AOT может напрямую преобразовывать исходный код в машинный код, имеет низкое использование памяти и высокую скорость запуска. Она может работать без среды выполнения и напрямую статически связывать среду выполнения с конечной программой. . , но при этом нет никакого повышения производительности во время выполнения, и дальнейшая оптимизация не может быть выполнена в зависимости от условий выполнения программы. Недостаток AOT заключается в том, что компиляция перед запуском программы увеличивает время установки программы.
Проще говоря:JITнемедленныйкомпилироватьэто во время работы программы,Преобразование байт-кода в машинный код, который может выполняться непосредственно на оборудовании.,AOT-компиляция — это процесс преобразования байт-кода в машинный код перед запуском программы.
GraalVM поддерживает как AOT, так и JIT. Поддерживает несколько языков разработки.
Технология AOT, поддерживаемая Spring6, эта GraalVM является базовой поддержкой, а Spring также обеспечивает первоклассную поддержку собственных образов GraalVM. GraalVM — это высокопроизводительный JDK, предназначенный для ускорения выполнения приложений, написанных на Java и других языках JVM, а также обеспечивающий среду выполнения для JavaScript, Python и многих других популярных языков. GraalVM предлагает два способа запуска Java-приложений: с помощью JIT-компилятора Graal на JVM HotSpot или в виде заранее скомпилированного собственного исполняемого файла (AOT). Многоязычные возможности GraalVM позволяют смешивать несколько языков программирования в одном приложении, устраняя при этом затраты на вызовы на иностранных языках. GraalVM добавляет усовершенствованный JIT-оптимизирующий компилятор, написанный на Java, в виртуальную машину Java HotSpot.
GraalVM имеет следующие возможности:
(1) Усовершенствованный оптимизирующий компилятор, генерирующий более быстрый и компактный код, требующий меньше вычислительных ресурсов.
(2) Компиляция собственного образа AOT заранее компилирует приложения Java в собственные двоичные файлы, запускается немедленно и достигает максимальной производительности без предварительного нагрева.
(3) Полиглотное программирование использует лучшие функции и библиотеки популярных языков в одном приложении без дополнительных затрат.
(4) Расширенные инструменты для отладки, мониторинга, анализа и оптимизации потребления ресурсов на Java и нескольких языках.
Адрес загрузки: https://www.graalvm.org/downloads/
Просто загрузите версию сообщества, нажмите, чтобы войти, и выберите соответствующую версию: https://github.com/graalvm/graalvm-ce-builds/releases.
После скачивания разархивируйте его
Добавлено: GRAALVM_HOME.
Редактировать пользовательские переменные
Измените JAVA_HOME на местоположение graalvm.
Проверьте, прошла ли настройка успешно
Используйте команду gu install Native-image, чтобы загрузить и установить плагин, поскольку версия сообщества не обеспечивает поддержку по умолчанию. Нужно скачать вручную
Native image (локальный образ) — технология создания нативных приложений на платформе Java. Он компилирует приложения Java в собственный машинный код для запуска без необходимости использования виртуальной машины Java (JVM). Это позволяет приложениям запускаться быстрее, работать более эффективно и использовать меньше памяти.
Native image использует технологию компилятора GraalVM для преобразования приложений Java в локальные исполняемые файлы, поддерживая несколько платформ операционных систем, таких как Windows, Linux и MacOS. Кроме того, образ Native может упаковывать приложения Java в один исполняемый файл для упрощения развертывания и распространения.
Используя собственный образ, разработчики могут создавать и развертывать приложения Java как собственные приложения, что приводит к повышению производительности и удобству взаимодействия с пользователем.
https://visualstudio.microsoft.com/zh-hans/downloads/
Аналогично мы можем скачать версию сообщества
После загрузки дважды щелкните, чтобы установить напрямую.
Ожидание онлайн-загрузки
Обратите внимание на варианты установки и продолжайте ждать.
Создайте обычный файл Hello.java.
public class Hello{
public static void main(String[] args){
System.out.println("Hello World ...");
}
}
Затем скомпилируйте его через javac Hello.java.
Выполняется через собственный образ Hello
Файл Hello.exe создается с помощью Native-Image, и мы можем создать его напрямую.
Мы также можем заранее скомпилировать наш проект через AOT в проекте SpringBoot и создать новый Maven-проект. Затем добавьте соответствующие зависимости
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
В то же время нам также необходимо добавить соответствующий плагин SpringBoot.
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Затем мы можем написать простой тест кода
Затем мы открываем командную строку x64 Native Tools для VS 2019. Затем нам нужно переключиться в каталог проекта
Затем выполните mvn -Pnative own:compile для компиляции. Если компиляция прошла успешно, в целевом каталоге будет создан EXE-файл. Просто запустите файл позже
Скомпилировано успешно
Затем дважды щелкните, чтобы выполнить exe-файл. Вы обнаружите, что это будет намного быстрее
Запуск приложения в качестве собственного образа требует дополнительной информации по сравнению с обычной средой выполнения JVM. Например, GraalVM необходимо заранее знать, использует ли компонент отражение. Аналогично, ресурсы пути к классам не предоставляются в собственном образе, если это не указано явно. Поэтому, если приложению необходимо загрузить ресурс, оно должно ссылаться на него из соответствующего файла конфигурации собственного образа GraalVM.
APIRuntimeHints
Собирать во время выполненияотражение、Загрузка ресурсов、Сериализация и JDK Нужен агент.
Объявить общий тип сущности
public class UserEntity {
public String hello(){
return "hello ...";
}
}
Дальше оперируем и обрабатываем через отражение в контроллере
@GetMapping("/hello")
public String hello(){
String res = "hello";
try {
Method hello = UserEntity.class.getMethod("hello");
res = (String)hello.invoke(UserEntity.class.newInstance(),null);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
return res;
}
Затем скомпилируйте его в exe-файл с помощью команды
После запуска exe-файла. Инициируем запрос через браузер
В HelloController. Мы используем конструктор UserEntity без параметров посредством отражения. Если ничего не будет сделано. Тогда его нельзя будет выполнить после того, как он будет преобразован в двоичный исполняемый файл. Выше приведено конкретное сообщение об ошибке. для этой ситуации. Мы можем справиться с этим с помощью механизма Runtime Hints.
Решения предоставлены официальным сайтом. Мы настраиваем класс реализации интерфейса RuntimeHintsRegistrar, а затем внедряем класс реализации в Spring.
наша собственная реализация
@RestController
@ImportRuntimeHints(HelloController.UserEntityRuntimeHints.class)
public class HelloController {
@GetMapping("/hello")
public String hello(){
String res = "hello";
try {
Method hello = UserEntity.class.getMethod("hello");
res = (String)hello.invoke(UserEntity.class.newInstance(),null);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
return res;
}
static class UserEntityRuntimeHints implements RuntimeHintsRegistrar{
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
try {
hints.reflection().registerConstructor(UserEntity.class.getConstructor(), ExecutableMode.INVOKE);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}
мы выполняем mvn -Pnative native:compile
время встречиосуществлятьGraalVMСоответствующие инструкции в。в конце концов позвонюSpringApplicationAotProcessorвmain Метод для выполнения связанных операций предварительной компиляции.
public static void main(String[] args) throws Exception {
int requiredArgs = 6; // Вызов основного метода получает 6 параметров
Assert.isTrue(args.length >= requiredArgs, () -> "Usage: " + SpringApplicationAotProcessor.class.getName()
+ " <applicationName> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId> <originalArgs...>");
// Получите начальный класс проекта SpringBoot.
Class<?> application = Class.forName(args[0]);
// Передаваемые параметры завершают настройку соответствующего сгенерированного каталога.
Settings settings = Settings.builder().sourceOutput(Paths.get(args[1])).resourceOutput(Paths.get(args[2]))
.classOutput(Paths.get(args[3])).groupId((StringUtils.hasText(args[4])) ? args[4] : "unspecified")
.artifactId(args[5]).build();
String[] applicationArgs = (args.length > requiredArgs) ? Arrays.copyOfRange(args, requiredArgs, args.length)
: new String[0];
// осуществлять process метод
new SpringApplicationAotProcessor(application, settings, applicationArgs).process();
}
Введите метод обработки
public final T process() {
try {
// Установить статус
System.setProperty(AOT_PROCESSING, "true");
return doProcess(); // Обработка Coreметод
}
finally {
System.clearProperty(AOT_PROCESSING);
}
}
Введите метод doProcess().
@Override
protected ClassName doProcess() {
deleteExistingOutput(); // Удалить существующий каталог
// Запустите службу SpringBoot Но он не будет сканировать бобы
GenericApplicationContext applicationContext = prepareApplicationContext(getApplicationClass());
return performAotProcessing(applicationContext);
}
подготовка ApplicationContext:
@Override
protected GenericApplicationContext prepareApplicationContext(Class<?> application) {
return new AotProcessorHook(application).run(() -> {
Method mainMethod = application.getMethod("main", String[].class);
return ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { this.applicationArgs });
});
}
На этот раз для запуска SpringBoot будет выполнен основной метод класса запуска.
При создании объекта контекста Spring во время запуска будет выполнена следующая обработка:
private ConfigurableApplicationContext createContext() {
if (!AotDetector.useGeneratedArtifacts()) {
return new AnnotationConfigServletWebServerApplicationContext();
}
return new ServletWebServerApplicationContext();
}
Если AOT не используется, будет создан AnnotationConfigServletWebServerApplicationContext и к нему будет добавлен ConfigurationClassPostProcessor, чтобы класс конфигурации был проанализирован и просканирован. Если AOT используется, будет создан ServletWebServerApplicationContext. Это пустой контейнер и нет ConfigurationClassPostProcessor. в нем, поэтому сканирование не будет запускаться в будущем.
Вернитесь к методу PerformAotProcessing.
protected ClassName performAotProcessing(GenericApplicationContext applicationContext) {
FileSystemGeneratedFiles generatedFiles = createFileSystemGeneratedFiles();
DefaultGenerationContext generationContext = new DefaultGenerationContext(
createClassNameGenerator(), generatedFiles);
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
// Выполнение связанных операций сканирования
ClassName generatedInitializerClassName = generator.processAheadOfTime(applicationContext, generationContext);
//Если есть регистрационная информация для отражения. Здесь соответствующая информация будет сгенерирована в RuntimeHints, соответствующем отражению-config.json.
registerEntryPointHint(generationContext, generatedInitializerClassName);
// Создать Java-документ в исходном каталоге.
generationContext.writeGeneratedContent();
// Запишите содержимое RuntimeHints в различные конфигурации Graalvm в документе каталога ресурсов.
writeHints(generationContext.getRuntimeHints());
writeNativeImageProperties(getDefaultNativeImageArguments(getApplicationClass().getName()));
return generatedInitializerClassName;
}
Логика в процессеAheadOfTime
public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
GenerationContext generationContext) {
return withCglibClassHandler(new CglibClassHandler(generationContext), () -> {
// Обработка сканирования
applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
// Получить объект Bean Factory
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
ApplicationContextInitializationCodeGenerator codeGenerator =
new ApplicationContextInitializationCodeGenerator(generationContext);
new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext, codeGenerator);
return codeGenerator.getGeneratedClass().getName();
});
}
Введите метод обновленияForAotProcessing.
public void refreshForAotProcessing(RuntimeHints runtimeHints) {
if (logger.isDebugEnabled()) {
logger.debug("Preparing bean factory for AOT processing");
}
prepareRefresh();
obtainFreshBeanFactory(); // Получить заводской объект. и завершите операцию сканирования
prepareBeanFactory(this.beanFactory);
postProcessBeanFactory(this.beanFactory);
invokeBeanFactoryPostProcessors(this.beanFactory); // заводской постпроцессор
this.beanFactory.freezeConfiguration();
PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
preDetermineBeanTypes(runtimeHints);
}
Логика метода BeanFactoryInitializationAotContributions: он считывает загрузчик файла aot.properties и инкапсулирует BeanFactory в объект Loader, а затем передает его в
BeanFactoryInitializationAotContributions(DefaultListableBeanFactory beanFactory) {
this(beanFactory, AotServices.factoriesAndBeans(beanFactory));
}