SpringBoot2_笔记

文章目录

SpringBoot2 入门

系统要求

  • Java 8 &兼容 Java 14
  • Maven 3.3+

Maven 设置

<!--配置阿里云的镜像,下载jar速度快-->
<mirror>
    <id>alimaven-central</id>
    <mirrorOf>central</mirrorOf>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>

<!-- 配置 JDK 默认版本及 编码格式-->
<profile>   
    <id>jdk1.8</id>    
    <activation>   
        <activeByDefault>true</activeByDefault>    
        <jdk>1.8</jdk>   
    </activation>    
    <properties>   
        <maven.compiler.source>1.8</maven.compiler.source>    
        <maven.compiler.target>1.8</maven.compiler.target>    
        <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>   
    </properties>    
</profile>

HelloWorld

1. 创建 maven 工程引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.9.RELEASE</version>
</parent>

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

2. 创建主程序

/**
* @SpringBootApplication :这是一个SpringBoot应用
*/
@SpringBootApplication
public class MainApplication {

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

3. 编写业务逻辑

//@Controller
//@ResponseBody //表示这个类的每一个方法都返回JSON字符串
@RestController //上两个注解结合版
public class HelloController {

    @GetMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot";
    }
}

4. 测试

直接运行 main 方法即可

5. 简化配置

application.properties

#修改tomcat默认端口号
server.port=8888

6. 简化部署

引入插件

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.3.9.RELEASE</version> <!--不加版本号可能会爆红-->
        </plugin>
    </plugins>
</build>

把项目打成 jar 包,直接在目标服务器执行即可

注意点:在cmd命令行窗口中,可能启用了快速编辑模式,起 SpringBoot 时鼠标一点就停掉了,要关闭这个设置

了解自动配置原理

SpringBoot 特点

依赖管理
  • 父项目做依赖管理

    <!--依赖管理-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
    </parent>
    <!--他的父项目-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.3.9.RELEASE</version>
    </parent>
    几乎声明了所有开发中常用的依赖版本号
    
  • 开发导入 starter 场景启动器

    1.见到很多 spring-boot-starter-*:*就某种场景
    2.主要引入starter,这个场景的所有常规需要的依赖我们都自动引入
    3.springboot所有支持的场景在官方文档中
    4.见到的 *-starter:第三方为我们提供的简化开发的场景启动器
    
  • 无需关注版本号,自动版本仲裁

    除了一些第三方的依赖插件,几乎所有的依赖不用额外添加版本
    
  • 可以修改版本号

    1.查看spring-boot-dependencies里面规定的当前依赖的版本用的key
    2.在当前项目里面重写配置,因为maven的就近优先原则
    <properties>
    	<mysql.version>5.1.43</mysql.version>
    </properties>
    
自动配置
  • 自动配置好了 tomcat

  • 自动配置好了SpringMVC

  • 自动配置好Web常见功能,如:字符编码问题

  • 默认的包结构

    • 主程序所在的包及其下面所有的子包,默认都会被扫描进来

    • 如果想要改变扫描路径

      @SpringBootApplication(scanBasePackages = "com.z") //方式一
      //方式二
      //@SpringBootApplication 等同于下面3个注解结合
      @SpringBootConfiguration
      @EnableAutoConfiguration
      @ComponentScan("com.z")
      
  • 各种配置拥有的默认值

    • 默认配置最终都是映射到MultipartProperties
    • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 按需加载所有自动配置项

    • 引入了哪些场景,这个场景的自动配置才会开启
    • springboot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面

容器功能

组件添加
@Configuration、@Bean
  • 基本使用

    /**
     * 1.配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
     * 2.配置类本身也是组件
     * 3.proxyBeanMethods:代理bean的方法
     *      Full(proxyBeanMethods = true)
     *      Lite(proxyBeanMethods = false)
     */
    @Configuration(proxyBeanMethods = true) //告诉SpringBoot 这是一个配置类,相当于以前的配置文件
    public class MyConfig {
    
        /**
         * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
         * @return
         */
        @Bean //给容器中添加组件。以方法名作为组件的id,返回类型就是组件类型,返回值就是组件在容器中的实例
        public User user01(){
            //user组件依赖了Pet组件
            return new User("zhangsan",18,"Man",tomcatPet());
        }
    
        @Bean("tom") //自定义组件名
        public Pet tomcatPet(){
            return new Pet("tomcat");
        }
    }
    
  • Full模式与Lite模式

    • 配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
    • 配置类组件之间有依赖关系,方法会被调用得到之前单例组件,用Full模式
@Import
  • 给容器中自动创建出导入的类型的组件

    @Import({User.class, DBHelper.class})
    @Configuration //告诉SpringBoot 这是一个配置类,相当于以前的配置文件
    public class MyConfig {}
    
@Conditional
  • 条件装配:满足Conditonal指定的条件,则进行组件注入

    @ConditionalOnBean(name="tom")
    当此注解作用在方法上,表示当容器中有tom这个组件才执行此方法;
    作用在类上,表示类中所有方法才执行
    @ConditionalOnMissingBean(name="tom")
    此注解恰好与上面的相反
    。。。。还有更多的子注解表示意思一样,只是针对的事物不同
    
@ImportResource
  • 导入Spring的配置文件,作用在配置类上

配置绑定

  • @Component + @ConfigurationProperties

    @Component
    @ConfigurationProperties(prefix = "")//指定配置文件中的头名称
    public class Car{
        
    }
    
  • @EnableConfigurationProperties + @ConfigurationProperties

    @ConfigurationProperties(prefix = "")//指定配置文件中的头名称
    public class Car{
        
    }
    @EnableConfigurationProperties(Car.class)
    //1.开启Car配置绑定功能
    //2.把这个Car组件自动注册到容器中
    public class MyConfig(){
        
    }
    

自动配置原理入门

引导加载指定配置类

