SpringBoot-核心功能

布鸽不鸽 Lv4

二.核心功能

配置文件

yaml

  • 字面量
1
k1: v1
  • 对象:键值对的集合(map,hash,set,object)
1
2
3
4
5
6
k: {k1:v1, k2:v2, k3:v3}
# 或
k:
k1: v1
k2: v2
k3: v3
  • 数组:顺序排列的值(array,list,queue)
1
2
3
4
5
6
k: [v1, v2, v3]
# 或
k:
- v1
- v2
- v3
  • 转义
1
2
3
4
5
# 双引号,将\n作为换行输出
k1: "abc \n def"

# 单引号,将\n作为字符串输出
k2: 'abc \n def'
  • 先加载properties的值,再加载yaml的值

  • yaml覆盖properties的值

自动提示

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

Web开发

SpringMVC自动配置概览

  • 内容协商视图解析器和BeanName视图解析器
  • 静态资源的自动配置
  • 自动注册 Converter,GenericConverter,Formatter
  • 支持 HttpMessageConverters (后面,配合内容协商理解原理)
  • 自动注册 MessageCodesResolver (国际化用)
  • 静态index.html页支持
  • 自定义Favicon
  • 自动使用 ConfigurableWebBindingInitializer,(DataBinder负责将请求数据绑定到JavaBean上)

静态资源访问

官方文档:Web/Servlet Web Application/The Spring Web MVC Framework/Static Content

  • 静态资源目录
    • /static
    • /public
    • /resources
    • /META-INF/resources

请求先看Controller能不能处理

不能处理的交给静态资源处理器

都找不到则404

  • 改变静态资源访问路径(便于拦截器放行静态资源)
1
2
3
spring:
mvc:
static-path-pattern: /resources/**
  • 改变静态资源存放位置
1
2
3
4
spring:
web:
resources:
static-locations: [classpath:/haha/, classpath:/page/]

欢迎页

官方文档:Web/Servlet Web Application/The Spring Web MVC Framework/Welcome Page

  • 静态资源路径下放置index.html
  • controller处理/index请求

static-path-pattern必须为默认的/**才能访问到默认首页,下面源码分析会看到为什么

请求映射

1
2
3
4
5
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
// @DeleteMapping("/user")
public String deleteUser() {
return "delete";
}
  • 使用HiddenHttpMethodFilter兼容PUT,DELETE等请求(源代码下方有)
1
spring.mvc.hiddenmethod.filter = enabled
  • SpringBoot帮我们注入了HiddenHttpMethodFilter,我们也可以自己注入
1
2
3
4
5
6
7
8
9
10
@Configuration(proxyBeanMethods = false)
public class MyConfig {

@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
filter.setMethodParam("_m"); // 改变"_method"为"_m"
return filter;
}
}

参数注解

1. 普通注解

  • @PathVariable:获取路径变量
1
public String hello(@RequestParam("username") String username) {}
1
2
3
@GetMapping("/car/{carId}/owner/{userId}")
public Map<String, Object> getCar(@PathVariable Integer carId,
@PathVariable Integer userId) {}
1
2
3
// 可以用一个Map一起接收,必须是Map<String, String>,不建议使用
@GetMapping("/car/{carId}/owner/{userId}")
public Map<String, Object> getCar(@PathVariable Map<String, String> map) {}
  • @RequestHeader:获取请求头
1
public Map<String, Object> getHeader(@RequestHeader("User-Agent") String userAgent) {}
1
public Map<String, Object> getHeader(@RequestHeader MultiValueMap<String, String> header) {}
  • @RequestParam:获取请求参数(URL参数,form表单中的参数)
1
2
3
// user?age=12&inters=aaa&inters=bbb
public Map<String, Object> getParam(@RequestParam("age") Integer age,
@RequestParam("inters") List<String> inters) {}
  • @CookieValue:获取cookie值
1
public Map<String, Object> getCookie(@CookieValue("_ga") String ga) {}
1
public Map<String, Object> getCookie(@CookieValue("_ga") Cookie gaCookie) {}
  • @RequestBody:获取请求体(POST)
1
public Map<String, Object> postMethod(@RequestBody String content) {}
  • @RequestAttribute:获取request域属性
1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/goto")
public String goToPage(HttpServletRequest request) {
request.setAttribute("msg", "value1");
return "forward:/success";
}

@ResponseBody
@GetMapping("/success")
public String success(@RequestAttribute("msg") String msg) {
return msg;
}
  • @MatrixVariable:获取矩阵变量(必须有url变量才能解析)
1
2
3
4
5
// url路径:/car/1001;low=34;brand=aaa,bbb,ccc
@GetMapping("/cars/{id}")
public Map<String, Object> carsSell(@MatrixVariable("low") Integer low,
@MatrixVariable("brand") List<String> brand,
@PathVariable("id") String id) {}
1
2
3
4
5
6
7
// 重名如何解决:
// url路径:/boss/1001;age=45/1002;age=23,都有age
@GetMapping("/boss/{bossId}/{empId}")
public Map<String, Object> boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
@PathVariable String bossId,
@MatrixVariable(value = "age", pathVar = "bossId") Integer empAge,
@PathVariable String empId) {

SpringBoot默认禁用了矩阵变量,需手动开启,开启方法见后文源码分析

矩阵变量需要和路径一起看,比如:
/boss/1/2
/boss/1;age=30/2;age=40

  • @ModelAttribute
  • 其他的一些总结

前端传Json对象,使用@RequestParam注解
前端传Json对象的字符串,使用@RequestBody注解

url重写(Cookie被禁用如何使用Session)

使用矩阵变量:/abc;jsessionid=xxxx

2. Servlet API

  • WebRequest
  • ServletRequest
1
public String goToPage(HttpServletRequest request) {}
  • MultipartRequest
  • HttpSession
  • javax.servlet.http.PushBuilder
  • Principal
  • InputStream
  • Reader
  • HttpMethod
  • Locale
  • TimeZone
  • ZoneId
1
public String hello(HttpSession session)

3. 复杂参数

  • Map, Model
1
2
3
4
5
6
7
8
9
10
11
// Map和Model中的数据会被放在request的请求域中(request.setAttribute)
@GetMapping("/params")
public String testParam(Map<String, Object> map,
Model model,
HttpServletRequest request){
map.put("mapKey", "mapValue");
model.addAttribute("modelKey", "modelValue");
request.setAttribute("reqKey", "reqValue");

return "forward:/success";
}
  • Errors/BindingResult
  • RedirectAttributes(重定向携带数据)
  • ServletResponse
  • SessionStatus
  • UriComponentsBuilder
  • ServletUriComponentsBuilder
  • 参数自动封装为对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 姓名: <input name="userName"/> <br/>
* 年龄: <input name="age"/> <br/>
* 生日: <input name="birth"/> <br/>
* 宠物姓名:<input name="pet.name"/><br/>
* 宠物年龄:<input name="pet.age"/>
*/

