SpringBoot3新特性

本篇文章参考尚硅谷springboot3课程: https://www.bilibili.com/video/BV1Es4y1q7Bf?p=94&vd_source=d6deb2b69988de2ae72087817e5143d7
原版笔记:
https://www.yuque.com/leifengyang/springboot3/xy9gqc2ezocvz4wn

1.自动配置包位置变化

现在指定自动配置类放在了下面这个AutoConfiguration.imports文件里面:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
springboot2是放在:
META-INF/spring.factories

2.Java EE API 迁移到 Jakarta EE变体

Spring Boot 3已经将所有Java EE API迁移到其等效的Jakarta EE变体,对于大多数用户来说,这意味着需要将任何javax导入替换为jakarta。例如:javax.servlet.Filter 将被替换为 jakarta.servlet.Filter.

3.新特性 - ProblemDetails、 函数式Web

ProblemDetails: 定义新的错误信息返回格式
遵循RFC 7807: https://www.rfc-editor.org/rfc/rfc7807

problemdetails默认是关闭的,可以在application.properties里面打开:
spring.mvc.problemdetails.enabled=true

原理:
WebMvcAutoConfiguration 里面定义了–> ProblemDetailsErrorHandlingConfiguration里面又返回了 --> ProblemDetailsExceptionHandler

@Configuration(proxyBeanMethods = false)
//配置过一个属性 spring.mvc.problemdetails.enabled=true, 才生效
@ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true")
static class ProblemDetailsErrorHandlingConfiguration {

    @Bean
    @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
    ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
        return new ProblemDetailsExceptionHandler();
    }

}

1)ProblemDetailsExceptionHandler 是一个 @ControllerAdvice集中处理系统异常
2)它会处理以下异常。如果系统出现以下异常,会被SpringBoot支持以 RFC 7807规范方式返回错误数据

@ExceptionHandler({
        HttpRequestMethodNotSupportedException.class, //请求方式不支持
        HttpMediaTypeNotSupportedException.class,
        HttpMediaTypeNotAcceptableException.class,
        MissingPathVariableException.class,
        MissingServletRequestParameterException.class,
        MissingServletRequestPartException.class,
        ServletRequestBindingException.class,
        MethodArgumentNotValidException.class,
        NoHandlerFoundException.class,
        AsyncRequestTimeoutException.class,
        ErrorResponseException.class,
        ConversionNotSupportedException.class,
        TypeMismatchException.class,
        HttpMessageNotReadableException.class,
        HttpMessageNotWritableException.class,
        BindException.class
    })

例子: 出现Method Not Allowed 异常:
1)不开启Problemdetails的时候,默认响应错误的json。状态码 405

{
    "timestamp": "2023-04-18T11:13:05.515+00:00",
    "status": 405,
    "error": "Method Not Allowed",
    "trace": "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' is not supported\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:265)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:441)\r\n\tat org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:382)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:126)\r\n\tat org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.getHandlerInternal(RequestMappingInfoHandlerMapping.java:68)\r\n\tat org.springframework.web.servlet.handler.",
    "message": "Method 'POST' is not supported.",
    "path": "/list"
}

2)开启ProblemDetails返回, 使用新的MediaType
返回的Content-Type是application/problem+json,而且可以自己扩展返回的数据

{
    "type": "about:blank",
    "title": "Method Not Allowed",
    "status": 405,
    "detail": "Method 'POST' is not supported.",
    "instance": "/list"
}

函数式Web:
SpringMVC 5.2 以后 允许我们使用函数式的方式,定义Web的请求处理流程。
Web请求处理的方式:
1.以前使用@Controller + @RequestMapping:耦合式 (路由、业务耦合)
2.现在可以使用函数式Web:分离式(路由、业务分离)

使用函数式Web的步骤:
1.实现配置类给容器中放RouterFunctions bean
2.每个业务准备一个自己的Handler


/**
 * 场景:User RESTful - CRUD
 * ● GET /user/1  获取1号用户
 * ● GET /users   获取所有用户
 * ● POST /user  请求体携带JSON,新增一个用户
 * ● PUT /user/1 请求体携带JSON,修改1号用户
 * ● DELETE /user/1 删除1号用户
 */
@Configuration
public class WebFunctionConfig {