@SpringBootConfiguration
@Configuration //表示是一个配置类
public @interface SpringBootConfiguration {}
@ComponentScann
//Spring注解,指定扫描哪些
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
  • @AutoConfigurationPackage

    • @Import({Registrar.class}) //给容器中导入一个组件
      public @interface AutoConfigurationPackage {
      }
        //利用Registrar给容器中导入一系列组件
        //将指定的一个包下的所有组件导入进来  
      
  • @Import({AutoConfigurationImportSelector.class})

    • 1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
      2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata,       	attributes)获取到所有需要导入到容器中的配置类
      3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader 		classLoader);得到所有的组件
      4、从META-INF/spring.factories位置来加载一个文件。
          默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
          spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
      
    • configurations

      • 加载了127个配置类,SpringBoot 在文件里面写死了 spring-boot 一启动就要给容器中加载所有的配置

按需开启自动配置项

  • 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration 按照条件装配规则(@Conditional),最终会按需配置。

修改默认配置

@Bean
@ConditionalOnBean(MultipartResolver.class)  //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    //给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
    //SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;
}
给容器中加入了文件上传解析器;
  • SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

    @Bean
    @ConditionalOnMissingBean
    public CharacterEncodingFilter characterEncodingFilter() {
    }
    

总结:

  • SpringBoot 先加载所有的自动配置类 xxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值(xxxProperties里面拿)。xxxProperties 和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有些这组件,相当于这些功能就有了
  • 定制化配置
    • 用户直接自己@Bean替换底层的组件
    • 用户取看这个组件是获取的配置文件扫描值就去修改
  • xxxxAutoConfiguration —> 组件 —> xxxProperties里面拿值 —> application.properties

最佳实践

  • 根据需求引入场景依赖
    • https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
  • 查看自动配置了哪些(底层原理)
    • 自己分析,引入场景对应的自动配置一般都生效了
    • 配置文件中 debug=true 开启自动配置报告。Negative(不生效)\Positive(生效)
  • 是否想要修改
    • 参照文档修改配置项
      • https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#common-application-properties
      • 自己分析,xxxxProperties 绑定了配置文件的哪些
    • 自定义加入或者替换组件
      • @Bean、@Component、、、

开发小Tips

Lombok

  • 简化JavaBean开发

在Idea中搜索安装lombok插件

然后在pom文件中引入依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

在JavaBean上添加注解

@Data // set get 方法以及 toString、equals
@AllArgsConstructor //有参构造器,声明了有参构造器一定要加上无参构造
@NoArgsConstructor //无参构造器
public class User {
    private String name;
    private Integer age;
    private String gender;

    private Pet pet;
}
Snipaste_2021-03-25_11-40-35
  • 简化日志开发

    @Slf4j
    @RestController
    public class HelloController {
        @RequestMapping("/hello")
        public String handle01(@RequestParam("name") String name){
            
            log.info("请求进来了....");
            
            return "Hello, Spring Boot 2!"+"你好:"+name;
        }
    }
    

dev-tools

  • 在idea 设置中开启自动构建项目

在这里插入图片描述

  • 按住 shift + ctrl + alt + / 四个键

在这里插入图片描述

选择 Registry 进入,找到 compiler.automake.allow… 打上勾

在这里插入图片描述

  • 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
  • 在 pom 文件下设置允许派生
<configuration>
	<fork>true</fork>
</configuration>
  • 设置idea更新 选择当前应用的启动类 再选择classes 和资源

在这里插入图片描述

  • 项目或者页面修改以后也可以按:Ctrl+F9 更新

Spring Initailizr (项目初始化向导)

  • 选择我们想要的开发场景

    在这里插入图片描述

  • 自动引入依赖

  • 自动创建项目结构

  • 自动编写好主配置类

SpringBoot2 核心技术

配置文件

文化类型

  • properties

    同以前的properties用法

  • yaml

    它非常适合用来做数据为中心的配置文件

基本语法

  • key: value ;kv之间有空格
  • 区分大小写
  • 使用缩进表示层级关系
  • 缩进不允许使用tab,只允许空格
  • 缩进的空格数不重要,只要相同层级的元素左对齐即可
  • #表示注释
  • 字符串无需加引号,如果要加,单引号表示内容会被转义,而双引号表示不会被转义

数据类型

  • 字面量:单个的、不可再分的值。date、boolean、string、number、null

    k: v
    
  • 对象:键值对的集合 map、hash、set、object

    #行内写法
    k: {k1:v1,k2:v2,...}
    #缩进写法
    k:
      k1: v1
      k2: v2
      ...
    
  • 数值:一组按次序排列的值。array、list、queue

    #行内写法
    k: [v1,v2,v3,...]
    #缩进写法
    k:
      - v1
      - v2
      - ...
    
  • 示例

    @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> animal;
        private Map<String, Object> score;
        private Set<Double> salarys;
        private Map<String, List<Pet>> allPets;
    }
    
    @Data
    public class Pet {
        private String name;
        private Double weight;
    }
    
    # yaml表示以上对象
    person:
      userName: zhangsan
      boss: false
      birth: 2019/12/12 20:12:33
      age: 18
      pet: 
        name: tomcat
        weight: 23.4
      interests: [篮球,游泳]
      animal: 
        - jerry
        - mario
      score:
        english: 
          first: 30
          second: 40
          third: 50
        math: [131,140,148]
        chinese: {first: 128,second: 136}
      salarys: [3999,4999.98,5999.99]
      allPets:
        sick:
          - {name: tom}
          - {name: jerry,weight: 47}
        health: [{name: mario,weight: 47}]
    

配置yaml提示

自定义的类和配置文件绑定一般没有提示。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<configuration>
    <excludes>
        <exclude>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </exclude>
    </excludes>
</configuration>

Web 开发

简单功能分析

静态资源目录