@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}

@Data
public class Pet {
private String name;
private String age;
}

// 数据绑定:页面提交的请求数据(GET,POST)都可以和对象属性进行绑定
@PostMapping("/saveuser")
public Person saveUser(Person person) {
return person;
}

自定义Converter

1
2
<!--目前的需求-->
宠物: <input name="pet" value="Tom,6">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 在配置类中配置自定义webMvcConfigurer,利用其中addFormatters添加自定义Converter
@Configuration(proxyBeanMethods = false)
public class MyConfig {

@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
if (!source.isEmpty()) {
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
}

可以在WebMvcConfigurer源码中看到用法提示

1
2
3
4
5
6
/**
* Add {@link Converter Converters} and {@link Formatter Formatters} in addition to the ones
* registered by default.
*/
default void addFormatters(FormatterRegistry registry) {
}

响应处理

响应页面

响应数据

  • JSON,XML,xls,音频,图片,自定义协议数据

  • 需要引入依赖

1
2
// web开发场景,已经帮我们引入了jackson
spring-boot-starter-web
1
2
3
4
5
6
7
8
9
10
@Controller
public class ResponseController {

@ResponseBody
@GetMapping("/test/person")
public Person getPerson() {
Person person = new Person();
return person;
}
}

内容协商

  • 引入xml依赖
1
jackson-dataformat-xml
  • 前端不同的请求头会返回不同的格式类型
1
2
Accept=application/xml
Accept=application/json
  • 也可以开启url参数决定内容协商
1
2
3
4
spring:
mvc:
contentnegotiation:
favor-parameter: true
1
2
http://localhost:8088/test/person?format=json
http://localhost:8088/test/person?format=xml
  1. @ResponseBody注解,代表数据要响应出去

