本篇文章参考尚硅谷springboot3课程: https://www.bilibili.com/video/BV1Es4y1q7Bf?p=94&vd_source=d6deb2b69988de2ae72087817e5143d7
原版笔记:
https://www.yuque.com/leifengyang/springboot3/xy9gqc2ezocvz4wn
现在指定自动配置类放在了下面这个AutoConfiguration.imports文件里面:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
springboot2是放在:
META-INF/spring.factories
Spring Boot 3已经将所有Java EE API迁移到其等效的Jakarta EE变体,对于大多数用户来说,这意味着需要将任何javax导入替换为jakarta。例如:javax.servlet.Filter 将被替换为 jakarta.servlet.Filter.
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();
}
}
====
Complier 与 Interpreter对比
AOT 与 JIT 对比
====
jvm执行java代码流程: jvm里面会判断一段代码是解释执行还是编译执行,如果是编译执行,就编译成机器码并且放到缓存里面,下次用到的时候,直接取;如果是解释执行,就直接解释执行,并且统计解释执行的次数,当次数达到某个阈值的时候,就触发编译,将机器猫放在缓存中
更详细的图:
方法执行的时候,判断是否已经有了编译好的机器码,如果是,直接从缓存中取出来执行;如果不是,进行热点探测,统计热点代码执行次数,是否超过阈值(这个阈值可以通过compileThreshold设置),
没有超过就解释执行,超过了就进行编译,编译的时候可以设置是否同步编译,如果是同步编译就是先编译好了再执行,非同步编译就是先解释执行完这块代码,然后提交一个编译请求,最后将编译好的机器码放在缓存中
====
(了解)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等
====
跨平台提供原生镜像原理
GraalVM提供native-image工具,native-image工具使用不同平台上的编译工具,将你的应用编译成可执行文件(比如windows的.exe),然后这些可执行文件就能够在相同平台的不同机器上跑了
详细步骤参考雷老师的笔记
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插件里面也有