只要静态资源放在类路径下:called /static or /public or /resources or /META-INF/resources

  • 访问:当前项目跟路径/+静态资源名
  • 原理:静态资源映射/**
    • 请求进来,先取找Controller 看能不能处理,不能处理的所有请求都交给静态资源处理器,静态资源也找不到则响应404页面

更改默认的静态资源路径和静态资源访问前缀(默认是没有前缀的)

spring:
  mvc:
  #给静态资源添加前缀,访问时需要加上/res/
    static-path-pattern: /res/**
  #改变默认的静态资源路径,类路径下haha/目录
  resources:
    static-locations: [classpath:/haha/]

webjar

自动映射 /webjars/**

https://www.webjars.org/

欢迎页支持

  • 在静态资源路径下的index.html

    • 可以配置静态资源路径

    • 不能配置静态资源的访问前缀,否则导致 index.html 不能被默认访问

      spring:
        mvc:
          static-path-pattern: /res/** #导致欢迎页失效 和favicon 失效
      

自定义 Favicon

  • favicon.ico 放在静态资源目录下即可

静态资源配置原理

  • SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)

  • SpringMVC功能的自动配置类 WebMvcAutoConfiguration,失效

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
            ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {}
    
  • 给容器配置的内容

    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
    
  • 配置文件的相关属性和xxx进行了绑定。WebMvcProperties==spring.mvc、ResourceProperties==spring.resources

配置类只有一个有参构造器

    //有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
//DispatcherServletPath  
//ServletRegistrationBean   给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, 
                            WebMvcProperties mvcProperties,ListableBeanFactory beanFactory,
                            ObjectProvider<HttpMessageConverters> messageConvertersProvider,
                            ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                            ObjectProvider<DispatcherServletPath> dispatcherServletPath,
                            ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
    this.resourceProperties = resourceProperties;
    this.mvcProperties = mvcProperties;
    this.beanFactory = beanFactory;
    this.messageConvertersProvider = messageConvertersProvider;
    this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
    this.dispatcherServletPath = dispatcherServletPath;
    this.servletRegistrations = servletRegistrations;
}

资源处理的默认规则

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    if (!this.resourceProperties.isAddMappings()) {
        logger.debug("Default resource handling disabled");
        return;
    }
    Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
    CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
    //webjars的规则
    if (!registry.hasMappingForPattern("/webjars/**")) {
        customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }

    //
    String staticPathPattern = this.mvcProperties.getStaticPathPattern();
    if (!registry.hasMappingForPattern(staticPathPattern)) {
        customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                                             .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                                             .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
    }
}
spring:
  resources:
    add-mappings: false   禁用所有静态资源规则
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
            "classpath:/resources/", "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

欢迎页面处理规则

//HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求。  
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
                                                           FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
        new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
        this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
                          ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
    if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
        //要用欢迎页功能,必须是/**
        logger.info("Adding welcome page: " + welcomePage.get());
        setRootViewName("forward:index.html");
    }
    else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
        //不是则调用Controller  /index
        logger.info("Adding welcome page template: index");
        setRootViewName("index");
    }
}

Favicon

浏览器会发送 /favicon.ico 请求获取到图标,整个session 期间不再获取

请求参数处理

请求映射

REST 使用与原理
  • @GetMapping、@PostMapping、@PutMapping、@DeleteMapping

  • Rest 风格支持(使用HTTP请求方式动词来表示对资源的操作)

    • 请求风格:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
    • 核心Filter:HiddenHttpMethodFilter
      • 用法:表单method=post,隐藏域_mthod=put/delete
      • SpringBoot中手动开启
    spring:
      mvc:
        hiddenmethod:
          filter:
            enabled: true   #开启页面表单的Rest功能
    

Rest 原理(表单提交要使用REST的时候)

  • 表单提交会带上 _method=PUT
  • 请求过来被HiddenHttpMehodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到_method的值
      • 兼容一下请求:PUT DELETE PATCH
      • 原生 request (post),包装模式requestWapper重写了getMethod方法,返回的是传入的值
      • 过滤器链放行的时候用wrapper,以后的方法调用getMehod是调用requestWrapper中的方法

Rest 使用客户端工具

  • 如使用PostMan 直接发送Put、Delete等方式请求,无需Filter

自定义filter

  • 此时表单中隐藏域的参数就不是 _method
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");+
    return methodFilter;
}
请求映射原理

SpringMVC 功能分析都从 org.springframework.web.servlet.DispatcherServlet -> doDispatch()

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

所有的请求映射都在HandlerMapping中

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping ,访问 / 能访问到index.html

  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping

  • 请求进来之后,挨个尝试所有的HandlerMapping看是否有请求信息。

    • 如果有就找到这个请求对应的handler
    • 如果没有就找下一个 HandlerMapping
  • 我们需要一些自定义的映射处理。我们也可以自己给容器中放HandlerMapping 自定义一个请求映射规则

普通参数与基本注解

注解

  • @PathVariable:映射 URL 绑定的占位符,可以将 URL 中占位符参数绑定到控制器处理方法的入参中

  • @RequestHeader:用于获取请求消息头

  • @RequestParam:用于获取请求参数

  • @CookieValue:获取Cookie的值

  • @RequestBody:用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的),前端不能使用GET方式提交数据,而是用POST方式进行提交,@RequestBody最多只能有一个

  • @RequestAttribute:获取request域属性

  • @MatrixVariable:矩阵变量

    • 当cooki被禁用时,可以使用矩阵变量把url重写将cookie的值传递进来
    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    //      手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    //              removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    //方式一:
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        };
    }
    
    //方式二:
    @Configuration(proxyBeanMethods = false)
    public class WebConfig  implements WebMvcConfigurer{
    
        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            //不移除 ; 后面的的内容,矩阵变量就可以失效
            urlPathHelper.setRemoveSemicolonContent(false);
            configurer.setUrlPathHelper(urlPathHelper);
        }
    }
    
    
    // /cars/sell;low=34;brand=byd,audi,yd
    @ResponseBody
    @GetMapping("/cars/{path}")
    public Map<String,Object> carsSell(@MatrixVariable("low")Integer low,
                                       @MatrixVariable("brand")List<String> brand,
                                       @PathVariable("path")String path){
        HashMap<String, Object> map = new HashMap<>();
        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }
    //当传递的参数名相同时
    // /boss/1;age=20/2;age=10
    @ResponseBody
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();
    
        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;
    
    }
    
    

Servlet API

SpringMVC也支持原生的 Servlet API

//WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、
//Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

//ServletRequestMethodArgumentResolver  以上的部分参数
@Override
public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
            ServletRequest.class.isAssignableFrom(paramType) ||
            MultipartRequest.class.isAssignableFrom(paramType) ||
            HttpSession.class.isAssignableFrom(paramType) ||
            (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
            Principal.class.isAssignableFrom(paramType) ||
            InputStream.class.isAssignableFrom(paramType) ||
            Reader.class.isAssignableFrom(paramType) ||
            HttpMethod.class == paramType ||
            Locale.class == paramType ||
            TimeZone.class == paramType ||
            ZoneId.class == paramType);
}

复杂参数

  • Map、Model(map、mode里面的数据会被放在request的请求域中 request.setAttribute)

    @GetMapping("/torequest/{data}")
        public String toRequest(@PathVariable("data")String data,
                                Map<String,Object> map,
                                ModelMap modelMap,
                                Model model){
            map.put("data",data);
            modelMap.addAttribute("data1",data);
            model.addAttribute("data2",data);
            return "forward:/data";
        }
    
        @ResponseBody
        @GetMapping("/data")
        public Map<String,Object> getRequest(HttpServletRequest request){
            Object data = request.getAttribute("data");
            Object data1 = request.getAttribute("data1");
            Object data2 = request.getAttribute("data2");
            HashMap<String, Object> map = new HashMap<>();
            map.put("data",data);
            map.put("data1",data1);
            map.put("data2",data2);
            return map;
        }
    /*
    {
      "data": "AAAAA",
      "data2": "AAAAA",
      "data1": "AAAAA"
    }
    */
    
  • Errors/BindingResult、RedirectAttributes(重定向携带数据)、ServletResponse(response)

  • SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