    /**
     * 函数式Web步骤:
     * 1、给容器中放一个Bean:类型是 RouterFunction,集中所有路由信息
     * 2、每个业务准备一个自己的Handler
     *
     *
     * 核心四大对象
     * 1、RouterFunction: 定义路由信息。发什么请求,谁来处理
     * 2、RequestPredicate:定义请求规则:请求谓语。请求方式(GET、POST)、请求参数
     * 3、ServerRequest:  封装请求完整数据
     * 4、ServerResponse: 封装响应完整数据
     */
    @Bean
    public RouterFunction<ServerResponse> userRoute(UserBizHandler userBizHandler/*这个会被自动注入进来*/){

        return RouterFunctions.route() //开始定义路由信息
                .GET("/user/{id}", RequestPredicates.accept(MediaType.ALL), userBizHandler::getUser)
                .GET("/users", userBizHandler::getUsers)
                .POST("/user", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::saveUser)
                .PUT("/user/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), userBizHandler::updateUser)
                .DELETE("/user/{id}", userBizHandler::deleteUser)
                .build();
    }

实现Handler:

/**
 * @author lfy
 * @Description 专门处理User有关的业务
 * @create 2023-04-18 21:55
 */
@Slf4j
@Service
public class UserBizHandler {

    /**
     * 查询指定id的用户
     * @param request
     * @return
     */
    public ServerResponse getUser(ServerRequest request) throws Exception{
        String id = request.pathVariable("id");
        log.info("查询 【{}】 用户信息,数据库正在检索",id);
        //业务处理
        Person person = new Person(1L,"哈哈","[email protected]",18,"admin");
        //构造响应
        return ServerResponse
                .ok()
                .body(person);
    }

    /**
     * 获取所有用户
     * @param request
     * @return
     * @throws Exception
     */
    public ServerResponse getUsers(ServerRequest request) throws Exception{
        log.info("查询所有用户信息完成");
        //业务处理
        List<Person> list = Arrays.asList(new Person(1L, "哈哈", "[email protected]", 18, "admin"),
                new Person(2L, "哈哈2", "[email protected]", 12, "admin2"));

        //构造响应
        return ServerResponse
                .ok()
                .body(list); //凡是body中的对象,就是以前@ResponseBody原理。利用HttpMessageConverter 写出为json
    }


    /**
     * 保存用户
     * @param request
     * @return
     */
    public ServerResponse saveUser(ServerRequest request) throws ServletException, IOException {
        //提取请求体
        Person body = request.body(Person.class);
        log.info("保存用户信息:{}",body);
        return ServerResponse.ok().build();
    }

    /**
     * 更新用户
     * @param request
     * @return
     */
    public ServerResponse updateUser(ServerRequest request) throws ServletException, IOException {
        Person body = request.body(Person.class);
        log.info("保存用户信息更新: {}",body);
        return ServerResponse.ok().build();
    }

    /**
     * 删除用户
     * @param request
     * @return
     */
    public ServerResponse deleteUser(ServerRequest request) {
        String id = request.pathVariable("id");
        log.info("删除【{}】用户信息",id);
        return ServerResponse.ok().build();
    }
}

4. GraalVM 与 AOT - 将应用编译成可执行文件,飞速运行

====
Complier 与 Interpreter对比
AOT 与 JIT 对比

====
jvm执行java代码流程: jvm里面会判断一段代码是解释执行还是编译执行,如果是编译执行,就编译成机器码并且放到缓存里面,下次用到的时候,直接取;如果是解释执行,就直接解释执行,并且统计解释执行的次数,当次数达到某个阈值的时候,就触发编译,将机器猫放在缓存中
SpringBoot3新特性_第1张图片
更详细的图:
方法执行的时候,判断是否已经有了编译好的机器码,如果是,直接从缓存中取出来执行;如果不是,进行热点探测,统计热点代码执行次数,是否超过阈值(这个阈值可以通过compileThreshold设置),
没有超过就解释执行,超过了就进行编译,编译的时候可以设置是否同步编译,如果是同步编译就是先编译好了再执行,非同步编译就是先解释执行完这块代码,然后提交一个编译请求,最后将编译好的机器码放在缓存中
SpringBoot3新特性_第2张图片

====
(了解)JVM编译器: C1和C2

(了解)分层编译: Java 7开始引入了分层编译(Tiered Compiler)的概念
总的来说,C1的编译速度更快,C2的编译质量更高,分层编译的不同编译路径,也就是JVM根据当前服务的运行情况来寻找当前服务的最佳平衡点的一个过程。从JDK 8开始,JVM默认开启分层编译。

====
存在的问题:
1.java应用如果用jar,解释执行,热点代码才编译成机器码;初始启动速度慢,初始处理请求数量少。
2.大型云平台,要求每一种应用都必须秒级启动。每个应用都要求效率高。
希望的效果:
1.java应用也能提前被编译成机器码,随时急速启动,一启动就急速运行,最高性能
2.编译成机器码的好处:
1)另外的服务器还需要安装Java环境
2)编译成机器码的,可以在这个平台 Windows X64 直接运行。

