框架5:SpringBoot 2 - 核心功能

SpringBoot2 - 基础入门【一 ~ 五】,详见:

六、配置文件

6.1 properties文件格式

同之前的用法。

6.2 yaml文件格式【推荐】

YAML本意:“YAML”不是一种标记语言。但在开发中,实际把它理解为:“Yet Another Markup Language”(仍是一种标记语言)
戏称为:薛定谔的YAML

非常适合以数据为中心的配置文件
后缀:.yaml 或 .yml

【语法】
(1) key: value \color{red}{冒号后一定要加空格!}
 ● 对于数组、List、Set的每个元素:
  ● 行内写法:[x1,x2,x3, ...]
  ● 层次写法:每一层使用 - x1
 ● 对于Map、Object的每个元素:
  ● 行内写法:{key1: value1, key2: value2, ...}
  ● 层次写法:空格缩进后,每一层直接写key : value

(2) 使用空格缩进表示层级关系\color{blue}{比properties更清晰}
  ● 数量不重要,只要相同层级对齐即可
  ● 不允许使用tab,只允许空格,但在IDE中我们不用关心

(3) 字符串我们可以不用加引号
 ● '...' 单引号:表示保留字符串原始内容,尽管是转义字符,也不会进行转义
 ● "..." 双引号:表示会将转义字符转义

(4) 大小写敏感

(5) #表示注释

e.g
@Data
public class Person{
  private String userName;
  private Boolean boss;
  private Date birth;
  private Integer age;
  private Pet pet;
  private String[] interests;
  private List<String> animals;
  private Map<String, Object> scores;
  private Set<Double> salarys;
  private Map<String, List<Pet>> allPets;
}
@Data
class Pet{
  private String name;
  private Double weight;
}

application.yml
person: 
  userName: zhangsan
  boss: true
  birth: 2019/12/9
  age: 18
  interests: 
    - 篮球
    - 足球
    - 18动漫
  animals: [阿猫, 阿狗]
  scores: 
    english: 80
    math: 90
  salarys: 
    - 9999.98
    - 9999.99
  pet: 
    name: 阿狗
    weight: 99.99
  allPets: 
    sick: 
      - {name: 阿狗, weight: 99.99}
      - name: 阿猫
      - weight: 88.88
      - name: 阿虫
      - weight: 77.77
    health: 
      - {name: 阿花, weight:199.99}
      - {name: 阿明, weight:11.55}

⭕ 自定义类绑定的配置提示(配置处理器)

<dependency>  
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <!-- 某个依赖项是可选的。这意味着当其他项目依赖于该项目时,该依赖项不会自动传递给其他项目,除非其他项目也显式地声明了该依赖项。 -->
  <optional>true</optional>
</dependency>

<!-- 官方建议:移除这个配置处理器,新版本SpringBoot自动移除了 -->
<build>
  <plugins>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin<artifactId>
    <configuration>
      <excludes>
        <exclude>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-configuration-processor<artifactId>
        </exclude>
      </excludes>
    </configuration>
  </plugins>
</build>

七、Web开发

7.1 静态资源

7.1.1 存放静态资源的目录

(1) 默认情况下,当用户键入:当前项目根路径/ + 静态资源文件
SpringBoot就会在项目的/static(或/public、/resources、/META-INF/resources)目录下查找静态资源

