Springboot3 的一些新特性
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide
环境要求
Springboot3.0.5 对java版本最低要求为java17,并向上兼容到java20,spring 版本要求为6.0.7+,maven 版本要求为3.5+,Servlet 版本要求为5.0+,Servlet5.0 对应Tomcat10.0。
参考:https://docs.spring.io/spring-boot/docs/3.0.5/reference/html/getting-started.html#getting-started.system-requirements
Java EE --> Jakarta EE
2017 年,Oracle 宣布将Java EE 捐赠给了Eclipse 基金会,但Oracle 不允许其使用Java 商标,于是社区将Java EE 改名了为Jakarta EE。于是javax 包变更为了 jakarta 包。Springboot3 已经将所有的Java EE 迁移到了 Jakarta EE 。
Spring Boot 3.0 has migrated from Java EE to Jakarta EE APIs for all dependencies. Wherever possible, Jakarta EE 10 compatible dependencies have been chosen, including:
- Jakarta Activation 2.1
- Jakarta JMS 3.1
- Jakarta JSON 2.1
- Jakarta JSON Bind 3.0
- Jakarta Mail 2.1
- Jakarta Persistence 3.1
- Jakarta Servlet 6.0
- Jakarta Servlet JSP JSTL 3.0
- Jakarta Transaction 2.0
- Jakarta Validation 3.0
- Jakarta WebSocket 2.1
- Jakarta WS RS 3.1
- Jakarta XML SOAP 3.0
- Jakarta XML WS 4.0
https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api
如上是servlet API jar包的变更
自动配置包位置改变
Spring Boot 2.7引入了一个用于注册自动配置的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,同时还兼容了原先的spring .factories
中的自动配置内容。在Spring Boot 3.0中,原先的spring .factories
中的自动配置内容已经被移除,其他的配置没有变化。
第一张图是springboot2.2的,第二张图是springboot3.0.5的
支持GraalVM原生镜像(Native Image)
GraalVM
GraalVM是一个高性能的JDK。GraalVM可以提前将Java应用程序编译成可执行的二进制文件(GraalVM原生镜像)。与运行在Java虚拟机(JVM)上的应用程序相比,这些二进制文件更小,启动速度更快,在没有预热的情况下提供峰值性能,并且使用更少的内存和CPU。
GraalVM减少了应用程序的攻击面。它从应用程序二进制文件中排除未使用的类、方法和字段。它将反射和其他动态Java语言特性限制为仅在构建时使用。它不会在运行时加载任何未知代码。
GraalVM的原生镜像是一个完整的、特定于平台的可执行文件,不需要java的运行环境即可运行。
GraalVM与原生JVM的区别
GraalVM提前将java代码编译成机器码,这就导致了其与原生的java运行环境有一些差异。主要的区别是:
-
应用程序的静态分析是在构建时从主程序的入口点执行的,也就是会扫描所有主程序可以访问的方法。所以,在创建可执行二进制文件时无法访问的代码将被删除,并且不会成为可执行文件的一部分。
-
GraalVM不能直接感知代码中的动态元素,必须告诉它反射、资源、序列化和动态代理。
-
应用程序类路径在构建时是固定的,不能更改。
-
没有延迟类加载,可执行文件中的所有内容将在启动时加载到内存中。
Spring Ahead-of-Time(AOT) Processing
基于GraalVM与原生JVM的区别,在构建生成原生镜像之前,Spring会执行一些提前处理,并生成GraalVM可以使用的额外资源。Spring AOT处理生成的额外资源通常包含:
-
Java源代码
-
字节码(用于动态代理等) :
-
GraalVM JSON提示文件:
-
资源提示(Resource -config.json)
-
反射提示(reflect-config.json)
-
序列化提示(Serialization -config.json)
-
Java代理提示(Proxy -config.json)
-
JNI提示(JNI -config.json)
-
Java源代码
//原生的java源代码
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
原生的spring ioc 容器,是扫描指定包下的所有文件,首先生成BeanDefinition
,然后再利用反射生成Bean。Spring Ahead-of-Time Processing 将BeanDefinition
的生成提前,MyConfiguration
类生成的Java 源代码示例如下:
/**
* Bean definitions for {@link MyConfiguration}. 经过spring aot 处理得到的java源代码
*/
public class MyConfiguration__BeanDefinitions {
/**
* Get the bean definition for 'myConfiguration'.
*/
public static BeanDefinition getMyConfigurationBeanDefinition() {
Class<?> beanType = MyConfiguration.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(MyConfiguration::new);
return beanDefinition;
}
/**
* Get the bean instance supplier for 'myBean'.
*/
private static BeanInstanceSupplier<MyBean> getMyBeanInstanceSupplier() {
return BeanInstanceSupplier.<MyBean>forFactoryMethod(MyConfiguration.class, "myBean").withGenerator(
(registeredBean) -> registeredBean.getBeanFactory().getBean(MyConfiguration.class).myBean());
}
/**
* Get the bean definition for 'myBean'.
*/
public static BeanDefinition getMyBeanBeanDefinition() {
Class<?> beanType = MyBean.class;
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType);
beanDefinition.setInstanceSupplier(getMyBeanInstanceSupplier());
return beanDefinition;
}
}
当使用Maven时,生成的Java 源代码可以在
target/spring-aot/main/sources
中找到,使用Gradle时可以在build/ Generated /aotSources
中找到。
字节码(用于动态代理等)
spring AOP生成代理类使用了直接生成字节码的cglib库。
当应用程序在JDK上运行时,代理类在应用程序运行时动态生成。在创建原生镜像时,需要在构建时创建这些代理,以便GraalVM可以包含它们。
生成的字节码文件可以在Maven的
target/spring-aot/main/classes
和Gradle的build/generated/aotClasses
中找到它们。
GraalVM JSON提示文件
Spring AOT Processing还将生成GraalVM使用的提示文件。提示文件一般是JSON数据,这些数据描述了GraalVM应该如何处理通过直接检查代码无法理解的事情,GraalVM生成Native image时会自动拾取它们。
当使用Maven时,生成的GraalVM JSON提示文件可以在
target/spring-aot/main/resources
中找到,而使用Gradle时可以在build/ Generated /aotResources
中找到。
如果我们需要为反射、资源、序列化、代理使用等提供一些自定义的提示,可以使用RuntimeHintsRegistrar API
。创建一个实现RuntimeHintsRegistrar
接口的类,然后对提供的RuntimeHints
实例进行适当的调用。然后,在任何@Configuration
类(例如@SpringBootApplication
注解)上使用@ Importuntimehints
来激活这些提示。
public class MyRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// Register method for reflection
Method method = ReflectionUtils.findMethod(MyClass.class, "sayHello", String.class);
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
// Register resources
hints.resources().registerPattern("my-resource.txt");
// Register serialization
hints.serialization().registerType(MySerializableClass.class);
// Register proxy
hints.proxies().registerJdkProxy(MyInterface.class);
}
}
内嵌的配置属性失效
反射提示是由Spring AOT引擎为配置属性自动创建的。然而,自定义类的嵌套配置属性必须用@NestedConfigurationProperty注解,否则它们将不会被检测到,也不会被绑定。
测试
mvn springboot:process-aot # 对应Spring Ahead-of-Time Processing 的过程
mvn -Pnative native:build -f pom.xml # 编译生成可执行文件(native image)
原生的Java | GraalVM | |
---|---|---|
编译耗时 | 3.916s | 2min11s |
启动耗时 | 0.88s | 0.055s |
可以看到GraalVM Native Image 启动的耗时比原生的Java快了有一个数量级,但编译的耗时比较长。
原生的JVM 编译指的是由源代码生成jar包的过程,而GraalVM 是指源代码生成可执行文件。
此部分参考:https://docs.spring.io/spring-boot/docs/3.0.0/reference/html/native-image.html#native-image.introducing-graalvm-native-images.understanding-aot-processing.source-code-generation
springboot3.0 的demo的源码,主要测试了反射、AspectJ还有@NestedConfigurationProperty注解。地址:https://gitee.com/ji-hang/springboot3-demo