参数处理原理

  • HandlerMapping中找到能处理请求的Handler(Controller.method())
  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
  • 适配器执行目标方法并确定方法参数的每一个值

适配器执行流程

  • HandlerAdapter 找到合适的一个 Adapter 执行它的目标方法
  • 通过参数解析器 HandlerMthodArgumentResolver 确定将要执行目标方法的每一个参数的值是什么
    • SpringMVC 目标方法可以有26种参数解析器,外加我们还可以自定义参数解析器
    • 当前解析器是否支持解析这种参数,支持就调用 resolveArgument 方法
      • 它内部是一个for循环语句 挨个判断所有的参数解析器,看哪个支持解析这个参数
  • 最后将返回的值交由值处理器

自定义类型参数 封装POJO

  • 如果为自定义类型参数,那么就会交由ServletModelAttributeMethodProcessor 这个参数处理器支持

  • 会调用一个**isSimpleValueType(Class<?> type)**判断是否为简单类型

  • 再通过WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);创建WebDataBinder 对象

    • WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
    • WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型,再封装到JavaBean中
      • GenericConversionService:在设置每一个值的时候,找它里面的所有converter遍历出可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
  • 我们还可以给WebDataBinder里面放一个自定义数据解析的Converter

    @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) {
                //添加一个自定义解析器(Converter)
                registry.addConverter(new Converter<String, Pet>() {
                    @Override
                    public Pet convert(String s) {
                        if (!s.isEmpty()){
                            String[] split = s.split(",");
                            return new Pet(split[0],Integer.parseInt(split[1]));
                        }
                        return null;
                    }
                });
            }
        };
    }
    

目标方法执行完成

  • 执行完成后所有的数据都会放在 ModelAndViewContainer,里面还包含了要去的页面地址 View

处理派发结果

  • processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

  • renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

    //暴露模型作为请求域属性
    exposeModelAsRequestAttributes(model, request);
    
    protected void exposeModelAsRequestAttributes(Map<String, Object> model,
                HttpServletRequest request) throws Exception {
        //model中的所有数据遍历挨个放在请求域中
            model.forEach((name, value) -> {
                if (value != null) {
                    request.setAttribute(name, value);
                }
                else {
                    request.removeAttribute(name);
                }
            });
        }
    

数据响应与内容协商

响应JSON

  • jackson.jar + @ResponseBody
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--web场景自动引入了json场景-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
</dependency>

返回值解析器原理

  • 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  • 返回值处理器调用 handleReturnValue 进行处理
  • RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 的注解
    • 利用 MessageConverters 进行处理 将数据写为json
      • 1、内容协商:浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9y0piySB-1619273809025)(D:\笔记\2021-3-26_16-40.png)]
      • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
      • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
        • 1、找到 MappingJackson2HttpMessageConverter可以将对象写为json
        • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去

SpringMVC支持的返回值

* ModelAndView
* Model
* View
* ResponseEntity 
* ResponseBodyEmitter
* StreamingResponseBody
* HttpEntity
* HttpHeaders
* Callable
* DeferredResult
* ListenableFuture
* CompletionStage
* WebAsyncTask
*@ModelAttribute 且为对象类型的
* @ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;

内容协商

  • 根据客户端接收能力不同,返回不同媒体类型的数据
  • 引入XML依赖
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

使用 postman测试

  • 改变请求头中Accept字段,Http协议中规定的,告诉服务器本客户端可以接收的数据类型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WfV39b8-1619273809032)(D:\笔记\Snipaste_2021-03-26_20-09-55.png)]

开启浏览器参数方式内容协商功能

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

发请求带上

http://localhost:8080/goto?format=json?format=json/xml

在浏览器上测试好像失败,但是重写了添加如下方法之后恢复正常

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    //Map<String, MediaType> mediaTypes
    Map<String, MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    //指定支持解析哪些参数对应的哪些媒体类型
    ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);

    HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

    configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}