  2. 调用RequestResponseBodyMethodProcessor(返回值处理器)处理返回值

  3. 调用各种MessageConverter进行数据的转换

  4. MessageConverter可以支持各种媒体类型数据的操作(读,写)

  5. 内容协商找到合适的MessageConverter

自定义MessageConverter

  1. 自定义MessageConverter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.myweb.converter;

public class MyConverter implements HttpMessageConverter<Person> {

@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}

@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(Person.class);
}

@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/my-type");
}

@Override
public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}

@Override
public void write(Person person,
MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String data = person.getName() + "-" + person.getAge();
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}
  1. 添加自定义MessageConverter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.example.myweb.config;

@Configuration(proxyBeanMethods = false)
public class MyConfig {

@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MyConverter myConverter = new MyConverter();
converters.add(myConverter);
}
};
}
}

(源码)请求参数与请求返回值

  • 结合前面使用看,单独整理

视图解析

  • thymeleaf:现代化,服务端Java模板引擎
  • SpringBoot要打包成Jar包,因此无法使用JSP

基本语法

  • 表达式
语法用途
${…}获取请求域、session域、对象等值
*{…}获取上下文对象值
#{…}获取国际化等值
@{…}生成链接
~{…}jsp:include 作用,引入公共页面片段
  • 字面量

    文本值:’one text’, ‘Another one!’

    数字: 0, 34, 3.0, 12.3

    布尔值: true, false

    空值:null

    变量:one,two,(变量不能有空格)

  • 文本操作

    字符串拼接:+

    变量替换:|The name is ${name}|

  • 数学运算

    运算符:+, -, *, /, %

  • 布尔运算

    运算符:and, or, !, not

  • 比较运算

    比较:>, <, >=, <= (gt, lt, ge, le)

    等式:== , != (eq, ne)

  • 条件运算

    (if) ? (then)
    (if) ? (then) : (else)
    (value) ?: (defaultvalue)

  • 特殊操作

    无操作:_

  • 为某个属性赋值

    th:attr=

1
2
3
4
5
6
7
8
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="submit" th:attr="value=#{subscribe.submit}"/>
</fieldset>
</form>

<!--设置多个值-->
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
`th:xxx=`
1
2
3
<!--替代写法-->
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">

基础配置

  • 引入starter
1
spring-boot-starter-thymeleaf
  • 自动配置好了thymeleaf
1
2
3
4
5
6
@AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class,
TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class })
public class ThymeleafAutoConfiguration { }
  • 自动配置好的策略

    1. 所有thymeleaf的配置值都在ThymeleafProperties
    2. 配置好了ThymeleafViewResolver(视图解析器)
  1. 我们的页面只需放在如下路径中,后缀html
1
2
3
4
5
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
}

基础用法

1
2
3
4
5
6
7
8
9
10
11
12
package com.example.myweb.controller;

@Controller
public class ViewTestController {
@GetMapping("/suc")
public String success(Model model) {
// model中的数据会被放到请求域中
model.addAttribute("msg", "你好thymeleaf");
model.addAttribute("link", "/login");
return "success";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
<a th:href="${link}">link1</a>
<a th:href="@{link}">link2</a>
</body>
</html>

拦截器

  • 编写一个拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.xuedongyun.myadmin.interceptor;

// 配置拦截器拦截哪些请求
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 先判断session中有没有存信息
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser!=null) {
return true;
}
request.setAttribute("msg", "请先登录");
request.getRequestDispatcher("/").forward(request, response);
return false;
}

// 目标方法执行后
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

// 页面渲染完
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}

  • 注册拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.xuedongyun.myadmin.config;

@Configuration
public class MyConfig {

@Bean
WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**"); // 为首页,静态资源放行
}
};
}
}
  • 另一种处理静态资源的方法