● 原因:默认的静态资源映射路径为:/**
当请求进来后,先会去Controller处理器中看看能不能处理,如果不能处理,则交给默认的静态资源处理器去查找

(2) 修改存放静态资源的目录
SpringBoot会在你修改后的目录中找静态资源

application.yaml
spring:
  resources:
    static-locations: [classpath:/myResource/]
    add-mappings: false # 这里可以禁用查找静态资源(默认是true)
7.1.2 静态资源的映射路径

(1) 默认情况下:无前缀(即/**)

(2) 修改静态资源的映射路径【推荐】
这样当我们后续设置Filter时,不会拦截到静态资源

application.yaml
spring:
  mvc:
    static-path-pattern: /res/**

● 当用户键入:当前项目 + static-path-pattern + 静态资源名 = SpringBoot就会前往静态资源目录中寻找对应的资源

7.1.3 webjar(了解)

一些第三方静态资源也打成了jar包,当需要获取时,可以通过加上前缀:/webjar/...


访问jQuery示例:http://localhost:8080/webjars/jquery/3.5.1/jquery.js
7.1.4 欢迎页和自定义Favicon

(1) 欢迎页
当用户键入项目根目录时,Spring Boot会:
● 先找ReqeustMappingHandlerMapping【保存了我们所有controller的映射规则】,即查找@RequestMapping("/")的处理器;
● 找不到,再去找WelcomePageHandlerMapping查找静态资源目录下的欢迎页作为默认首页。还找不到,才会去寻找模板,即对应的Controller中写了@RequestMapping("/index")并返回"index"视图名的处理器方法

源码

⭕ 注意:当配置了自定义的静态资源的映射路径时,用户键入项目根路径将无法找到index.html这类静态资源。

(2) favicon
在静态资源目录下,放入名为"favicon.ico"的静态资源,则会自动的将icon显示,作为网页的图标

⭕ 注意:当配置了自定义的静态资源的映射路径时,用户键入项目根路径将无法显示favicon.ico图标。

7.2 请求参数处理

7.2.1 支持Rest风格的HiddenHttpMethodFilter \color{blue}{注意}

在SpringBoot的WebMvcAutoConfiguration自动配置类中,已经帮我们自动配置了一个该过滤器对象,但是它需要配置文件的支持!

底层源码

如果是表单中使用,需要手动开启HiddenHttpMethodFilter!

application.yaml
spring: 
  mvc: 
    hiddenmethod: 
      filter: 
        enabled: true

因为有很多客户端(如Postman),可以发送DELETE、PUT等请求,这一项是选择性开启。

7.2.2 自定义转换器

(若干个)参数解析器中大多都会有一个类WebDataBinder,它用来做数据绑定,里面有一个conversionService对象,注册了(若干)转换器converter,用来做数据类型的转换

⭕ 假设现在有一个需求:表单中提交name="pet",value="阿猫, 18";服务器(SpringBoot)的handler中参数接收一个Pet类型的数据。
默认converter没有办法处理这种String→Pet的转换,会报错
● 我们自定义一个converter,并把它注册进容器中。

    //1、WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new Converter<String, Pet>() {

                    @Override
                    public Pet convert(String source) {
                        // 啊猫,3
                        if(!StringUtils.isEmpty(source)){
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }

7.3 数据响应和内容协商

数据响应分类
7.3.1 原理
HttpMessageConverter规范

SpringBoot【处理@ResponseBody响应】中,根据handler返回值类型(假设是自定义Person类)确定一个返回值处理器(有若干个),在这个处理器内部,先获取浏览器可以接收的数据类型,然后去(第一次)遍历HttpMessageConverter,根据【canWrite方法】,拿到所有可以将Person写成别的数据类型的转换器,将这些转换器支持的MediaType保存起来,即获取服务器可以写出的数据类型,然后做浏览器和服务器的【内容协商】,确定最终匹配的MediaType(可能有多个),最后,(第二次)遍历MessageConverter,仍然根据【canWrite方法】,找到含有最终匹配MediaType的这些转换器。然后按照优先级排序,选出【第一个】可以将Person转换成最终的MediaType的转换器,使用它将Person转换为最终格式并返回给浏览器。
● 第一次遍历HttpMessageConverter:服务器具备写哪些数据格式的能力
● 第二次遍历HttpMessageConverter:浏览器和服务器协商后,找到可以对应的转换器

⭕ 另:canRead:带有@RequestBody的请求参数值是否可以读成handler形参中的类型。

7.3.2 如何响应Json和xml

响应json:

● json相关依赖 + @ResponseBody

(1) json相关依赖
引入spring-boot-starter-web场景启动器,内部就含有spring-boot-starter-json场景启动器,主要是通过jackson来实现的。

(2) 在handler方法或者类上标注@ResponseBody

响应xml:

(1) 引入xml依赖
由于spring-boot-starter-web中没有内置对xml的解析支持,所以需要引入额外的依赖。

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

(2) 在handler方法或者类上标注@ResponseBody

7.3.3 开启浏览器:使用请求参数,来进行内容协商
spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求:(带上键为"format"的请求参数,如:json)
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml

内容协商管理器
参数内容协商管理器支持的数据类型(想要协商其他的可以自定义)
7.3.4 自定义MessageConverter(修改服务器需要的MediaType)

假定业务需求,有一种自定义的输出格式"application/myFormat",它要求将Person的属性值按照分号间隔,返回给客户端。

在@Confiugration配置类中添加WebMvcConfig的Bean组件,并在里面添加自定义的MessageConverter功能。

省略了Person的JaveBean

@RestController
public class testController{
   @GetMapping("/test")
  public Person getPerson(){
    Person p = new Person("张三", 18);
    return p;
  }
}

// 自定义的转换器
class MyMessageConverter implements HttpMessageConverter<Person>{
  @Override
  public boolean canRead(Class<?> clazz, MediaType mediaType){
    return false; // 这部分是@requestBody需要做的,我们现在不关心
  }
  // 自定义判断是否可写
  @Override
  public boolean canWrite(Class<?> clazz, MediaType mediaType){
    return clazz.isAssignableFrom(Person.class);
  }
  @Override
  public List<MediaType> getSupportMediaTypes(){
    return MediaType.parseMediaType("application/myFormat"); // 自定义MediaType,即可以处理的数据类型
  }
  @Override
  public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage)throws IOException{
    return null; // 这部分是@requestBody需要做的,我们现在不关心
  }
  // 自定义如何将Person写出 
  @Override
  public Person write(Class<? extends Person> clazz, HttpOutputMessage outputMessage)throws IOException{
    String data = person.getUserName + ";" + person.getAge();
    OutputStream os = outputMessage.getBody()
    os.write(data.getBytes());
  }
}

在我们的含有@Configuration配置类中自定义WebMvcConfig的Bean组件,对其中的功能(这里是添加MessageConverter)进行定制。
   @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            // 这个方法是追加MessageConverter,并不会覆盖掉原来的
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
              converters.add(new MyMessageConverter()); // 添加自定义MessageConverter
            }
        }
    }
7.3.5 自定义ContentNegotiationStrategy(通过携带自定义参数,修改浏览器需要的MediaType)

开启【基于参数format的】,在WebMvcConfigurer里配置一个新的ParameterContentNegotiationStrategy

在我们的含有@Configuration配置类中自定义WebMvcConfig的Bean组件,对其中的功能(这里是覆盖内容协商管理器的策略)进行定制。
   @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            // 配置自定义内容协商器
            @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
              Map<String, MediaType> mediaTypes = new HashMap<>();
              map.put("json", MediaType.APPLICATION_JSON);
              map.put("xml", MediaType.APPLICAION_XML);
              map.put("myFormat", MediaType.parseMediaType("application/myFormat")); // 自定义MediaType数据格式
              ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypes);
              configurer.strategies(Arrays.asList(strategy)); //【这里会覆盖原有的内容协商管理器的策略,可以使用别的方法,实现新增】
              // configurer.strategies(Arrays.asList(strategy, new HeaderContentNegotiationStrategy());
            }
        }
    }

● 使用这种方法会覆盖原有的请求头内容协商策略,导致默认功能失效,我们尽量修改的【原则是新增,而不是覆盖】!(参照SpringBoot用别的方法开发自定义内容协商部分)

7.4 视图解析与模板引擎

SpringBoot默认不支持JSP(需要服务器解压,并提供Java编译器,带来额外的负担),需要引入第三方模板引擎技术(自身的模板引擎技术是支持JSP的)。

● 引入Thymeleaf模板引擎场景启动器

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
</dependencies>

它帮我们配置了ThymeleafViewResolver,并绑定到了ThymeleafProperties类。


默认前缀、后缀
7.4.1 视图解析原理

(1) 所有的handler执行完成后会返回ModelAndView对象
(2) DispatcherServlet调用processDispatchResult方法,遍历各个视图解析器根据视图名称确定可以处理的解析器;
(3) 解析器会给我们返回View对象(View是接口,里面有render方法定义了渲染逻辑
(4) View调用render方法渲染视图
e.g:
● RedirectView:render方法最终会调用:

response.sendRedirect("浏览器解析的路径");

● InternalResourceView:render方法最终会调用:

request.getRequestDispatcher("服务器解析的路径").forward(request, response);

● TymeleafView:有自己的渲染规则...

7.5 拦截器 Interceptor

通常情况下,我们除了登录页面之外,其他页面的都需要用户登录后才能进入,我们会使用拦截器实现这个功能。

(1) 自定义拦截器

/**
 * 登录检查
 * 1、配置好拦截器要拦截哪些请求
 * 2、把这些配置放在容器中
 */
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if(loginUser != null){
            //放行
            return true;
        }

        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