自定义MessageConverter

实现多协议数据兼容,(json、xml、z-kanan)

  • @ResponseBody响应数据出去调用RequestResponseBodyMethodProcessor 处理
  • Processor 处理方法返回值,通过MessgeConverter处理
  • 所有MessgeConverter合起来可以支持各种媒体类型的操作(读、写)
  • 内容协商找到最终的messageConverter
@Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {

            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
				 converters.add(new ZKananMessageConverter()); //添加一个自定义的MessageConverter
            }
        }
    }
//自定义的MessageConverter
public class ZKananMessageConverter implements HttpMessageConverter<Person> {
    @Override
    public boolean canRead(Class<?> aClass, MediaType mediaType) {
        return false;
    }

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

    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
        return  MediaType.parseMediaTypes("application/z-kanan");
    }

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

    @Override
    public void write(Person person, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
        //自定义协议数据写出格式
        String data = person.getName()+";"+person.getAge()+";"+person.getPet().getName()+";"+person.getPet().getAge();

        //写出去
        OutputStream body = httpOutputMessage.getBody();
        body.write(data.getBytes());
    }
}

有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

Thymeleaf 模板引擎

  • 是一个现代化的服务端Java模板引擎

thymeleaf 使用

  • 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 引入以后,它已经帮我们配置好了一切,我们只需要关注页面开发即可
//将文件放在类路径下的templates/
public static final String DEFAULT_PREFIX = "classpath:/templates/";
//文件以.html结尾
public static final String DEFAULT_SUFFIX = ".html";  //xxx.html

页面开发

  • 引入空间名称
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">  <!引入空间名称 >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
</body>
</html>

基本语法

表达式
表达式名字语法用途
变量取值${…}获取请求域、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)
  • if-then-esle:(if)?(then):(else)
  • Default:(value)?:(defaultvalue)
特殊操作
  • 无操作:_

th 表达式

th:text
<p th:text="${user}">数据为空</p>
th:inline
  • HTML 内联
<p th:inline="text">数据:[[${user}]]</p>
  • CSS 内联
<style type="text/css" th:inline="css">
    body{
        background-color: [[${color}]]
    }
</style>
  • JavaScript 内联
<script type="css/javascript" th:inline="javascript"></script>
th:object 和 *
<div th:object="${user}">
    <p th:text="*{name}"></p>
    <p th:text="*{password}"></p>
</div>

循环

  • th:each
<table>
    <caption>User 信息列表</caption>
    <thead>
        <tr>
            <th>ID</th>
            <th>NAME</th>
            <th>PASSWORD</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="i:${users}">
            <td th:text="${i.id}"></td>
            <td th:text="${i.name}"></td>
            <td th:text="${i.password}"></td>
        </tr>
    </tbody>
</table>

条件判断

  • th:if :如果条件不成立,这一列标签就根本不会存在
<p th:if="${user} == null">信息为空</p>
  • th:swich 和 th:case
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="'manager'">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

更多使用方法请参考官方网站:https://www.thymeleaf.org/documentation.html

碎片使用

碎片的概念:就是 HTML 片段,我们可以将多个页面中使用的相同的 HTML 标签部分单独抽取出来,然后通过 th:include 可以在 HTML 网页中引入定义的碎片

在这里插入图片描述

  • header.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">  <!引入空间名称 >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="frament1">青山不改,绿水长流 你我山巅自相逢!</div>
</body>
</html>
  • footer.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">  <!引入空间名称 >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="frament2">乾坤未定,你我皆黑马!</div>
</body>
</html>
  • index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">  <!引入空间名称 >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:replace="header::frament1"></div>
    <div>
        自定义内容
    </div>
    <div th:include="footer::frament2"></div>
</body>
</html>
  • 效果图

在这里插入图片描述

可以发现使用th:include 只是把里面的内容引用过来所以没有样式

而使用th:replace 就是这个div 替换为要引用的 div 所以样式也带了过来

关闭缓存

它默认缓存是开启的,我们可以使用yaml配置文件进行设置

spring:
  thymeleaf:
    cache: false #关闭缓
  #thymeleaf的默认设置
#    prefix: classpath:/templates/
#    suffix: .html

然后配合设置就可以实现实时更新页面

image-20210329171646324

拦截器

HandlerInterceptor 接口

package com.z.admin.controller.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * @author ZHOUJIEStart
 * Email:z2371099771@163.com
 * @create 2021-03-27-13:28
 *
 * 登入检查
 */