1
2
3
spring:
mvc:
static-path-pattern: /static/**

文件上传

  • 邮箱,密码,头像(单张图片),照片(多张图片)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">密码</label>
<input type="password" name="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-group">
<label for="headInputFile">头像</label>
<input type="file" name="headImage" id="headInputFile">
<label for="photoInputFile">照片</label>
<input type="file" name="photos" id="photoInputFile" multiple>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
  • 控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("password") String password,
@RequestPart("headImage") MultipartFile headImage,
@RequestPart("photos") MultipartFile[] photos) throws IOException {

if(!headImage.isEmpty()) {
// 保存到文件服务器,或oss服务器
// 这里存到磁盘
File file = new File("C:\\cache\\" + headImage.getOriginalFilename());
headImage.transferTo(file);
}

if (photos.length > 0) {
for (MultipartFile photo : photos) {
if (!photo.isEmpty()) {
File file = new File("C:\\cache\\" + headImage.getOriginalFilename());
photo.transferTo(file);
}
}
}

return "main";
}
1
2
3
4
5
6
# 设置一下请求和文件的最大大小
spring:
servlet:
multipart:
max-request-size: 100MB
max-file-size: 50MB

错误处理

  • 文档中有写

    https://docs.spring.io/spring-boot/docs/3.0.2/reference/htmlsingle/#web.servlet

  • 默认情况下,SpringBoot提供了/error处理所有错误映射

    • 机器客户端响应json,浏览器客户端响应whitelabel视图
    1
    2
    3
    4
    5
    6
    7
    {
    "timestamp": "2023-02-04T14:35:21.392+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/tttttt"
    }
    • 可以实现ErrorController,或添加ErrorAttributes类型的组件,来自定义属性

添加自定义视图

  • 可以自行添加error视图替代默认,error.htmlerror/404.htmlerror/5xx.html

@ExceptionResolver

  • @ControllerAdvice+@ExceptionResolver处理全局异常
1
2
3
4
5
6
7
8
9
10
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public String handleAlgorithmException(Exception exception) {
log.error("异常是: {}", exception);
return "login";
}
}

@ResponseStatus

1
2
3
4
5
6
@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "错误的具体原因")
public class MyException extends RuntimeException {
public MyException(String msg) {
super(msg);
}
}
1
throw new MyException("dddddd")

自定义异常处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.xuedongyun.myadmin.exception;

// 数字越小优先级越高
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
throws IOException {

if (!StringUtils.hasLength(reason)) {
response.sendError(statusCode);
}
else {
String resolvedReason = (this.messageSource != null ?
this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
reason);
response.sendError(statusCode, resolvedReason);
}
return new ModelAndView();
}

原生组件注入

使用Serlvet API

  • 推荐这种方法
  • 首先需要开启扫描,并指定路径
1
2
3
4
5
6
7
8
9
10
// 开启扫描功能,并指定扫描包路径
@ServletComponentScan(basePackages = "com.xuedongyun.servlet")
@SpringBootApplication
public class MyAdminApplication {

public static void main(String[] args) {
SpringApplication.run(MyAdminApplication.class, args);
}

}
Servlet
1
2
3
4
5
6
7
@WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("test");
}
}
Filter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
@WebFilter(urlPatterns = {"/css/*", "/images/*"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("Filter初始化");
}

@Override
public void destroy() {
log.info("Filter销毁");
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("Filter工作");
chain.doFilter(request, response);
}
}

小提示:url匹配时,Servlet中用*Spring中用**

Listener
1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("监听到项目初始化完成");
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("监听到项目销毁");
}
}

使用RegistrationBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class MyConfig {

@Bean
public ServletRegistrationBean myServlet() {
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean<>(myServlet, "/my", "/my02");
}

@Bean
public FilterRegistrationBean myFilter() {
MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter, myServlet()); // 拦截所有myServlet的路径
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>(myFilter);
registrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*"));
return registrationBean;
}

@Bean
public ServletListenerRegistrationBean myListener() {
MyListener myListener = new MyListener();
return new ServletListenerRegistrationBean(myListener);
}
}

定制Web服务器

修改配置文件

  • server旗下的

ServletWebServerFactoryAutoConfiguration绑定了属性ServerProperties

1
2
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {}
1
server.undertow.accesslog.dir=/tmp

直接自定义webServerFactory

  • ConfigurableServletWebServerFactoryServletWebServerFactory的子接口,可以很方便的设置一些属性
1
2
3
4
5
6
7
8
public interface ConfigurableServletWebServerFactory
extends ConfigurableWebServerFactory, ServletWebServerFactory, WebListenerRegistry {

void setContextPath(String contextPath);
void setDisplayName(String displayName);
void setSession(Session session);
...
}
  • 在容器中放入自定义ConfigurableServletWebServerFactory
1
2
3
4
5
6
7
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(9000);
factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not_found.html"));
return factory;
}

使用FactoryCustomizer

  • ServletWebServerFactoryAutoConfiguration中有ServletWebServerFactoryCustomizer(web服务器工厂类定制化器)
    • 定制化器负责把配置文件中的值和ServletWebServerFacory进行绑定
    • 定制化器后置的修改ServletWebServerFacory中的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {

// web服务器工厂类定制化器
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties,
ObjectProvider<WebListenerRegistrar> webListenerRegistrars,
ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) {
return new ServletWebServerFactoryCustomizer(serverProperties,
webListenerRegistrars.orderedStream().collect(Collectors.toList()),
cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList()));
}
}
  • 直接自定义定制化器
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyWebServerFactoryCustomizer extends ServletWebServerFactoryCustomizer {

public MyWebServerFactoryCustomizer(ServerProperties serverProperties) {
super(serverProperties);
}

@Override
public void customize(ConfigurableServletWebServerFactory factory) {
factory.setPort(9000);
}
}

定制化原理

定制化的常见方式

  1. 编写自定义配置类xxxConfiguration,然后@Bean替换容器中组件,或增加组件(比如视图解析器)

  2. 修改配置文件

  3. xxxCustomizer(比如定制web服务器时)

  4. 配置类实现WebMvcConfigurer,来定制化web功能

  5. 向容器中注入WebMvcRegistrations组件,来修改SpringMVC底层的组件(比如修改HandlerMapping

    • 文档中有写相关内容,太底层了,一般不用
  6. @EnableWebMvc+WebMvcConfigurer

    • 配置类加上@EnableWebMvc将全面接管SpringMVC
    • 静态资源,视图解析器,欢迎页…全没了
    • 需要自己来实现自动配置类了,自行实现WebMvcConfigurer,利用addxxx方法…

原理分析套路

  1. starter
  2. xxxAutoConfiguration
  3. 导入xxx组件
  4. 绑定xxxProperties

数据访问

数据源

数据源的自动配置

  • 导入JDBC场景
1
spring-boot-starter-data-jdbc
  • 分析自动配置,帮我们导入了
1
2
3
HikariCP (官方数据库连接池)
spring-jdbc (jdbc)
spring-tx (用于处理事务)

官方场景没有导入驱动

  • 导入mysql驱动
1
mysql-connector-j
  • 修改配置项
1
2
3
4
5
6
7
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: 122599
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource // 默认就是它,不需要写
  • 使用jdbcTemplate
1
2
3
4
5
6
7
8
@Autowired
JdbcTemplate jdbcTemplate;

@Test
void test() {
Long count = jdbcTemplate.queryForObject("select count(*) from t_user", Long.class);
log.info(String.valueOf(count));
}
分析相关自动配置
  • autoconfigure/jdbc

    • DataSourceAutoConfiguration:数据源的自动配置

    • DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置

    • JdbcTemplateAutoConfigurationJdbcTemplate的自动配置,可以用来对数据库进行CRUD

    • XADataSourceAutoConfiguration:分布式事务相关

  • (源码)DataSourceAutoConfiguration自动配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
// 基于响应式编程的数据库连接池,没有才引入
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@AutoConfigureBefore(SqlInitializationAutoConfiguration.class)
// 开启配置绑定spring.datasource
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class,
DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {

// 数据库连接池
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
// 容器中没有数据源才导入
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
// 默数据库连接池是Hikari,如果导入了其他几个,也可以在配置类中指定
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {

}
}

使用Druid数据源

自定义使用
  • 引入依赖
1
druid
  • 配置类
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class MyDataSourceConfig {

// 创建自己的数据源
@Bean
@ConfigurationProperties("spring.datasource") // 和配置文件中属性绑定
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setFilters("stat, wall"); // 加入监控, 防火墙功能
return dataSource;
}
}
  • 其他功能自行到官方看说明配置即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 配置监控页
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet servlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(servlet, "/druid/*");
registrationBean.addInitParameter("loginUsername", "xdy");
registrationBean.addInitParameter("loginPassword", "1234");
registrationBean.addInitParameter("resetEnable", "true");
return registrationBean;
}

// Web监控功能
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter filter = new WebStatFilter();
FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean<>(filter);
registrationBean.setUrlPatterns(Arrays.asList("/*"));
registrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return registrationBean;
}
使用starter
  • (源码)DruidDataSourceAutoConfigure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@ConditionalOnClass(DruidDataSource.class)
// 必须在DataSourceAutoConfiguration之前注入,不然容器中就已经有Hikari数据源了
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
// 绑定了DruidStatProperties属性,“spring.datasource.druid”
@EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class})
@Import({DruidSpringAopConfiguration.class,
DruidStatViewServletConfiguration.class,
DruidWebStatFilterConfiguration.class,
DruidFilterConfiguration.class})
public class DruidDataSourceAutoConfigure {

private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class);

// 容器中没有DataSource才注入
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
  • 自动配置导入了
    • DruidSpringAopConfiguration:监控Spring用的
    • DruidStatViewServletConfiguration:监控页面
    • DruidWebStatFilterConfiguration:web监控
    • DruidFilterConfiguration:开启相关监控
  • 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: 122599
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
stat-view-servlet:
enabled: true
login-username: xdy
login-password: 1234
reset-enable: true
web-stat-filter:
enabled: true
# 可以点开看看这两个都是有默认值的,其实可以不写
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# 监控Spring Bean
aop-patterns: com.xuedongyun.myadmin.*
# 底层开启功能,stat(sql监控),wall(防火墙)
filters: stat, wall

MyBatis

1
mybatis-spring-boot-starter

MyBatis的自动配置

  • mybatis-spring-boot-autoconfigure/MybatisAutoConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@org.springframework.context.annotation.Configuration
// 有SqlSessionFactory和SqlSessionFactoryBean(导了mybatis就有)
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
// 只有一个数据源
@ConditionalOnSingleCandidate(DataSource.class)
// 绑定属性MybatisProperties,"mybatis"
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {

// 构造函数注入了MybatisProperties
public MybatisAutoConfiguration(MybatisProperties properties, ...) {
this.properties = properties;
...
}

// 自动配好了SqlSessionFactory,使用容器中的数据源
// 其他配置都参考this.properties
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
...
}

// 内部包含一个SqlSession
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

@org.springframework.context.annotation.Configuration
// 导入AutoConfiguredMapperScannerRegistrar
// 负责找到所有标注@Mapper注解的接口,作为Mapper
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

@Override
public void afterPropertiesSet() {
logger.debug("xxx");
}
}
}

以前需要

  • 全局配置的文件
  • SqlSessionFactory->SqlSession->Mapper

现在

  • 全局配置文件:yaml配置

  • SqlSessionFactory:自动配置

  • SqlSessionTemplate:自动配置

  • Mapper@Mapper接口

配置模式

  • 配置文件
1
2
3
4
5
6
mybatis:
# config-location和configuration只能二选一,不然报错
config-location: classpath:mybatis/mybatis-config.xml # 全局配置文件路径
mapper-locations: classpath:mybatis/mapper/*.xml # mapper文件路径
configuration:
map-underscore-to-camel-case: true # 使用驼峰命名
1
2
3
4
5
6
7
8
9
10
11
<!--全局配置文件:resources/mybatis/mybatis-config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启驼峰命名,这里只是举个例子,其实最好在yaml配置文件中写-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
  • mapper文件
1
2
3
4
5
6
package com.xuedongyun.myadmin.mapper;

@Mapper
public interface TUserMapper {
public TUser getTUser(Long userId);
}
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuedongyun.myadmin.mapper.TUserMapper">
<select id="getTUser" resultType="com.xuedongyun.myadmin.bean.TUser">
select * from user_db.t_user where user_id = #{userId}
</select>
</mapper>
  • 使用
1
2
3
4
5
6
7
8
@Autowired
TUserMapper tUserMapper;

@Test
public void test() {
TUser tUser = tUserMapper.getTUser(1002L);
System.out.println("tUser = " + tUser);
}

总结:

  1. 导入starter
  2. 编写mapper接口
  3. 编写mapper.xml文件并绑定mapper接口
  4. application.yaml中指定mapper.xml文件的位置
  5. (二选一)在application.yaml中指定全局配置文件的位置
  6. (二选一)在application.yaml中直接进行配置(推荐)

注解模式

1
2
3
4
5
6
7
8
package com.xuedongyun.myadmin.mapper;

@Mapper
public interface TUserMapper {

@Select("select * from user_db.t_user where user_id = #{userId}")
TUser getTUser(Long userId);
}

最佳实践

  1. 引入starter
  2. 配置application.yaml中,指定mapper-location位置
  3. 编写Mapper接口并标注@Mapper注解
  4. 简单方法直接注解方式
  5. 复杂方法编写mapper.xml

MyBatisPlus

Redis

单元测试

JUnit5基础

  • JUnit5
    • JUnit PlatformJVM上启动测试框架的基础,不止JUnit,其他测试引擎也能接入
    • JUnit Jupiter:提供了JUnit的新的测试模型,是JUnit5新特性的核心。包含一个测试核心,在JUnit Platform上运行
    • JUnit Vintage:提供的兼容JUnit4JUnit3的测试引擎(SpringBoot2.4以上已经默认移除了,要用需要手动添加)
  • 引入依赖
1
spring-boot-starter-test
  • 使用
1
2
3
4
5
6
7
8
@SpringBootTest
class MyAdminApplicationTests {

@Test
void contextLoads() {

}
}

Junit测试类支持Spring的功能

  • @Autowired
  • @Transaction

JUnit5常用注解

  • @Test:表示方法是测试方法。但是与JUnit4@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest:表示方法是参数化测试
  • @RepeatedTest:表示方法可重复执行
  • @DisplayName:为测试类或者测试方法设置展示名称
  • @BeforeEach:表示在每个单元测试之前执行
  • @AfterEach:表示在每个单元测试之后执行
  • @BeforeAll:表示在所有单元测试之前执行
  • @AfterAll:表示在所有单元测试之后执行
  • @Tag:表示单元测试类别
  • @Disabled:表示测试类或测试方法不执行
  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith:为测试类或测试方法提供扩展类引用
1
2
3
4
5
6
7
8
/*
@SpringBootTest其实是个复合注解:
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({SpringExtension.class})
*/