//        re.sendRedirect("/");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

(2) 将拦截器添加注册到WebMvcConfigurer中

/**
 * 1、编写一个拦截器实现HandlerInterceptor接口
 * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
 * 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

拦截器的原理详见SpringMVC:框架3:SpringMVC - 简书 (jianshu.com)

7.6 文件上传

SpringBoot中帮我们自动配置了【MultipartAutoConfiguration】文件上传自动配置类,它里面帮我们配置好了StandardServletMultipartResolver文件上传解析器,并和MultipartProperties绑定(我们可以通过修改application的全局配置文件来修改,诸如:单个文件上传最大大小、整个请求上传的最大大小...)。

@RequestPart注解:当有多个文件需要上传时,显式指定提交表单项的"name"名称,用于确定哪一个上传的文件。
(当然如果页面中使用<input type="file" name="files" multiple/>,handler中可以使用形参MultipartFile[]接收)

7.6.1 文件上传源码分析

(1) DispatcherServlet中的doDispach()方法,接收请求,然后会使用multipartResolver文件上传解析器判断当前是不是一个分段请求(即文件上传请求),如果是:会将当前Request封装成新的MultipartHttpServletRequest对象
⭕ 具体的判断条件:

multipartResolver.isMultipart(request);
↓
StringUtils.startWithIgnoreCase(request.getContentType(), prefix:"multipart/");

\color{red}{本质:判断request对象里的ContentType的值是否以"multipart/"为前缀!文件上传ContentType为"multipart/form"!}
(2) 然后DispatcherServlet再去找到对应的HandlerMapping,做后续的处理……
(3) 在使用HandlerAdapter处理器适配器去执行handler时,匹配到RequestPartMethodParameterResolver参数解析器,会把封装过的request对象中的文件封装成MultipartFile对象,放到handler的形参列表中,对应的handler就可以进行处理了。

7.7 异常处理

SpringBoot在默认的情况下,对于来自机器客户端的错误,将生成JSON响应,其中包括错误、HTTP状态、异常消息的详细信息;对于浏览器的错误,会响应一个"whitelabel"错误视图,以HTML格式呈现同样的数据。

两种默认的响应方式
7.7.1 ErrorMvcAutoConfiguration

这是SpringBoot为异常处理提供的自动配置类。里面主要包含三大组件:

(1) DefaultErrorAttributes——handlerExceptionResolver实现类之一
● 作用:它主要是用来定义错误页面可以包含哪些数据,并将它保存到request域中,以便在错误页面展示。
\color{red}{它是比较特殊的异常处理解析器,不会返回ModelAndView对象(null),仅仅做异常信息保存,异常会继续交给下一个解析器}

(2) BasicErrorController——handler
● 作用:可以理解为是一个普通的handler,但他是专门处理默认的"/error"请求默认情况下(没有任何的自定义处理异常),会使用SpringBoot放在容器中的默认错误页,即id为"error"的View组件【这就是浏览器错误白页的由来】。\color{red}{这是SpringBoot错误页面的最后一道屏障,由SpringBoot支持的。没有它,只能显示原生Tomcat的错误页面。}

(3) DefaultErrorViewResolver——视图解析器
● 作用:ErrorMvcAutoConfiguration默认配置,专门用来处理异常视图的解析器。它首先会先根据状态响应码,对状态码进行匹配。先进行精确匹配,如"404";如果匹配不到,则进行序列匹配,如"4xx"、"5xx"【可以在"/error"目录下,自定义4xx、5xx、400等html页面】。如果找不到,则解析上述的"error"默认错误视图\color{red}{无论是否自定义,都是由这个解析器最后对视图进行解析。我们一般不会自定义新的异常解析器}

7.7.2 异常处理步骤

(1) 执行目标方法,如果方法运行期间出现异常,会被捕获,并标志请求结束。
(2) 进入视图解析阶段,调用processDispatchResult(request, response, handler, mv, exception)方法,派发返回页面。
 ● 遍历所有的handlerExceptionResolver,看谁能处理当前异常。【HandlerExceptionResolver是一个接口,它里面只定义了一个方法,即resolveException,如何处理异常,返回类型是ModelAndView】

系统默认的异常解析器

   a) 第一个执行的解析器是DefaultErrorAttributes,它仅定义了将哪些错误信息放到request域,供错误页面使用,返回ModelAndView为null。
   b) 执行第二个解析器,该解析器中定义了三个自带的异常处理解析器。
   ● ExceptionHandlerExceptionResolver:携带了注解@ExceptionHandler,并能够处理当前异常的解析器【它就是@ControllerAdvice + @ExceptionHandler 的底层支持】
   ● ResponseStatusExceptionResolver:携带了注解@ResponseStatus,并能够处理当前异常的解析器【它就是@ResponseStatus 的底层支持】
   ● DefaultHandlerExceptionResolver:SpringBoot专门用来处理内部异常的解析器,如参数不存在...
   c) 自己实现了HandlerExceptionResolver接口的解析器类【可以通过@Order来更改优先级】

 ● 只要上述任意一个异常处理解析器返回的ModelAndView对象不为空,则直接跳出。如果找到了解析器,那么就使用该解析器解析,处理异常;如果所有的异常处理解析器都无法处理,那么会直接将异常抛出去,给Tomcat处理。但是SpringBoot在这里进行了封装,它不会直接由Tomcat的默认错误页处理,而是会转发"/error"请求,被SpringBoot中的BasicErrorController组件处理

7.7.3 自定义异常处理

● 方式一:自定义错误页
在静态资源目录,或者templates目录下,创建一个"/error"目录,里面存放"404"、"4xx"、"5xx"等以状态码命名的html页面。DefaultErrorViewResolver会先找这样的错误模板页,如果找不到才使用默认的错误视图"error"。

● 方式二:@ControllerAdvice + @ExceptionHandler【推荐】
 它可以:自定义如果解析异常信息,并返回ModelAndView。底层由ExceptionHandlerExceptionResolver支持。

● 方式三:@ResponseStatus 自定义状态码,以及详细的错误信息
 它不能定义如何解析这个异常以及返回ModelAndView,只能手动标识一个HTTP状态码和详细错误信息【可以将注解加到控制器方法或异常类上】。最终还是会调用response.sendError(statusCode, revolcedReason),即转发"/error"请求(由BasicErrorController处理,由DefaultErrorViewResolver解析)。可以通过这个搭配自定义错误页(方式一)使用!底层由ResponseStatusExceptionResolver支持。

@ResponseStatus
● value/code:类型HttpStatus,它是由Spring框架提供的枚举类型,用于表示HTTP响应码
● reason:自定义的相应信息
(没有办法自己创建一个HttpStatus对象)

● 方式四:实现HandlerExceptionResolver接口的解析器类
实现里面的resolveException方法,并返回ModelAndView对象。【用的比较少】,因为它可以更改优先级,会改变SpringBoot默认的处理异常的顺序。

7.8 Web原生组件 和 嵌入式Servlet容器

7.8.1 注入Servlet、Filter、Listener

(1) 方式一:@ServletComponentScan(basePackage="...") + ( @WebServlet / @WebFilter / @WebListener )
如,可以像原生Servlet一样书写,并且在类上标注@WebServlet,然后在主程序类(主配置类)上加上@ServletComponentScan注解,如果不写basePackage,则默认是主配置类所在包目录

(2) 方式二:使用RegistrationBean
可以在@Configuration配置类中,添加@Bean组件——ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean

@Configuration
public class MyRegistConfig {

    @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());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
        return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
}
7.8.2 嵌入式Servlet容器

SpringBoot默认支持的WebServer:Tomcat、Jetty、Undertow

(1) 原理
SpringBoot应用在启动时,检测到是Web应用,就会创建一个ServletWebServerApplicationContext的IOC容器
● 该容器启动时,会寻找ServletWebServerFactory
● SpringBoot底层拥有ServletWebServerFactoryAutoConfiguration自动配置类。该配置类中装配了Tomcat、Jetty、Undertow这些Servlet容器。并不会全部生效!导入了哪个,生效哪个!
● 生效的ServletWebServerFactory会创建对应的Servlet容器,并启用它。

(2) 如何切换服务器
默认的web场景启动器,内置了Tomcat的Servlet容器。可以排除它,并引入其他的Servlet容器。

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!-- 排除Tomcat -->
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
  </dependency>
  <!-- 引入undertow -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
  </dependency>
</dependencies>

八、数据访问

SpringBoot中有关数据访问的场景启动器:【spring-boot-starter-data-*】

8.1 JDBC场景

8.1.1 引入 场景启动器 + 驱动 依赖

(1) 导入spring-boot-starter-data-jdbc

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>   

里面内置了HikariCp连接池这个数据源,以及Spring中支持jdbc、事务相关的包

jdbc场景

(2) 引入驱动的依赖
⭕ 为什么官方不给我们提供在jdbc场景中呢?——因为它不知道我们要使用哪个数据库!所以我们需要自己引入

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
   <!--<version>5.1.49</version>-->
</dependency>

想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、依照parent的properties重新声明版本(maven的属性的就近优先原则)
    <properties>
        <java.version>1.8</java.version>
        <mysql.version>5.1.49</mysql.version>
    </properties>

spring-boot-starter-parent里面给我们管理了主流的驱动依赖。我们可以不用写版本,但是一定要看看驱动版本和我们自己用的数据库对不对应的上!

● 配置驱动的连接信息

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

(3) 测试

@Slf4j
@SpringBootTest
class Boot05WebAdminApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;


    @Test
    void contextLoads() {

//        jdbcTemplate.queryForObject("select * from account_tbl")
//        jdbcTemplate.queryForList("select * from account_tbl",)
        Long aLong = jdbcTemplate.queryForObject("select count(*) from account_tbl", Long.class);
        log.info("记录总数:{}",aLong);
    }

}
8.1.2 DataSource的自动配置类

● DataSourceConfiguration
 数据库连接池的自动配置类,和DataSourceProperties绑定(prefix="spring.datasource"),当容器中没有自己的DataSource时才自动配置
底层配置好的连接池:HikariDataSource
● DataSourceTransactionManagerAutoConfiguration
 事务管理器的自动配置类
● JdbcTemplateAutoConfiguration
 JdbcTemplate的自动配置类,可以用来对数据库进行crud操作。

8.2 使用Druid数据源(第三方)

8.2.1 自定义整合

(1) 引入Druid连接池数据源

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.17</version>
</dependency>

(2) 给Spring容器中添加DruidDataSource组件

● 使用【@ConfigurationProperties】,指定application全局配置文件的前缀,会自动地为当前组件的属性注入值。【底层:反射,需要依赖于无参构造+set】

@Configuration
public class MyDataSourceConfiguration{
  @ConfigurationProperties("spring.datasource")
  @Bean
  public DataSource dataSource(){
    DruidDataSource druidDataSource = new DruidDataSource();
    // druidDataSource.serUrl("...");
    // druidDataSource.serUsername("...");
    // druidDataSource.serPassword("...");
    return druidDataSource;
  }
}

(3) 添加Druid功能到我们的自动配置类
功能很多,比如:查询慢SQL记录等等...

● 基本原则:参照官方文档说明,只要有<bean>标签,就可以在配置类中添加一个@Bean组件进Spring容器,并在里面放好指定的属性,就可以完成功能。

官方链接:
https://github.com/alibaba/druid

8.2.2 starter整合

(1) 引入druid-spring-boot-starter

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-starter</artifactId>
  <version>1.1.17</version>
</dependency>

(2) 根据官方的自动配置类,配置文件

给个示例:

application.yaml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      aop-patterns: com.atguigu.admin.*  #监控SpringBean
      filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)

      stat-view-servlet:   # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        resetEnable: false

      web-stat-filter:  # 监控web
        enabled: true
        urlPattern: /*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'


      filter:
        stat:    # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          logSlowSql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

8.3 整合MyBatis

https://github.com/mybatis

● 引入mybatis提供的场景启动器

mybatis-spring-boot-starter包含

可以在官方中查看pom.xml文件提取坐标信息,也可以使用Spring Initializer向导帮助我们创建MyBatis Spring Boot Application。

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.4</version>
</dependency>
8.3.0 回顾原生mybatis整合spring

(1) mybatis自己的配置
● 在mybatis-config.xml全局配置文件中,指定mapper包路径
● 在XXXmapper.xml映射sql文件中,编写sql语句,并在namespace、sql标签id上绑定对应接口
通过sqlSessionFactory,创建sqlSession对象,调用getMapper方法时传入接口类名,创建代理对象,执行sql。

(2) 整合配置
需要在Spring容器中(Spring配置文件或者Spring配置类中),放入SqlSessionFactory组件(里面配置了dataSource信息、通过configLocation配置了mybatis-config.xml全局配置文件路径),并开启Mapper接口的扫描

而在springboot中,提供了三种模式供我们进行整合。

8.3.1 配置模式【重要】

(0) 导入mybatis官方starter
(1) 编写Mapper接口,标注@Mapper注解。【推荐】或者在SpringBoot主启动类中标注@MapperScan("Mapper接口的包路径")【AutoConfiguredMapperScannerRegistrar自动帮我们扫描该路径下的Mapper接口】
使用 @Mapper 注解时,需要在每个 Mapper 接口上都标注 @Mapper 注解。如果应用中的 Mapper 接口比较多,这样做会显得繁琐。此时,可以使用 @MapperScan 注解指定 Mapper 接口的扫描路径,以避免重复标注 @Mapper 注解。
(2) 编写sql映射文件并通过namespace、sql标签id,分别绑定接口类名、接口方法名。
(3) 在application.yaml中指定mybatis-config.xml全局配置文件路径,和mapper映射文件位置。

application.yaml

# 配置mybatis规则
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml
  # 这里配置了,mybatis全局配置中就不用指定mapper的包路径了,它的作用:创建代理对象时找到对应的接口
  mapper-locations: classpath:mybatis/mapper/*.xml 
  configuration:
    map-underscore-to-camel-case: true

⭕ 注意:
● 在yaml中配置mybatis.configuration,相当于在mybatis-config.xml(MyBatis全局配置文件)中配置。只能二选一
● (1)只是帮我们把实现了接口的代理对象作为组件放入Spring的IOC容器中;(2)、(3)是mybatis中的逻辑,需要指定sql映射文件的位置、指定全局配置文件路径、以及两处绑定接口的操作。 \color{purple}{理解}

8.3.2 注解模式

直接省略Sql的映射文件,在接口方法上书写sql。

@Mapper
public interface CityMapper {
    @Select("select * from city where id=#{id}")
    public City getById(Long id);
}

8.3.3 混合模式(最佳实践)

● 简单方法直接注解方式
● 复杂方法编写mapper.xml进行绑定映射

8.4 整合MyBatis-Plus

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

● 建议安装 MybatisX 插件
● 只需要我们的Mapper继承 BaseMapper 就可以拥有简单的crud能力
● 一些Service接口的实现类直接调用Dao方法,没有什么额外的业务逻辑。我们可以让它的接口实现 IService<T>,让该实现类继承 ServiceImpl<Mapper, Bean>(Mapper是操作数据库的Mapper接口,Bean是返回封装的JavaBean)

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
  ServiceImpl<Mapper, Bean>是Mybatis-Plus提供给我们的
}

public interface UserService extends IService<User> {
  IService是Mybatis-Plus提供给我们的
}

65、数据访问-整合MyBatisPlus操作数据库_哔哩哔哩_bilibili

8.5 整合Redis

Redis是一个开源的(BSD许可),内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

● 引入redis的场景启动器

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
</dependencies>

自动装配了RedisAutoConfiguration配置类,绑定了RedisProperties属性类(前缀为"spring.redis")
 ● 配置了 客户端连接工厂:Lettuce、Jedis(早期默认Jedis,高版本默认Lettuce)
 ● 配置了 RedisTemplate<Object, Object>组件。redisTemplate 是 【Spring Framework 提供的】一个用于【简化 Redis 操作的模板类】,它可以使用多种 Redis 客户端实现,包括 Jedis 和 Lettuce 等。
 ● 配置了 StringRedisTemplate组件,它的key、value都是String

● 高版本的redis场景启动器,默认的客户端是Lettuce,如果要切换客户端到Jedis

(1) 引入Jedis依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <!-- 这里可以排除Lettuce,不排除的话,相当于有两个客户端包。在application.yaml中配置一下使用的客户端为Jedis也可以 -->
</dependency>

<!--导入jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
(2) applcaition.yaml全局指定redis客户端
spring:
  redis:
      host: r-bp1nc7reqesxisgxpipd.redis.rds.aliyuncs.com
      port: 6379
      password: lfy:Lfy123456
      client-type: jedis
      jedis:
        pool:
          max-active: 10

● 简单测试

class Test{
    @Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();

        operations.set("hello","world");

        String hello = operations.get("hello");
        System.out.println(hello);
    }
}

九、单元测试

SpringBoot 2.2.0 版本开始引入JUnit5作为单元测试默认库。
SpringBoot 2.4 以上版本移除了默认对Vintage的依赖。如果需要兼容JUnit4,需要自行引入

● 引入测试场景启动器

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
  </dependency>
  <!-- 以下是对Junit4的兼容 -->
  <dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
  </dependency>
</dependencies>

在JUnit4中,我们在SpringBoot中测试:@SpringBootTest + @RunWith(SpringRunner.class)
在JUnit5中,我们在SpringBoot中测试:@SpringBootTest 即可。JUnit类具有Spring的功能。如可以使用@Autowired自动注入,使用@Transactional标注事务,测试完成后会自动回滚……

9.1 常用的测试注解

(1) @Test:无参数测试

(2) @ParameterizedTest:参数化测试
参数化测试时JUnit5很重要的一个新特性,它使得不同的参数多次运行测试成为了可能。
 ● @ValueSource
 ● @NullSource
 ● @EnumSource
 ● @CsvSource
 ● @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流,并且方法是静态的)
⭕ 另外:它可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参。

@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");
}

(3) @RepeatedTest(次数):表示方法可重复执行的次数

(4) @DisplayName("名字"):为测试类或者测试方法设置展示名字

(5) @BeforeEach:在【每个】单元测试方法之前执行

(6) @AfterEach:在【每个】单元测试方法之后执行

(7) @BeforeAll:在【所有】单元测试方法之前执行,只执行一次

(8) @AfterAll:在【所有】单元测试方法之后执行,只执行一次

(9) @Tag("标签名"):设置单元测试的类别,可以在运行的时候筛选

(10) @Disabled:测试类或者测试方法不执行(报告方法跳过,而不是错误)

(11) @Timeout:当测试方法运行超出了时间会返回错误
● 可以设置时间单位
● 设置具体的时间数

(12) @ExtendWith:给测试类或者测试方法提供【扩展类】的引用
例如:
● @SpringBootTest:复合注解
 ● @ExtendWith(SpringExtension.class)——对接了Spring的测试驱动
 ● @BootstrapWith(SpringBootTestContextBootstrapper.class)

其他的注解:JUnit 5 User Guide

import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!

@DisplayName("我的测试类")
public class TestDemo {

  @Test
  @DisplayName("第一次测试")
  public void firstTest() {
      System.out.println("hello world");
  }

9.2 断言

断言(Assertions)是测试方法中的核心部分,是org.junit.jupiter.api.Assertions包下的静态方法。
在maven项目中进行test,会生成一个详细的测试报告。

(1) 简单断言
● 参数列表:期望值(Boolean类型的就不用期望值了)、实际运行值、String(message)(可以为空。当断言失败时,输出的消息)

简单断言

(2) 数组断言:判断两个数组长度和内容上是否相等
● 参数列表:期望值、实际运行值、String(message)(可以为空。当断言失败时,输出的消息)

assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});

(3) 组合断言:多个断言组合起来,只有全部断言成功,才成功;否则,报告所有失败的断言【即所有断言均会执行,无论是否存在失败】
● 参数列表:String(heading,指定一个组合名字用于测试报告显示)、Executable可变形参

Executable是一个接口,支持Lambda表达式编写多个断言

assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );

(4) 异常断言:如果出现指定异常,才断言成功;否则失败
● 参数列表:期望值、Executable、String(message)

@Test
@DisplayName("异常测试")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
           //扔出断言异常
            ArithmeticException.class, () -> {
              int i = 10 / 0;
            }, message:"业务逻辑居然正常运行?");

}

(5) 超时断言:断定方法会在指定时间内运行完成
● 参数列表:期望值、Executable、String(message)

@Test
@DisplayName("超时测试")
public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

(6) 快速失败
fail方法,直接使得测试失败

@Test
@DisplayName("fail")
public void shouldFail() {
 fail("This should fail");
}

9.3 前置条件

JUnit5 中的前置条件(Assumptions)类似于断言,都会在不满足条件时,终止执行。
● 区别在于:断言会报告方法失败,而前置条件不满足会报告方法被跳过

@DisplayName("前置条件")
public class AssumptionsTest {
 private final String environment = "DEV";
 
 @Test
 @DisplayName("simple")
 public void simpleAssume() {
    assumeTrue(Objects.equals(this.environment, "DEV"));
    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
 }
 
 @Test
 @DisplayName("assume then do")
 public void assumeThenDo() {
    assumingThat(
       Objects.equals(this.environment, "DEV"),
       () -> System.out.println("In DEV")
    );
 }
}

assumeTrue 和 assumeFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。
assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行

9.4 嵌套测试

● @Nested:加在测试类的【内部类】上。
 ● 测试【内部类】的方法,【会】驱动外部类的Before(After)Each/All方法;
 ● 测试【外部类】的方法,【不会】驱动内部类的Before(After)Each/All方法。

@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() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            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() {
                assertFalse(stack.isEmpty());
            }

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

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

十、指标监控

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。

笔记:08、指标监控 (yuque.com)
视频:77、指标监控-SpringBoot Actuator与Endpoint_哔哩哔哩_bilibili

十一、原理解析

11.1 多环境适配——Profile

生产环境、开发环境、测试环境等等...配置文件内容、配置类等可能会随着环境的不同而有不同的设置。

11.1.1 配置文件application.yaml相关

如果我们每次都在一个配置文件上修改,显得太麻烦了。SpringBoot允许我们使用多个配置文件。

● 默认的配置文件:application.yaml——任何时候都会加载;
● 自定义环境配置文件:application-{env}.yaml

激活自定义环境配置文件
 ● 方式一:在默认的配置文件中指定

假定自定义环境配置文件:application-myEnv1.yaml、application-myEnv2.yaml

(1) 在默认配置文件中【激活某个】自定义的环境配置文件:
application.yaml 
spring.profiles.active = myEnv1

(2) 在默认配置文件中,对profile进行分组,【激活组】:
spring.profiles.group.myEnv[0] = myEnv1
spring.profiles.group.myEnv[0] = myEnv2
如果在默认配置文件中:spring.profiles.active = myEnv,则两个配置文件都会生效

 ● 方式二:命令行激活(我们的spring-boot项目最终都可以被打包成一个jar包部署到服务器)
 可以在外部修改配置文件的任何信息

java -jar xxx.jar --spring.profiles.active=prod  --person.name=haha
11.1.2 @Profile条件装配

● 可以加在配置类,也可以加在方法上。可以指定在某个环境生效。(application.yaml中spring.profiles.active指定)

application.yaml
spring.profile.active: production

@Configuration(proxyBeanMethods = false)
@Profile("production") 【默认是"default",可以写成数组形式】
public class ProductionConfiguration {

    // ...

}

11.2 外部化配置

将数据不是写死在代码中,而是抽取成外部文件,集中管理。
SpringBoot中支持各种外部化配置,其中包括:Java Properties文件,YAML文件,环境变量和命令行参数。

举例:直接获取系统环境变量的值

\color{blue}{生效规则(按照先后顺序,后面的覆盖前面的):配置文件 → 外部命令行修改}
\color{blue}{配置文件:类路径 → 类路径下的/config → jar包所在目录 → jar包所在目录/config → jar包同级/config的直接子目录}

11.3 自定义starter

模拟源码编写自定义starter。

● 步骤

(1) 编写一个maven项目,用来作为myservice-spring-boot-starter,只写pom.xml,里面引入了MyServiceAutoConfiguration自动配置类的依赖(除了pom文件以外,没有任何其他的Java代码、配置文件)

(2) 编写一个maven项目,用来作为MyServiceAutoConfiguration自动配置类(被自定义starter引用),编写自动配置类(@Configuration + @EnableConfigurationProperties)、业务组件(@Bean + 条件装配)、MyServiceProperties类(@ConfigurationProperties + 绑定到自动配置类)

(3) 在MyServiceAutoConfiguration项目中resources目录下准备META-INF目录,存放spring.factories文件,里面编写项目启动时自动加载的配置类"xxx.xxx.EnableAutoConfiguration=MyServiceAutoConfiguration自动配置类的全类名"

(4) 在外部项目中,引入my-spring-boot-starter即可使用我们自定义的功能。

● myservice-spring-boot-starter:maven项目

pom.xml
...
<!-- my-spring-boot-starter坐标 -->
<groupId>xxxx</groupId>
<artifactId>myservice-spring-boot-starter</artifactId>
<version>1.0</version>
...
<dependencies>
  <dependency>
    <!-- 引入MyAutoConfiguration自动配置类 -->
    <groupId>xxx</groupId>
    <artifactId>myservice-spring-boot-starter-autoconfigure</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>
...
● MyServiceAutoConfiguration项目myservice-spring-boot-starter-autoconfigure:maven项目

pom.xml
...
<!-- 自己的坐标 -->
<groupId>xxx</groupId>
<artifactId>myservice-spring-boot-starter-autoconfigure</artifactId>
<version>1.0</version>
...

// 逻辑业务组件
@Service
public class MyService{
  @Autowired
  private MyServiceProperties myServiceProperties;
  public string sayHello(String name){
    return myServiceProperties.getHelloWord() + name;
  }
}

@ConfigurationProperties(prefix="my.myService")
public class MyServiceProperties{
  private String helloWord;
  public String getHelloWord(){
    return helloWord;
  }
}

application.yaml
my: 
  myService: 
    helloWord: hello~

@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration{
  @ConditionalOnMissingBean(MyService.class)
  @Bean
  public MyService myService(){
    MyService myService = new MyService();
    return myService;
  }
}

在项目resources文件夹下创建/META-INF,创建spring.factories文件
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
MyServiceAutoConfiguration全类名
● 自己测试myservice-spring-boot-starter,创建一个外部项目
pom.xml
...
<!-- 自己的坐标(略) -->
<!-- 引入我们自定义的starter -->
<dependencies>
  <dependency>
    <groupId>xxxx</groupId>
    <artifactId>myservice-spring-boot-starter</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>
...

@RestController
public class MyTestController{
  @Autowired
  private MyService myService;
  @RequestMapping("/hello")
  public String sayHello(){
    return myService.sayHello("张三");
  }
}

11.4 原理总结

【创建】并【运行】SpringApplication。
● 【创建】:主要是通过spring.factories文件去创建一些关键组件。如:启动引导器(BootStrapppers)、IOC初始化器(ApplicationContextInitializer)、应用监听器(ApplicationListener)
● 【运行】:找到spring.factories中所有SpringApplicationRunListener,对每个阶段进行监听。包括准备环境(读取并绑定系统配置、外部配置源等等)、根据Web项目类型创建IOC容器刷新IOC容器(创建Bean组件)Runner启动(应用启动后会调用run方法)等等。

——以上ApplicationContextInitializer、ApplicationListener、SpringApplicationRunListener、Runner均可以自定义。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容