@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("拦截的请求路径是{}",requestURI);

        //登入检查逻辑
        HttpSession session = request.getSession();
        if (session.getAttribute("loginUser") != null){

            //放行
            return true;
        }
        //跳转到登录页
        request.setAttribute("msg","请先登入");
        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 {

    }
    /**
     * 页面渲染以后
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

配置拦截器

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**")//要拦截的东西,拦截所有包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/js/**","/images/**");//不拦截的资源
    }
}

拦截器原理

  • 根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有拦截器)
  • 先来顺序执行所有的拦截器的 preHandle方法
    • 如果当前拦截器 preHandler返回为tre,则执行下一个拦截器的 preHandle
    • 如果当前拦截器返回为 false,直接倒序执行已经拦截了的拦截器的 afterCompletion方法
  • 如果任何一个拦截器返回 false,则直接跳出,不执行目标方法
  • 所有的拦截器都返回为true,才会执行目标方法
  • 倒叙执行所有拦截器的postHandle方法
  • 前面的步骤如果有任何异常产生,都会直接倒叙触发 afterCompletion方法
  • 等到页面成功渲染完成以后,也会倒叙的触发 afterCompletion方法

文件上传

表单页面

<form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="headerImg"><br>
    <input type="file" name="photos" multiple><br>
    <input type="submit" value="提交">
</form>

文件上传代码

  /**
     * MultipartFile 自动封装上传过来的文件
     * @param headerImg
     * @param photos
     * @return
     */
    @PostMapping("/upload")
    public String upload(@RequestPart("headerImg") MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws IOException {

        log.info("上传的信息:headerImg={},photos={}",headerImg.getSize(),photos.length);

        if(!headerImg.isEmpty()){
            //保存到文件服务器,或者OSS服务器
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.transferTo(new File("D:\\cache\\"+originalFilename));
        }

        if(photos.length > 0){
            for (MultipartFile photo : photos) {
                if(!photo.isEmpty()){
                    String originalFilename = photo.getOriginalFilename();
                    photo.transferTo(new File("D:\\cache\\"+originalFilename));
                }
            }
        }
        return "main";
    }

设置文件上传大小

spring:
  servlet:
    multipart:
      max-file-size: 4MB #单个文件不超过4MB
      max-request-size: 15MB #总共不超过15MB

文件上传自动配置原理

  • 文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

    • 它里面自动配置好了 一个文件上传的解析器StandardServletMultipartResolver
  • 原理步骤:

    • 请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
    • 参数解析器来解析请求中的文件内容,封装成一个MultipartFile
    • 将request中的文件信息封装为一个Map;MultiValueMap<String,MultipartFile>
    • 还可以使用 FileCopyUtils 来实现文件流的拷贝

异常处理

错误处理

默认规则:

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

  • 对于机器客户端,它将生成JSON响应,其中包含错误信息,HTTP状态和异常消息的详细信息,对于浏览器客户端,响应一个 whitelabel 错误视图,以HTML 格式出线相同的数据

    JSON 错误格式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jqHkl4yO-1619273809034)(D:\笔记\errorjson.png)]

    Whitelabel 格式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHSJKoQJ-1619273809035)(D:\笔记\errorhtml.png)]

自定义错误处理

  • 要对其进行自定义,添加View解析为error
  • 要完全替换默认行为,可以实现ErrorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有的机制替换其内容
  • error/ 下的4xx、5xx页面会被自动解析
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1wPzh36-1619273809036)(D:\笔记\Snipaste_2021-03-28_10-56-09.png)]

定制错误处理逻辑

  • 添加错误页面在error/404.html,error/5xx.html;有精确的错误状态码页面就会匹配精确,没有就找4xx.html;如果都没有就触发百页

Web 原生组件注入

@ServletComponentScan(basePackages = “com.z.admin”) 指定原生Servlet组件都放在哪里,默认与SpringBoot扫描范围相同

@ServletComponentScan
@SpringBootApplication
public class Boot3WebAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(Boot3WebAdminApplication.class,args);
    }
}

Servlet

@WebServlet("/myServlet") 请求路径

@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("66666");
    }
}

Filter

  • @WebFilter(urlPatterns = {"/css/","/js/"}) 指定拦截的内容 /*原生写法,/**springboot写法
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/js/*"})
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyFilter开始工作");
    }

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

Listener

@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyServletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

方式二:使用RegistrationBean

//(proxyBeanMethods = false) 保证依赖的组件始终是单实例的
@Configuration //默认为true
public class MyRegistConfig {

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

    @Bean
    public FilterRegistrationBean myFilter(){
//       return new FilterRegistrationBean(new MyFilter(),myServlet()); //只拦截servlet的请求地址
        FilterRegistrationBean<MyFilter> filterFilterRegistrationBean = new FilterRegistrationBean<>(new MyFilter());
        filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/myServlet"));//自己设置要拦截的路径
        return filterFilterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
        return new ServletListenerRegistrationBean<>(new MyServletContextListener());
    }
}

执行过程

  • 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties 对应的配置文件配置项是spring.mvc
  • 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来
  • 默认映射的是 / 路径
  • 而Tomcat-Servlet我们配置的是 /my 多个Servlet都能处理到同一层路径
    • 由于精确优先原则,如果发送的/my 那么直接走Tomcat-Servlet,所以springboot的拦截器拦不到这个请求
    • 而发送的 / **走DispatcherServlet 拦截器就会进行拦截

嵌入式Servlet容器

切换容器

  • springboot 的web Starter中默认支持的是Tomcat的服务器
  • 而在springoot中它同时支持 Tomcat 、Jetty、Undertow 三种服务器

在依赖中配置别的服务器依赖即可进行替换

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

定制Servlet容器

  • 实现 WebServerFactoryCustomizer
    • 把配置文件的值和 ServletWevServerFactory 进行绑定
  • 修改配置文件 server.xxx
  • 直接自定义 ConfigurableServletWebServerFactory

xxxCustomizer:代表某某定制化器,可以改变某某一个默认规则

数据访问

使用默认数据源-HikariDataSource

导入JDBC场景

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

由于springboot 不知道我们要操作具体哪个数据库,所以我们需要手动添加驱动

  • 我们想要操作哪个数据库,就添加哪个数据库的驱动

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    
  • mysql默认数据库版本为 8,如果我们自己的数据库版本不同,可以取消它的版本仲裁,自己添加版本

    默认版本:<mysql.version>8.0.22</mysql.version>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
    <!--            <version>5.1.49</version>-->
            </dependency>
    想要修改版本
    1、直接依赖引入具体版本(maven的就近依赖原则)
    2、重新声明版本(maven的属性的就近优先原则)
        <properties>
            <java.version>1.8</java.version>
            <mysql.version>5.1.49</mysql.version>
        </properties>
    

分析自动配置

自动配置类:

  • DataSourceAutoConfigyration:数据库的自动配置
    • 修改数据源相关的配置:spring.datasource
    • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
    • 底层配置好的连接池是:HikariDataSource
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
         DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
         DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration
  • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置
  • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
    • 可以修改这个配置项 @ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
    • @Bean @Primary JdbcTemplate 容器中有这个组件
  • JndiDataSourceAutoConfiguration: jndi的自动配置
  • XADataSourceAutoConfiguration: 分布式事务相关的

修改配置项

spring:
  datasource:
    url: jdbc:mysql:///ssm_crud #没写地址端口号,默认本机端口号为3306
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
#    type: com.zaxxer.hikari.HikariDataSource #默认就是HikariDataSource

测试

@Slf4j
@SpringBootTest
public class Boot04DatabaseApplicationTest {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads(){
        Long aLong = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM ssm_crud.tbl_emp", Long.class);
        log.info("记录总数:{}",aLong);  //日志结果 记录总数:1996
    }
}

使用Druid数据源

创建数据源

  • druid官方 github地址:https://github.com/alibaba/druid
  • 添加依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
  • 配置数据源
@Configuration
public class MyDataSourceConfig {

    /**
     * 配置druid数据源
     * @return
     */
    @ConfigurationProperties("spring.datasource") //直接使用配置文件中的数据源配置信息
    @Bean
    public DataSource dataSource() throws SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