@SpringBootTest
class MyAdminApplicationTests {}

断言机制

  • assertEquals:判断两个对象或两个原始类型是否相等
  • assertNotEquals:判断两个对象或两个原始类型是否不相等
  • assertSame:判断两个对象引用是否指向同一个对象
  • assertNotSame:判断两个对象引用是否指向不同的对象
  • assertTrue:判断给定的布尔值是否为 true
  • assertFalse:判断给定的布尔值是否为 false
  • assertNull:判断给定的对象引用是否为 null
  • assertNotNull:判断给定的对象引用是否不为 null
  • assertArrayEquals:判断两个数组是否相等
  • assertAll:组合断言,都成功才往下走
1
2
3
4
Assertions.assertAll(
() -> Assertions.assertEquals(a, b),
() -> Assertions.assertSame(c, d)
);
  • assertThrows:出现指定的异常才算成功
1
2
3
4
Assertions.assertThrows(
ArithmeticException.class,
() -> {int i = 1/0;}
);
  • assertTimeout:测试方法超时将会异常
  • fail:手动让方法失败

前置条件

不满足前置条件,只会使得测试方法的执行终止,不会失败

  • assumeTrue
  • assumeFalse

嵌套测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@DisplayName("A stack")
class TestingAStackDemo {

Stack<Object> stack;

@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}