====
原生镜像概念:native-image(就是编译成机器码、本地镜像)
1.最终的目标: 把应用打包成能适配本机平台的可执行文件(机器码、本地镜像)

GraalVM
https://www.graalvm.org/
GraalVM是一个高性能的JDK,旨在加速用Java和其他JVM语言编写的应用程序的执行,同时还提供JavaScript、Python和许多其他流行语言的运行时
GraalVM提供了两种运行Java应用程序的方式:
1.在HotSpot JVM上使用Graal即时(JIT)编译器 --> 和C2编译器类似,具有C2编译器的功能,常用于代替C2,一般不用,常见的jvm实现里面的C2已经很好了
2.作为预先编译(AOT)的本机可执行文件运行(本地镜像)。 --> 将程序编译成本机可执行文件
GraalVM的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外部语言调用的成本。

GraalVM基于HotSpot,替换了里面的编译器,不仅支持java系列的语言,还想要支持其它的语言,比如js,python等
SpringBoot3新特性_第3张图片

====
跨平台提供原生镜像原理
GraalVM提供native-image工具,native-image工具使用不同平台上的编译工具,将你的应用编译成可执行文件(比如windows的.exe),然后这些可执行文件就能够在相同平台的不同机器上跑了
SpringBoot3新特性_第4张图片
详细步骤参考雷老师的笔记

native-image可以本地安装然后编译jar包到可执行文件,springBoot也集成了,既然本地能够将应用编译成可执行文件了,为什么springBoot还要额外支持呢?
1.动态能力损失: 运行时才能确定的代码
并不是所有的Java代码都能支持本地打包,例如反射的代码(反射动态获取构造器,反射创建对象,反射调用一些方法),需要做一些额外处理,告诉graalvm反射会用到哪些方法、构造器
SpringBoot做了这部分: 它提供了一些注解:提前告知 graalvm 反射会用到哪些方法、构造器
2.配置文件损失 --> 配置文件时放在jar包内的,在编译成可执行文件的时候,配置文件不能被编译放在最后的可执行文件中
SpringBoot做了这部分: 额外处理(配置中心):提前告知 graalvm 配置文件怎么处理

不是所有的代码都能打包编译: 二进制里面不能包含的,不能动态的都得提前处理 --> SpringBoot都做好了

/**
 * 打包成本地镜像:
 *
 * 1、打成jar包:  注意修改 jar包内的 MANIFEST.MF 文件,指定Main-Class的全类名
 *        - java -jar xxx.jar 就可以执行。
 *        - 切换机器,安装java环境。默认解释执行,启动速度慢,运行速度慢
 * 2、打成本地镜像(可执行文件):
 *        - native-image -cp  你的jar包/路径  你的主类  -o 输出的文件名
 *        - native-image -cp boot3-15-aot-common-1.0-SNAPSHOT.jar com.atguigu.MainApplication -o Demo
 *
 * 并不是所有的Java代码都能支持本地打包;
 * SpringBoot保证Spring应用的所有程序都能在AOT的时候提前告知graalvm怎么处理?
 *
 *   - 动态能力损失:反射的代码:(动态获取构造器,反射创建对象,反射调用一些方法);
 *     解决方案:额外处理(SpringBoot 提供了一些注解):提前告知 graalvm 反射会用到哪些方法、构造器
 *   - 配置文件损失:
 *     解决方案:额外处理(配置中心):提前告知 graalvm 配置文件怎么处理
 *   - 【好消息:新版GraalVM可以自动进行预处理,不用我们手动进行补偿性的额外处理。】
 *   二进制里面不能包含的,不能动态的都得提前处理;
 *
 *   不是所有框架都适配了 AOT特性;Spring全系列栈适配OK
 *
 *  application.properties
 *  a(){
 *      //ssjsj  bcde();
 *      //提前处理
 *  }
 */

详细步骤参考雷老师的笔记

两条命令:
1、运行aot提前处理命令:mvn springboot:process-aot --> IDEA meavn插件里面也有
2、运行native打包:mvn -Pnative native:build --> IDEA meavn插件里面也有

你可能感兴趣的:(SpringBoot,SpringBoot3,SpringBoot3新特性,新特性)