//        druidDataSource.setUrl();
//        druidDataSource.setDriverClassName();
//        druidDataSource.setPassword();
//        druidDataSource.setUsername();
        druidDataSource.setFilters("stat,wall"); //开启页面监控功能,和防火墙
        return druidDataSource;
    }

开启页面监控

//druidDataSource.setFilters("stat"); //在数据源中开启页面监控功能
/**
     * 配置druid的监控页功能
     * @return
     */
@Bean
public ServletRegistrationBean statViewServlet(){
    StatViewServlet statViewServlet = new StatViewServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(statViewServlet);
    registrationBean.setUrlMappings(Arrays.asList("/druid/*"));//指定访问路径
    //添加访问密码
    registrationBean.addInitParameter("loginUsername","admin");
    registrationBean.addInitParameter("loginPassword","123456");
    return registrationBean;
}

开启Web应用监控和防火墙

//druidDataSource.setFilters("stat,wall"); //开启页面监控功能,和防火墙
/**
     *WebStatFilter用于采集 web-jdbc关联监控的数据
     * @return
     */
@Bean
public FilterRegistrationBean webStatFilter(){
    WebStatFilter statFilter = new WebStatFilter();
    FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean<>(statFilter);
    //设置监控拦截路径
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
   //不拦截的路径 filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
    return filterRegistrationBean;
}

使用官方Starter方式

  • 引入druid-starter
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>
  • 分析自动配置

    • 扩展配置项 spring.datasource.druid
    • DruidSpringAopConfiguration.class 用来监控SpringBean的,它的配置项 spring.datasource.druid.aop-patterns
    • DruidStatViewServletConfiguration.class, 监控页的配置,配置项:spring.datasource.druid.stat-view-servlet;默认开启
    • DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
    • DruidFilterConfiguration.class}) 所有Druid自己filter的配置
  • 配置项示例

    spring:
      datasource:
        url: jdbc:mysql:///ssm_crud
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
    
        druid:
          aop-patterns: com.z.database.*  #监控SpringBean
          filters: stat,wall     # 底层开启功能,stat(sql监控),wall(防火墙)
    
          stat-view-servlet:   # 配置监控页功能
            enabled: true
            login-username: admin #访问监控页 账号
            login-password: 123456 #密码
            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 #超过1000毫秒的为慢查询
              logSlowSql: true #是否记录慢查询
              enabled: true
            wall:
              enabled: true #防火墙功能是否开启
              config:
                drop-table-allow: false #开启拦截不允许删表操作
    
SpringBoot配置实例
  • https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
详细配置项列表

整合 MyBatis

  • 导入mybatis的依赖
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
  • 使用 yaml 对mybatis 进行配置
    • 如果全局配置写在配置文件的xml中,那么就指定 config-location的地址
    • 也可以直接在 yaml中配置 configuration 中,就不需要config的xml文件了
# 配置mybatis规则
mybatis:
  #config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml

  configuration: #指定mybatis全局配置文件的相关项
    map-underscore-to-camel-case: true  # 开启驼峰命名规则
  • 编写mapper接口,使用 @Mapper 标注
@Mapper
public interface EmployeeMapper {

    Employee getEmp(Integer empId);
}
  • 编写sql映射文件并在namespace中绑定mapper接口
<?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.z.database.mapper.EmployeeMapper">

    <select id="getEmp" resultType="com.z.database.bean.Employee">
          SELECT * FROM ssm_crud.tbl_emp WHERE emp_id = #{empId};
    </select>
    
</mapper>

MyBatis 注解混合

  • 对于一些简单的 sql 语句,我们可以直接在mapper接口的方法上添加注解
@Mapper
public interface EmployeeMapper {
    @Select("SELECT * FROM ssm_crud.tbl_emp ORDER BY emp_id")
    List<Employee> getEmpAll();
}
  • 关于 @Mapper 注解,可以直接在SpringBoot启动方法上标注 @MapperScan,就不用每个mapper接口都需要加上 @mapper
@MapperScan("com.z.database.mapper") //默认扫描同主配置扫描范围相同,也可以直接定义
@SpringBootApplication
public class Boot04DatabaseApplication {

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

NoSQL

Redis 自动配置

  • 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 自动配置:
    • RedisAutoConfigration 自动配置类。 RedisProperties 属性类–>spring.redis.xxx 是对redis的配置
    • 连接工厂是准备好的,LettuceConnectionConfiguration、JedisConnectionConfiguration
    • 自动注入了RedisTemplate<Object, Object> : xxxTemplate;
    • 自动注入了StringRedisTemplate;k:v都是String
    • 底层只要我们使用 **StringRedisTemplate、**RedisTemplate就可以操作redis
  • 配置连接Redis
 # redis 连接
  redis:
    host: 192.168.147.131
    port: 6379
    password: 123456
  • 测试
@Slf4j
@SpringBootTest
public class Boot04DatabaseApplicationTest {
    @Autowired
    StringRedisTemplate redisTemplate;
    
    @Test
    void testRedis(){
        ValueOperations<String, String> operations = redisTemplate.opsForValue();
        operations.set("k3","v3");
        String k1 = operations.get("k1");
        String k3 = operations.get("k3");
        System.out.println(k1+" "+k3); //v1 v3
    }
}

切换 Jedis