@Nested
@DisplayName("when new")
class WhenNew {

@BeforeEach
void createNewStack() {
stack = new Stack<>();
}

@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}

@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
Assertions.assertThrows(EmptyStackException.class, stack::pop);
}

@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
Assertions.assertThrows(EmptyStackException.class, stack::peek);
}

@Nested
@DisplayName("after pushing an element")
class AfterPushing {

String anElement = "an element";

@BeforeEach
void pushAnElement() {
stack.push(anElement);
}

@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
Assertions.assertFalse(stack.isEmpty());
}

@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
Assertions.assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}

@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
Assertions.assertEquals(anElement, stack.peek());
Assertions.assertFalse(stack.isEmpty());
}
}
}
}

参数化测试

  • @ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource:表示为参数化测试提供一个null的入参
  • @EnumSource:表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("参数化测试1")
public void parameterizedTest1(String string) {
System.out.println(string);
Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
// 指定方法名
@MethodSource("method")
@DisplayName("方法来源参数")
public void testWithExplicitLocalMethodSource(String name) {
System.out.println(name);
Assertions.assertNotNull(name);
}

static Stream<String> method() {
return Stream.of("apple", "banana");
}

指标监控

Profile功能

配置文件

  • 比如某个值从配置文件中取得
1
2
@Value("${person.name:default}")
private String name;

@Value()注解

${}取值

:还可以指定默认值

  • 两个配置文件application-prod.yamlapplication-test.yaml
1
2
3
# application-prod.yaml
person:
name: prod-xdy
1
2
3
# application-test.yaml
person:
name: test-xdy
  • 在默认配置文件application.yaml中指定环境
1
2
3
spring:
profiles:
active: test

即使打包完成,也可以使用命令行的形式激活配置

1
java -jar ./xxx.jar --spring.profiles.active=test

优先级:

命令行 > application-[env].yaml > application.yaml

  • 还可以分组批量加载
1
2
3
4
5
6
7
spring:
profiles:
active: myprod
group:
myprod[0]: prod
myprod[1]: ppd
mytest[0]: test

两个组:myprodmytest

同一个组优先级:后面的覆盖前面的

@Profile注解

  • 指定环境才注入容器,可以标在类上,或方法上
1
2
3
4
5
6
7
8
9
10
11
@Profile("prod")
@Component
public Boss extends Person {

}

@Profile("test")
@Component
public Staff extends Person {

}

外部化配置

  • 官方给了14种配置方式,后面的覆盖前面的
  • 本节只讲常用的方式
  • 常用:java属性文件(properties文件),yaml文件,环境变量,命令行参数
  • SpringBoot中也能拿到环境变量
1
2
@Value("${MAVEN_HOME}")
private String mavenHome;

配置文件查找位置

  1. classpath根路径
  2. classpath根路径下/config目录
  3. jar包当前目录
  4. jar包当前目录的/config目录
  5. /config目录直接的子目录

优先级:下面的覆盖上面的

配置文件的加载顺序

  1. 当前jar包中的application.properties, application.yaml
  2. 当前jar包中的application-[env].properties, application-[env].yaml
  3. 引用外部jar包的application.properties, application.yaml
  4. 引用外部jar包的application-[env].properties, application-[env].yaml

自定义starter

graph LR;
    starter-->autoconfigure-->spring-boot-starter
  • starter:引入所需依赖,同时引入该场景autoconfigure
  1. 项目1:hello-spring-boot-starter
1
2
3
4
5
6
7
8
9
10
11
12
<groupId>com.xuedongyun</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>

<!--依赖:hello-spring-boot-starter-autoconfigure-->
<dependencies>
<dependency>
<groupId>com.xuedongyun</groupId>
<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
  1. 项目2:hello-spring-boot-starter-autoconfigure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<groupId>com.xuedongyun</groupId>
<artifactId>hello-spring-boot-starter-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!--autoconfigure依赖:spring-boot-starter-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

假设需求是创建一个HelloService,返回前缀+名字+后缀

  • 属性类
1
2
3
4
5
6
7
8
9
10
package com.xuedongyun.hello.bean;

@ConfigurationProperties("xuedongyun.hello")
public class HelloProperties {

private String prefix;
private String suffix;

// getter & setter
}
  • HelloService
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xuedongyun.hello.service;

// 默认不放容器中
public class HelloService {

// 从属性类中拿值
@Autowired
HelloProperties helloProperties;

public String sayHello(String username) {
return helloProperties.getPrefix() + ": " + username + ">" + helloProperties.getSuffix();
}
}
  • HelloServiceAutoConfiguration
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xuedongyun.hello;

@Configuration
@EnableConfigurationProperties(HelloProperties.class) // 组件放容器中,并绑定属性
public class HelloServiceAutoConfiguration {

@Bean
@ConditionalOnMissingBean(HelloService.class) // 容器中没有HelloService才注入
public HelloService helloService() {
HelloService helloService = new HelloService();
return helloService;
}
}
  • resources/META-INF/spring.factories
    • 这样SpringBoot会自动帮我们加载AutoConfiguration
1
2
3
# Auto Configure
org.springframework.boot.autoconfigure.AutoConfiguration=\
com.xuedongyun.hello.HelloServiceAutoConfiguration
  • 最终,对上面两个模块mvn cleanmvn install

  • 在需要使用的项目中,引入依赖hello-spring-boot-starter即可

使用时:

  • 配置文件中配置属性
  • 直接从容器中获取HelloService组件
  • 标题: SpringBoot-核心功能
  • 作者: 布鸽不鸽
  • 创建于 : 2024-04-10 20:34:10
  • 更新于 : 2024-10-25 16:45:16
  • 链接: https://xuedongyun.cn//post/25031/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论