JDK19中有用的新特性
Java Record
// 声明Student Record
public record Student(Integer id, String name){
}
// 创建Record对象
Student student = new Student(001, "John");
Record和Class相比,有以下特点:
首先Record
是public
访问器,带有全部参数的构造方法,且自带toString()
,hashCode()
,equals()
方法;
其次是隐式的final
类,不能被继承,属性也是final
,通过构造方法创建后不可修改;
最后,没有set()
,get()
方法,不能声明实例属性,能声明static
属性。
public record Student(Integer id, String name){
// 紧凑构造方法
public Student {}
// 自定义构造方法
public Student(String name) {
this(null, name);
}
}
除了全部参数的构造方法,还提供紧凑构造方法和自定义构造方法。
Switch
int week = 2;
String memo = "";
switch (week) {
case 1,7 -> memo = "weekend";
case 2,3,4,5,6 -> memo = "workday";
default -> throw new IllegaArgumentExcepting("Ineffective date");
}
支持使用箭头->
,->
和:
不能混用。
int week = 2;
String memo = switch (week) {
case 1,7 : yield "weekend";
case 2,3,4,5,6 : yield = "workday";
default -> throw new IllegaArgumentExcepting("Ineffective date");
}
使用yield
关键字返回结果。
Text Block
// 第一行必须是三个双引号
String colors = """
red
green
blue
""";
colors.indent(4);
文本块在使用时和普通字符串没有区别,但文本块中的缩进会被自动去除,要保留左侧的缩进,需要使用Tab键
按需移动或者使用indent()
方法。
另外还可以使用formatted()
方法进行格式化;使用stripIndent()
方法删除每行开头和结尾的空格;使用translateEscapes()
转义字面量。
Var
var num = 0;
var name = "John";
var customer = getCuntomer();
var是一个保留字不是关键字,用于声明局部变量。必须有初值。
能够代替显示类型,让代码简洁,但也降低了程序的可读性。
sealed
// 声明sealed类
public sealed class Shape permits Circle, Square, Rectangle {
}
// 声明子类
// final 子类不能被继承,依然是密封的
public final class Circle extends Shape {
}
// sealed 子类也是密封类,仍需要指定子类实现
public sealed class Square extends Shape permits RoundSquare {
}
// non-sealed 子类非密封类,可以被任意类继承
public non-sealed class Rectangle extends Shape {
}
public class Line extends Rectangle {
}
sealed类主要特点是限制继承,拒绝无限的扩张。
Spring Boot的特性
1创建独立的Spring应用程序,并且内嵌Web容器;
2提供自动化配置starter功能,简化Maven配置;
3没有代码生成,无需XML配置就能尽可能地自动配置好Spring和第三方框架;
4开箱即用,使用脚手架创建项目后,所有基础配置就已经完成;
5约定优于配置,定义了常用类,包的位置和结构,代码不需做任何调整,项目就能按照预期运行。
与Spring,Spring Cloud的关系
Spring Boot和Spring都可以创建Spring应用程序,不同的是Spring Boot消除了设置Spring应用程序所需的XML配置,更快更高效更易用。
Spring Cloud是一些列框架的有序组合,提供了快速构建分布式系统中常用工具。而Spring Boot是每个独立的微服务的平台。
Spring Boot3中的变化
1.JDK支持版本17-20
2.更新Servlet6.0规范
3.支持Jackson2.14
4.SpringMVC默认使用PathpatternParser,删除过时的FreeMarker和JSP支持
5.对第三方库更新版本支持
6.底层依赖从Java EE迁移到Jakarta EE API。
7.支持GraalVM原生镜像,将Java应用编译为本机代码,提供显著的内存和气动性能改善。
8.支持Ahead Of Time(运行前编译)。
9.SpringHttp客户端提供基于Micrometer的可观察性跟踪服务,记录服务运行状态
10.其他
使用Spring Boot
从spring-boot-starter-parent开始
starter是一组依赖描述,在应用中加入starter依赖就可以获取Spring相关技术的一站式的依赖和版本。通过starter能够快速启动并运行项目。
starter包含:
依赖坐标和版本;
传递依赖的坐标和版本;
配置类和配置项
// pom.xml
// 在parent标签中指定,表示继承父项目
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
</parent>
// 在依赖管理中单独加入依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
<dependencyManagement>
可以使用两种方式加入Spring Boot提供的父项目,从spring-boot-starter-parent
继承获得合理的默认值和完整的依赖树,从而快速建立一个Spring Boot项目。
父项目提供以下功能:
1.JDK的基准版本;
2.源码使用UTF-8格式编码;
3.公共依赖版本;
4.自动化的资源过滤,默认把src/main/resources目录下的文件进行打包;
5.maven的占位符是@
6.对maven插件完成默认设置
7.其他
application配置文件
Spring Boot同时支持properties和yml格式的配置文件,推荐使用yml。默认名称是application
。
YAML基本语法规则:
大小写敏感;
使用空格缩进表示层级关系;
#
表示注释,只能单行注释。
// application.yml
spring:
redis:
host:localhost
port:6379
// ReadConfigService.java
@Service
public class ReadConfigService {
// 通过@Value注解获取
@Value("${spring.redis.port:8080}")
private String port;
@Autowired
private Environment environment;
// 通过Environment对象获取
public void printConfig() {
this.port = environment.getProperty("server.port");
}
}
通过@Value
注解或者外部化配置的抽象对象Environment
可以读取配置文件。
// db.yml
spring:
datasource:
url: jdbc:mysql://...
username: operation
password: 123456
// redis.yml
spring:
redis:
host:localhost
sort:6379
// application-test.yml
spring:
config:
activate:
on-profile: test
// application-dev.yml
spring:
config:
activate:
on-profile: dev
// application.yml
spring:
config:
import: conf/db.yml, conf/redis.yml
profiles:
active:dev
可以在application.yml中使用import
导入多个配置;
使用on-profile
和active
激活某个环境配置。
绑定Bean
使用@Value
每个成员变量只能绑定一个属性。当属性较多时需要需要重复写很多注解。此时可以使用绑定Bean。将多个配置项绑定到一个Bean,这个强类型的Bean就能访问到所有的配置数据了。
// application.yml
app:
host: Lession07-yml
port: 8002
security:
username: root
password: 12345
// 第三方库属性
administrator:
username: common
password: 123
// ReadConfigBean.java
// com.example.config
@Configuration(proxyBeanMethods=false)
@ConfigurationProperties(prefix="app")
public class ReadConfigBean {
private String host;
private String port;
private Security security;
@ConfigurationProperties("administrator")
@Bean
public Security createAdministrator() {
return new Administrator();
}
}
// group-info.properties
group.name=testGroup
group.leader=John
group.member=10
// GroupConfigBean.java
// com.example.config
@Configuration
@ConfigurationProperties(prefix="group")
@PropertySource(Value="classpath:/group-info.properties")
public class GroupConfigBean {
private String name;
private String leader;
private String member;
}
// Application.java
@ConfigurationPropertiesScan({"com.example.config"})
@SpringBootApplication
public class Application {
...
}
基本原则是定义标准的Java Bean,要有无参构造方法并且包含属性的访问器。配合@ConfigurationProperties
注解一起使用。注解中的prefix
属性可以写前缀。
@ConfigurationProperties
注解还需要@EnableConfigurationProperties
或@ConfigurationPropertiesScan
才能起作用,所以最后在启动类上使用扫描注解@ConfigurationPropertiesScan
,声明所在的包就完成了。
除了读取简单的属性和集合提供的属性外,将嵌套对象声明为成员变量后,也可以读取到嵌套的属性。
有时候需要读取第三方库的属性,此时需要使用@Bean
注解,将第三方库中的类注入到绑定Bean中,配合@ConfigurationProperties
注解,就能读取了。
还可以读取指定的配置文件。具体做法除了使用@ConfigurationProperties
注解外,还需要使用@Configuration
和@PropertySource
注解,指定文件路径。
自动配置
自动配置的注解是@EnableAutoConfiguration
。这个注解也被标注在@SpringBootApplication
注解上,所以通常是由@SpringBootApplication
注解带入。
@EnableAutoConfiguration
注解源码上标注了@Import(AutoConfigruationImportSelector.class)
,这行代码意味着AutoConfigruationImportSelector
这个对象也被导入进了IOC容器。
AutoConfigruationImportSelector
的作用是导入自动配置类,从META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
读取到要加载的配置类列表。这个文件在2.7版本之前是spring.factories
。
另外框架根据pom.xml
中配置的xxxstarter
启动器,找到相关的xxxAutoConfiguration
,然后通过@import
注解找到要加载的配置类列表。
最终框架把以上所有的配置类加载到容器中,根据条件注解判定配置是否生效。
SpringBoot中的Spring MVC
控制器 Controller
@Controller
注解被声明为@Component
,控制器对象本质就是被Spring容器管理的Bean对象。
创建普通的类,定义public方法,类上注解@Controller
或者@RestController
,这个类就是一个控制器对象。
@RestController
表示@Controller + @ResponseBody
。
@ResponseBody
表示将返回值序列化输出。如果返回值是字符串,直接将字符串写到客户端;如果是一个对象就转化成json串返回。
类上或者方法上注解@RequestMapping
和一个Uri
,用于将web请求映射到控制器类的方法,表示这个Uri
由这个类或者方法处理。
参数上常用@RequestParam
和@RequestBody
这两个注解。
请求参数要与形参一一对应,@RequestParam
用来接收前台传来的键值对参数,@RequestBody
用来接收前台传来的对象,也就是json数据。这两个注解可以一起使用。
同时还可以使用字符流,@RequestHeader
注解等接收参数。
使用Java Bean Validation
对参数进行验证。
在Bean的属性上,加入JSR-303
注解,实现验证规则的定义。通过BindingResult
获取验证结果。还可以定义组接口,进行分组操作。
常用的JSR-303
注解有@Null, @NotNull, @AssertTrue, @AssertFalse
等。
模型 Model
简单来说就是传给浏览器的数据,Model
对象,ModelAndView
对象或者json
字符串都是模型。
视图 View
视图用于展示数据,所有的视图类都实现了org.springframework.web.servlet.View
接口。
常用的返回值可以是String
对象,自定义Object
对象,ResponseEntity
对象(包含HttpStatus Code和应答数据的结合体)。
SpringMVC的请求流程
在SpringMVC框架中,DispatcherServlet
是核心对象。作为中央调度器,负责接收对所有Controller
的请求,调用Controller
中的方法处理业务逻辑,最终将返回值经过视图
处理响应给浏览器。
应用了门面设计模式,是SpringMVC对外的入口,解耦其他组件,使其他组件间彼此没有关联。实现了ApplicationContextAware接口,所有可以访问容器中的所有对象。
具体流程如下:
DispatcherServlet
接收到客户端发送的请求后,调用HandlerMapping
根据URI找到对应的handler
和拦截器
,组装成HandlerExecutionChain
对象并返回;
接着DispatcherServlet
根据返回值调用HanderAdapter
进行接口转换,调用具体的handler
中的方法并返回ModelAndView
对象;
DispatcherServlet
调用HandlerExceptionResolver
处理异常,如果有异常则返回包含异常的ModelAndView
对象;
然后DispatcherServlet
调用ViewResolver
解析ModelAndView
对象,返回View
对象;
最终DispatcherServlet
将得到的View
对象进行渲染,填充Model
中的数据到request
域,返回给客户端响应结果。
SpringMVC中的自动配置
WebMvcAutoConfiguration
是SpringMVC的自动配置类,根据类上的注解,有以下几个代表性的配置类。
1.@DispatcherServletAutoConfiguration
自动配置DispatcherServlet
:创建DispatcherServlet
,并注册到容器中,设置load-on-startup = -1
;创建MultipartResolver
;它的绑定Bean是WebMvcProperties
。
2.@WebMvcConfigurationSupport
:创建HandlerMapping, HandlerAdapter, HandlerExceptionResolver
接口的多个实现对象;PathMatchConfigurar, ContentNegotiationManager, OptionalValidatorFactoryBean, HttpMessageConverter
等实例。
在SpringMVC中,Handler
的表现形式有多种,所以处理请求的方式也不同。通过HandlerMapping
实现类包装Handler
,再由HandlerAdapter
接口的实现类调用handler
。
平时用的最多的@RequestMapping
方式是通过RequestMappingHandlerMapping, RequestMappingHandlerAdapter, DefaultHandlerExceptionResolver, ExceptionHandlerExceptionResolver
这几个组件实现的。
3.ValidationAutoConfiguration
:配置JSR-303
验证器
4.ServletWebServerFactoryAutoConfiguration
:SpringBoot根据classpath
上存在的类,判断当前应用使用的是Tomcat/Jetty/Undertow
中的哪个服务器没从而决定定义相应的Web服务器。
绑定Bean是ServerProperties
。
使用Servlet
通过注解@WebServlet
定义Servlet对象
@WebServlet(urlPatterns="/helloServlet", name="HelloServlet")
public class HelloServlet extends HttpServlet {
...
}
@ServletComponentScan(basePackages="com.example.servlet")
@SpringBootApplication
public calss Application {
...
}
通过ServletRegistrationBean
注册Servlet对象
@Configuration
public class WebAppConfig {
@Bean
public ServletRegistrationBean addServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(new LoginServlet());
registrationBean.addUrlMappings("/user/login");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}
使用Filter
通过@WebFilter
定义Filter对象
@WebFilter(urlPatterns = "/*")
public class LogFilter implements Filter {
...
}
@ServletComponentScan(basePackages="com.example.filter")
@SpringBootApplication
public calss Application {
...
}
通过FilterRegistrationBean
注册Filter对象
@Bean
public FilterRegistrationBean addFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new Filter());
filterRegistration.addUrlPatterns("/*");
filterRegistration.setOrder(1);
return filterRegistration;
}
使用框架中的Filter
@Bean
public FilterRegistrationBean addOtherFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
...
filterRegistration.setFilter(filter);
...
return filterRegistration;
}
使用Listener
使用@WebListener
定义Listener
@WebListener
public class MySessionListener implements HttpSessionListener {
...
}
@ServletComponentScan(basePackages="com.example.listener")
@SpringBootApplication
public calss Application {
...
}
通过ServletListenerRegistrationBean
注册Filter对象
@Bean
public ServletListenerRegistrationBean addListener() {
ServletListenerRegistrationBean registration = new ServletListenerRegistrationBean();
registration.setListener(new Filter());
...
return registration;
}
使用WebMvcConfigurar定制SpringMVC
定义一个配置类实现接口,并@Override
需要的方法
常用的功能有页面跳转控制器,数据格式化,拦截器等
// 实现类
public class DeviceFormatter implements Formatter<DeviceInfo> {
...
}
public class AuthInterceptor implements HandlerInterceptor [
...
}
@Configuration
public class MvcSettings implements WebMvcConfigurar {
// 页面跳转控制器
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/welcome").setViewName("index");
}
// 数据格式化
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DeviceFormatter());
}
// 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor());
.order(1)
.addPathPatterns("/**");
.excludePathPatterns("/article/query");
}
}
使用MultipartResolver上传文件
MultipartResolver
的内部实现类是StandardServletMultipartResolver
,封装了Part
接口,实现基于Servlet3.0
规范的文件上传。
@Controller
public class UploadFileController {
// 使用MultipartResolver读取POST请求数据
@PostMapping("/uploadByMultipartResolver")
public String uploadM(@RequestParam("uploadFile") MultipartFile multipartFile) {
...
}
// 使用Part接口实现文件上传
@PostMapping("/uploadByPartInterface")
public String uploadP(HttpServletRequest request) {
Part part = request.getParts();
...
}
}
使用HandlerExceptionResolver处理全局异常
常用的注解:
@ControllerAdvice, @RestControllerAdvice, @ExceptionHandler
@ControllerAdvice
public class GlobalExceptionHandler {
// 处理全局异常
@ExceptionHandler({Exception.class})
public String handlerException(Exception e, Model model) {
...
}
// 校验JSR-303规范验证参数的结果
@ExceptionHandler({BindException.class})
public Map<String, Object> handleJSR303Exception(BindException e) {
BindingResult result = e.getBindingResult();
...
}
}
SpringBoot3中支持使用ProblemDetail增强异常处理
在RFC7807
规范中,对错误信息做了更规范的定义,使异常信息更完善。ProblemDetail
类封装了标准字段和扩展字段,作为返回值输出RFC7807
规范的字段。
除了ProblemDetail
类,还可以使用ErrorResponse
处理异常。包含异常信息和完整的REF7807
格式的ProblemDetail
正文。ErrorResponseException
是ErrorResponse
接口的一个实现。通过继承ErrorResponseException
类,那么这个异常会交给框架处理。
public class BookNotFoundException extends RuntimeException {
...
}
public class caculErrorException extends RuntimeException {
...
}
@RestControllerAdvice
public class GlobalExceptionHandler {
// 使用ProblemDetail处理异常
@ExceptionHandler({BookNotFoundException.class})
public ProblemDetail handleBookNotFoundException(BookNotFoundException e) {
ProblemDetail detail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
// 设置标识错误类型的URI
detail.setType()
...
// 可以设置自定义字段
detail.setProperty("timestamp", Instant.now());
return detail;
}
// 直接使用ErrorResponse作为异常处理方法的返回值
@ExceptionHandler({caculErrorException.class})
public ErrorResponse handleCaculErrorException (CaculErrorException e) {
ErrorResponse error = new ErrorResponseException(HttpStatus.INTERNAL_SERVER_ERROR, e);
return error;
}
// 通过继承ErrorResponseException类,将异常交给框架处理
public class BookNotFoundException extends ErrorResponseException {
...
}
远程访问
SpringBoot3提供了多种远程访问技术。其中基于HTTP协议的远程访问应用最广泛。首先定义一个接口,使用@HttpExchange
提供HTTP服务;通过生成HTTP客户端WebClient
的代理对象,实现远程访问。WebClient
是基于Reactor
的非阻塞异步请求客户端。
声明式
// POJO
public class Schedule {
...
}
// 声明服务接口
public interface ScheduleService {
@GetExchange("/schedules/{id}")
Schedule getScheduleById(@PathVariable Integer id);
}
// 创建代理类
@Coufiguration(proxyBeanMethods = false)
public class HttpConfiguration {
@Bean
public ScheduleService requestService() {
WebClient webClient = WebClient.builder().baseUrl("https://jsonplaceholder.typicode.com/").build();
return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build().createClient(ScheduleService.class);
}
// 调用
public class CallHttpClient {
@Resource
private ScheduleService service;
public Schedule getSchedule(Integer id) {
return service.getScheduleById(id);
}
}
使用接口组合
// POJO
public class Schedule {
...
}
// 声明服务接口
@HttpExchange(url = "https://jsonplaceholder.typicode.com/")
public interface ScheduleService {
@GetExchange("/schedules/{id}")
Schedule getScheduleById(@PathVariable Integer id);
}
// 创建代理类
@Coufiguration(proxyBeanMethods = false)
public class HttpConfiguration {
@Bean
public ScheduleService requestService() {
WebClient webClient = WebClient.create();
return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build().createClient(ScheduleService.class);
}
// 自定义配置
@Bean
public ScheduleService selfDefiningService() {
HttpClient client = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000);
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(client ));
return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build().createClient(ScheduleService.class);
}
// 调用
public class CallHttpClient {
@Resource
private ScheduleService service;
public Schedule getSchedule(Integer id) {
return service.getScheduleById(id);
}
}
AOT和GraalVM
JVM提高执行速度的技术有JIT
实时编译技术,和在Java9中作为实验性功能的AOT
预编译技术。
JIT
会发现一些运行频繁的热点代码,并将热点代码编译成本地代码。这个过程是在程序执行中进行的,所以完成优化需要时间。
AOT
是静态的,通过Native Image Builder
这个工具,预先将代码编译为独立可执行文件Native Image
本机镜像。这个编译过程会在项目构建期间完成。
目前,SpringBoot3支持使用GraalVm
中的AOT
技术编译,并执行镜像文件。