  • 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  • 在 yaml 配置文件中指明客户端类型
spring:
  redis:
      host: 192.168.147.131
      port: 6379
      password: 123456
      client-type: jedis
      jedis:
        pool:
          max-active: 10 #最大池容量

单元测试

SpringBoot 2.2.0 版本开始引入了JUnit 5 作为单元测试默认库

作为最新版本的JUnit框架,JUnit5与之前版本的JUnit框架有很大的不同,现由三个不同子项目的几个不同模块组成

  • JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。

JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。

JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

@ExtendWith(SpringExtension.class)//导入spring测试框架
@SpringBootTest
class ApiApplicationTests {

	@Test
	void contextLoads() {

	}
}

JUnit 5 常用注解

JUnit5的注解与JUnit4的注解有所变化及详细介绍:https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

  • @DisplayName:为测试类或者测试方法设置展示名称

  • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试

  • @ParameterizedTest :表示方法是参数化测试

  • @RepeatedTest:表示方法可重复执行

  • @BeforeEach:表示在每个单元测试之前执行

  • @AfterEach:表示在每个单元测试之后执行

  • @BeforeAll:表示在所有单元测试之前执行

  • @AfterAll:表示在所有单元测试之后执行

  • @Tag:表示单元测试类别,类似于JUnit4中的@Categories

  • @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore

  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误

  • @ExtendWith:为测试类或测试方法提供扩展类引用

断言(assertions)

断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行检验,这些断言方法都是org.junit.jupiter.api.Assertions 的静态方法,它们检查业务逻辑返回的数据是否合理,且所有的测试运行结束以后,会有一个详细的测试报告

简单断言

  • 用来对单个值进行简单的验证
方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不相等
assertSame判断两个对象引用是否指向同一个对象
assertNotSame判断两个对象引用是否指向不同的对象
assertTrue判断给定的布尔值是否为 true
assertFalse判断给定的布尔值是否为 false
assertNull判断给定的对象引用是否为 null
assertNotNull判断给定的对象引用是否不为 null
@Test
@DisplayName("simple assertion")
public void simple() {
     assertEquals(3, 1 + 2, "simple math");
     assertNotEquals(3, 1 + 1);

     assertNotSame(new Object(), new Object());
     Object obj = new Object();
     assertSame(obj, obj);

     assertFalse(1 > 2);
     assertTrue(1 < 2);
    
     assertNull(null);
     assertNotNull(new Object());
}

数组断言

  • 通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
public void array() {
 assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
}

组合断言

  • assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("assert all")
public void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}

异常断言

  • 在JUnit4时期,想要测试方法的异常情况时,需要用**@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()** ,配合函数式编程就可以进行使用。

    @Test
    @DisplayName("异常测试")
    public void exceptionTest() {
        ArithmeticException exception = Assertions.assertThrows(
               //扔出断言异常
                ArithmeticException.class, () -> System.out.println(1 % 0));
    
    }
    

超时断言

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

快速失败

  • 通过 fail 方法直接使得测试失败
@Test
@DisplayName("fail")
public void shouldFail() {
 fail("This should fail");
}

前置条件(assumptions)

前置条件类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法的前提,当该前提不满足时,就没有继续执行的必要

  • assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止
  • assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
@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")
    );
 }
}

嵌套测试

可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。

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

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用**@ValueSource**等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
  • @NullSource: 表示为参数化测试提供一个null的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参
  • @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");
}

迁移指南

在进行迁移的时候需要注意如下的变化:

  • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
  • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
  • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
  • 把@Ignore 替换成@Disabled。
  • 把@Category 替换成@Tag。
  • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。

指标监控

SpringBoot Actuator

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

  • 引入场景依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 访问http://localhost:8080/actuator/**
  • 设置暴露所有监控信息为HTTP
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露
  • 访问格式:http://localhost:8080/actuator/endpointName/detailPath
    • http://localhost:8080/actuator/beans
    • http://localhost:8080/actuator/configprops
    • http://localhost:8080/actuator/metrics
    • http://localhost:8080/actuator/metrics/jvm.gc.pause

Actuator Endpoint

  • 常用端点介绍
ID描述
auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
beans显示应用程序中所有Spring Bean的完整列表。
caches暴露可用的缓存。
conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops显示所有@ConfigurationProperties
env暴露Spring的属性ConfigurableEnvironment
flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
health显示应用程序运行状况信息。
httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
info显示应用程序信息。
integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
loggers显示和修改应用程序中日志的配置。
liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
metrics显示当前应用程序的“指标”信息。
mappings显示所有@RequestMapping路径列表。
scheduledtasks显示应用程序中的计划任务。
sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown使应用程序正常关闭。默认禁用。
startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
threaddump执行线程转储。
  • 如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID描述
heapdump返回hprof堆转储文件。
jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus

Health:监控状况

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告,很多的健康检查默认已经自动配置好了,比如:数据库、redis等可以很容易的添加自定义的健康检查机制

Metrics:运行时指标

提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已有Metrics

暴露Endpoints

支持的暴露方式:

  • HTTP:默认只暴露 health 和 info Endpoint
  • JMX:默认暴露所有的Endpoint
  • 除过health和info,剩下的Endpoint都应该进行保护访问,如果引入SpringSecurity,则默认配置安全访问规则
IDJMXWeb
auditeventsYesNo
beansYesNo
cachesYesNo
conditionsYesNo
configpropsYesNo
envYesNo
flywayYesNo
healthYesYes
heapdumpN/ANo
httptraceYesNo
infoYesYes
integrationgraphYesNo
jolokiaN/ANo
logfileN/ANo
loggersYesNo
liquibaseYesNo
metricsYesNo
mappingsYesNo
prometheusN/ANo
scheduledtasksYesNo
sessionsYesNo
shutdownYesNo
startupYesNo
threaddumpYesNo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值