JAVA入门基础_从零开始的培训_SpringBoot2入门

SpringBoot2快速入门

配置maven仓库的配置文件

<mirrors>
      <mirror>
        <id>nexus-aliyun</id>
        <mirrorof>central</mirrorof>
        <name>Nexus aliyun</name>
        <url>http://maven.aliyun.com/nexus/content/groups/public</url>
      </mirror>
  </mirrors>

  <profiles>
         <profile>
              <id>jdk-1.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>
              </properties>
         </profile>
  </profiles>

创建一个Maven项目,修改pom文件添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelversion>4.0.0</modelversion>
    <!-- -->
    <parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.6.11</version>
        <relativepath> <!-- lookup parent from repository -->
    </relativepath></parent>
    <groupid>com.codestars</groupid>
    <artifactid>first_springboot</artifactid>
    <version>0.0.1-SNAPSHOT</version>
    <name>first_springboot</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- 引入web开发模块 -->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-web</artifactid>
        </dependency>
        <!-- 简化POJO类的开发 -->
        <dependency>
            <groupid>org.projectlombok</groupid>
            <artifactid>lombok</artifactid>
            <optional>true</optional>
        </dependency>
        <!-- 整合junit配置 -->
        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-starter-test</artifactid>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 配置了该插件后,将项目打包成jar包后,可以通过 java -jar 直接运行-->
            <plugin>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-maven-plugin</artifactid>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupid>org.projectlombok</groupid>
                            <artifactid>lombok</artifactid>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

创建一个Controller控制器

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello world&#xFF01; &#x4F60;&#x597D;&#x5440;";
    }
}

运行项目即可,最终的项目目录如下

JAVA入门基础_从零开始的培训_SpringBoot2入门

了解自动配置原理

依赖管理及其父项目(版本仲裁)

  • spring-boot-dependencies 中配置了几乎所有我们项目开发所需要用到依赖的版本
  • 因此我们项目中引入各种依赖时,基本无需关注版本的问题,会 自动进行版本仲裁,如果需要修改,则自己在引入依赖时修改即可。
- &#x6211;&#x4EEC;&#x9879;&#x76EE;&#x4E2D;&#x5F15;&#x5165;&#x7684;&#x7236;&#x5DE5;&#x7A0B;&#x662F;spring-boot-starter-parent
<parent>
        <groupid>org.springframework.boot</groupid>
        <artifactid>spring-boot-starter-parent</artifactid>
        <version>2.3.4.RELEASE</version>
</parent>

- &#x800C;spring-boot-starter-parent&#x7684;&#x7236;&#x5DE5;&#x7A0B;&#x662F;
<parent>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-dependencies</artifactid>
    <version>2.3.4.RELEASE</version>
</parent>

所有场景启动器spring-boot-starter中最底层的依赖

  • 只要见到spring-boot-starter-xxx就代表是spring官方开发的场景启动器
  • 只要引入了相对应场景的启动器starter,那么就会为我们引入大部分常规所需要的依赖
  • 一旦见到了xxx-spring-boot-starter的场景启动器,一般就是由第三方提供整合springboot的启动器
<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter</artifactid>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

我们的springboot的web项目为什么可以直接启动呢?

  • 我们引入的web场景启动器中,可以看到如下依赖
    <dependency>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-tomcat</artifactid>
      <version>2.6.11</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>org.springframework</groupid>
      <artifactid>spring-web</artifactid>
      <version>5.3.22</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupid>org.springframework</groupid>
      <artifactid>spring-webmvc</artifactid>
      <version>5.3.22</version>
      <scope>compile</scope>
    </dependency>

  • springboot在项目启动时,为我们加载了内置的tomcat帮我们运行项目
  • 并且会对web场景进行一些默认的自动配置,例如添加当初在web.xml文件中配置的编码过滤器、HiddenMethodFilter、在springmvc中配置的注解驱动、访问静态资源等等到一系列功能。
  • 而且其中的各种配置类,都会映射到类似于xxxxxProperties这样的类并加载到IOC容器中

容器功能

@Configuration注解

  • 标识在类上,指明一个类为一个spring的配置类

Full模式与Lite模式

  • 在springboot2之后,可以在@Configuration类中添加proxyBeanMethods属性
  • Full模式
- &#x914D;&#x7F6E;&#x4E3A;true(&#x9ED8;&#x8BA4;)&#xFF0C;&#x4EE3;&#x8868;Full&#x6A21;&#x5F0F;&#xFF0C;&#x4F1A;&#x4E3A;&#x5F53;&#x524D;&#x7684;&#x914D;&#x7F6E;&#x7C7B;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x4EE3;&#x7406;&#x5BF9;&#x8C61;&#xFF0C;&#x6BCF;&#x6B21;&#x8C03;&#x7528;&#x5176;&#x4E2D;&#x5E26;&#x6709;@Bean&#x6CE8;&#x89E3;&#x7684;&#x65B9;&#x6CD5;&#x65F6;&#xFF0C;&#x90FD;&#x4F1A;&#x5148;&#x53BB;spring&#x7684;ioc&#x5BB9;&#x5668;&#x4E2D;&#x5BFB;&#x627E;&#x3002; &#xFF08;&#x5F71;&#x54CD;&#x6548;&#x7387;&#xFF09;
  • Lise模式(效率高)
// &#x914D;&#x7F6E;&#x4E3A;false&#xFF0C;&#x4EE3;&#x8868;Lite&#x6A21;&#x5F0F;&#xFF0C;&#x5982;&#x679C;&#x901A;&#x8FC7;&#x8C03;&#x7528;&#x8BE5;&#x914D;&#x7F6E;&#x7C7B;&#x4E2D;&#x7684;&#x65B9;&#x6CD5;&#xFF0C;&#x4F1A;&#x76F4;&#x63A5;&#x751F;&#x6210;&#x4E00;&#x4E2A;&#x65B0;&#x7684;&#x5BF9;&#x8C61;&#xFF0C;&#x4E0D;&#x4F1A;&#x4F7F;&#x7528;&#x5230;spring&#x7684;ioc&#x5BB9;&#x5668;&#x4E2D;&#x5DF2;&#x7ECF;&#x88C5;&#x8F7D;&#x597D;&#x7684;&#x5BF9;&#x8C61;&#x3002;

@Bean注解

  • 标识在方法上,需要在spring的配置类中使用
  • 该方法的 方法名会作为组件的id, *方法的返回值会作为组件的类型

@Import注解

  • 标识上类上,通常与@Configuration注解配合使用,表示 *将一个类装载到IOC容器当中

@Conditional派生注解

  • 标识在类上,如果该类满足@Conditional所标识的规则时,就加载该组件(组件注入),否则就不加载该组件。

@ImportResource

  • 如果想要引入一些组件,而这些组件配置在xml文件中,则可以通过该注解指定xml文件的路径,将xml文件中配置的组件装载到spring的ioc容器当中

配置绑定的2种方式

@Component 配合 @ConfigurationProperties注解完成组件依赖注入

  • 在spring的配置文件中添加组件的值
user.name=&#x5F20;&#x4E09;
user.age=15
  • 在需要注入的Bean上添加上如下注解
@Component
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private Integer age;
}

@ConfigurationProperties配合@EnableConfigurationProperties完成组件依赖注入

  • @ConfigurationProperties标注在需要注册的组件上
@ConfigurationProperties(prefix = "user")
public class User {
    private String name;
    private Integer age;
}
  • @EnableConfigurationProperties配置在一个配置类上,指定需要注入的组件
@EnableConfigurationProperties(User.class)
@Configuration
public class MyConfigTest {
}

自动配置原理

@SpringBootApplication注解

  • 该注解点开之后,发现这是一个组合注解,其中有3个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

@SpringBootConfiguration

  • 该注解包装了@Configuration注解,实质上就是一个spring的配置类

@ComponentScan

  • 该注解仅是配置了包扫描

核心注解 @EnableAutoConfiguration 开启自动配置

  • 该组件仍然是一个组合组件,其中有2个注解
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration

@AutoConfigurationPackage 注解,自动配置包

  • 该注解中配置了一个@Import向spring的ioc容器中导入组件
@Import(AutoConfigurationPackages.Registrar.class)
  • AutoConfigurationPackages.Registrar这个类是一个静态内部类,其中导入了一堆组件,类的定义如下
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

        @Override
        public Set<object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }

    }
</object>
  • 其中的registerBeanDefinitions方法中调用了register方法,为我们容器中注册了一些组件,而我们现在需要知道它注册了什么组件
  • 因此打了一个断点在registerBeanDefinitions方法中调用register方法的地方进行了查看
  • 这也就是为什么springboot会 扫描我们启动类所在的包及其子包中的所有组件的原因。
        // AnnotationMetadata&#x5F53;&#x524D;&#x6CE8;&#x89E3;&#x7684;&#x5143;&#x6570;&#x636E;&#xFF0C;&#x53EF;&#x4EE5;&#x83B7;&#x53D6;&#x5230;&#x5F53;&#x524D;&#x6CE8;&#x89E3;&#x6240;&#x5728;&#x7684;&#x7C7B;&#x5728;&#x54EA;&#xFF0C;&#x4E5F;&#x5C31;&#x662F;&#x6211;&#x4EEC;&#x5199;&#x7684;&#x542F;&#x52A8;&#x7C7B;
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // new PackageImports(metadata).getPackageNames()&#x5F97;&#x5230;&#x7684;&#x5C31;&#x662F;&#x6211;&#x4EEC;&#x5F53;&#x524D;&#x6CE8;&#x89E3;&#x6240;&#x5728;&#x7C7B;&#x7684;&#x5305;&#x540D;&#xFF0C;&#x6211;&#x8FD9;&#x91CC;&#x662F;&#xFF1A;com.codestars
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }

@Import(AutoConfigurationImportSelector.class)注解(重点)

通过该类的getAutoConfigurationEntry方法得到需要注册到Spring容器中的组件
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // &#x83B7;&#x53D6;&#x5230;&#x9700;&#x8981;&#x52A0;&#x8F7D;&#x5230;spring&#x5BB9;&#x5668;&#x4E2D;&#x7684;&#x6240;&#x6709;&#x7EC4;&#x4EF6;&#x7684;&#x5168;&#x9650;&#x5B9A;&#x7C7B;&#x540D;&#xFF0C;&#x901A;&#x8FC7;getCandidateConfigurations(annotationMetadata, attributes);
        List<string> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        // &#x5BF9;&#x5168;&#x90E8;&#x9700;&#x8981;&#x52A0;&#x8F7D;&#x5230;&#x5BB9;&#x5668;&#x4E2D;&#x7684;&#x7EC4;&#x4EF6;&#x8FDB;&#x884C;&#x4E00;&#x7CFB;&#x5217;&#x7684;&#x8FC7;&#x6EE4;&#xFF0C;&#x9760;&#x7740;&#x5404;&#x4E2A;&#x7EC4;&#x4EF6;&#x4E2D;&#x7684;@Conditional&#x6765;&#x5224;&#x65AD;&#x662F;&#x5426;&#x9700;&#x8981;&#x52A0;&#x8F7D;
        configurations = removeDuplicates(configurations);
        Set<string> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
</string></string>
  • 该方法中会调用 SpringFactoriesLoader.loadFactoryNames()方法,该方法又会调用 loadSpringFactories,这个方法中会获取到 META-INF/spring.factories这个文件,这个文件的路径由 FACTORIES_RESOURCE_LOCATION这个类变量声明,然后读取其中需要加载到Spring容器中的组件。

总结

  • SpringBoot在启动时,会扫描启动类所在的包及其子包,将组件添加到spring的ioc容器当中
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。会从xxxxProperties里面拿。注意组件上用到了@EnableConfigurationProperties这个注解去加载xxxProperties组件。
  • 同时会获取到spring-boot-autoconfigure-2.6.11.jar这个包下的/META-INF/spring.factories中所有需要注册到spirng容器中的组件的全类名,并通过这些组件上的@Conditional注解判断是否将其加载到容器。最终将这些组件都添加到spring容器当中
  • 定制化配置
  • 如果我们想要实现一些定制化功能,比如说不想使用spring为我们自动配置的编码过滤器,我们就可以自己创建一个spring的配置类装载一个编码过滤器。
  • 如果想要修改某个spring帮我们加载的功能的配置,可以通过application.properties修改(yaml也可以的)

最佳开发实践

  • 引入所需要的场景依赖
  • 查看自动配置为我们配置了哪些组件(提供了哪些功能)
  • 判断是否需要修改
  • 是否需要修改某些组件的配置
  • 是否需要自定义部分组件(不使用spring提供的某个组件时)

SpringBoot核心功能

配置文件

  • 配置文件支持2种语法,一种是properties、一种是yaml,yaml可以有2种后缀格式: 分别为: ymlyaml
  • 传统的properties的配置如何进行就不进行阐述了,主要是yml文件如何使用

YAML配置文件的格式

  • 定义一个实体类
@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;
}
</string,></double></string,></string>
  • 在yml中进行文件的配置
yaml&#x8868;&#x793A;&#x4EE5;&#x4E0A;&#x5BF9;&#x8C61;
person:
  userName: zhangsan
  boss: false
  birth: 2019/12/12 20:12:33
  age: 18
  # &#x5BF9;&#x8C61;
  pet:
    name: tomcat
    weight: 23.4
  # &#x6570;&#x7EC4;
  interests: [&#x7BEE;&#x7403;,&#x6E38;&#x6CF3;]
  # &#x96C6;&#x5408;
  animal:
    - jerry
    - mario
  # &#x914D;&#x7F6E;Map&#x96C6;&#x5408;&#xFF0C;&#x6CE8;&#x610F;&#x4E0D;&#x8981;&#x52A0; -
  score:
    english:
      first: 30
      second: 40
      third: 50
    math: [131,140,148]
    chinese: {first: 128,second: 136}
  # &#x914D;&#x7F6E;set&#x96C6;&#x5408;
  salarys: [3999,4999.98,5999.99]
  # &#x914D;&#x7F6E;map&#x96C6;&#x5408;&#xFF0C;map&#x4E2D;&#x88C5;&#x7684;&#x662F;List
  allPets:
    sick:
      - {name: tom}
      - {name: jerry,weight: 47}
    health: [{name: mario,weight: 47}]

如果想要自己编写的实体类在yml中有提示的话

        <dependency>
            <groupid>org.springframework.boot</groupid>
            <artifactid>spring-boot-configuration-processor</artifactid>
            <optional>true</optional>
        </dependency>
        <build>
        <plugins>
            <plugin>
                <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>
            </plugin>
        </plugins>
    </build>

Web开发

简单功能分析

静态资源访问

  • springboot在项目启动时为我们添加一个了一个资源映射: /**,该映射可以匹配到如下3个路径
{
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/"
}

JAVA入门基础_从零开始的培训_SpringBoot2入门
  • 并且我们可以通过 /webjars/&#x8D44;&#x6E90;&#x8DEF;&#x5F84;的方式访问webjars中的静态资源
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
静态资源与Controller的优先级
  • 先找Controller看能不能处理,Controller不能处理再找静态资源处理器
静态资源的访问前缀、欢迎页、自定义Favicon
  • 修改静态资源的访问前缀
spring:
  mvc:
    # &#x9759;&#x6001;&#x8D44;&#x6E90;&#x8BBF;&#x95EE;&#x524D;&#x7F00;&#xFF0C;&#x9ED8;&#x8BA4;&#x4E3A;/**&#xFF0C;&#x6CE8;&#x610F;&#xFF1A;&#x5982;&#x679C;&#x8BBE;&#x7F6E;&#x4E86;&#x9759;&#x6001;&#x8D44;&#x6E90;&#x7684;&#x8BBF;&#x95EE;&#x524D;&#x7F00;&#xFF0C;&#x90A3;&#x4E48;&#x9ED8;&#x8BA4;&#x7684;index&#x9875;&#x9762;&#x548C;favicon&#x90FD;&#x4ECD;&#x7136;&#x4F1A;&#x901A;&#x8FC7;/index &#xFF0C;/favicon&#x627E;&#xFF0C;&#x6240;&#x4EE5;&#x4F1A;&#x5931;&#x6548;
    static-path-pattern: /res/**
  • 默认会在静态资源目录中找
  • index.html
  • favicon.ico

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody 获取请求参数

    @GetMapping("/car/{id}/owner/{username}")
    public Map<string,object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<string,string> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<string,string> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<string> inters,
                                     @RequestParam Map<string,string> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie)

@PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<string,object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }

 //1&#x3001;&#x8BED;&#x6CD5;&#xFF1A; &#x8BF7;&#x6C42;&#x8DEF;&#x5F84;&#xFF1A;/cars/sell;low=34;brand=byd,audi,yd
    //2&#x3001;SpringBoot&#x9ED8;&#x8BA4;&#x662F;&#x7981;&#x7528;&#x4E86;&#x77E9;&#x9635;&#x53D8;&#x91CF;&#x7684;&#x529F;&#x80FD;
    //      &#x624B;&#x52A8;&#x5F00;&#x542F;&#xFF1A;&#x539F;&#x7406;&#x3002;&#x5BF9;&#x4E8E;&#x8DEF;&#x5F84;&#x7684;&#x5904;&#x7406;&#x3002;UrlPathHelper&#x8FDB;&#x884C;&#x89E3;&#x6790;&#x3002;
    //              removeSemicolonContent&#xFF08;&#x79FB;&#x9664;&#x5206;&#x53F7;&#x5185;&#x5BB9;&#xFF09;&#x652F;&#x6301;&#x77E9;&#x9635;&#x53D8;&#x91CF;&#x7684;
    //3&#x3001;&#x77E9;&#x9635;&#x53D8;&#x91CF;&#x5FC5;&#x987B;&#x6709;url&#x8DEF;&#x5F84;&#x53D8;&#x91CF;&#x624D;&#x80FD;&#x88AB;&#x89E3;&#x6790;
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<string> brand,
                        @PathVariable("path") String path){
        Map<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

    @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;

    }
</string,object></string,object></string></string,object></string,string></string></string,string></string,string></string,object>

SpringMVC处理请求的流程

  • 执行DispatcherServlet中的doService方法()
  • 再执行到doDispatch(request, response)方法
  • 从处理器映射器集合中获取该路径能够执行的MappingHandle处理器
  • 通过MappingHandle来获取到HandleAdapter适配器
  • 执行所有该处理器的拦截器的preHandle方法(按照配置顺序执行)
  • 通过HandleAdapter适配器来执行MappingHandle处理器中的方法,处理后得到一个ModelAndView
  • 其中所有Map、Model、ModelMap等使用的都是同一个ModelAndView来存储请求域的数据
  • 放在ModelAndViewContainer中
  • 执行所有该处理器的拦截器的postHandle方法(按照配置逆序执行)
  • 执行派遣结果
  • 将ModelAndView中的Model中的存储到作用域中
  • 按照ModelAndView中的视图进行页面的渲染
  • 执行该处理器的拦截器的afterCompletion方法(按照配置逆序执行)
  • 最终响应给客户端。

处理器方法参数的解析原理

  • 通过HandleAdapter适配器来执行处理器的方法
  • 执行时会获取到所有的 参数解析器返回值解析器
  • 然后遍历所有的参数列表,遍历时会将该参数与所有的参数解析器进行匹配
  • 如果匹配上了,则使用该参数解析器 为参数进行赋值操作
  • 如果是自定义的参数,会使用到 *转换器

特殊参数:ModelMap、Model、Map等

  • 通过HandleAdapter适配器来执行处理器的方法
  • 执行时会获取到所有的 参数解析器返回值解析器
  • 遍历参数列表拿到ModelMap、Model、Map等参数时,会分别匹配到不同的参数解析器
  • 但是匹配到的这几个参数解析器,最终都会返回一个 BindModelMapContainer
  • 至于这3个参数存储到值为什么会保存到请求域中,是因为在进行最后 的视图解析时,会将这些值存储到请求域中

自定义一个返回值解析器

//1&#x3001;WebMvcConfigurer&#x5B9A;&#x5236;&#x5316;SpringMVC&#x7684;&#x529F;&#x80FD;
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // &#x4E0D;&#x79FB;&#x9664;&#xFF1B;&#x540E;&#x9762;&#x7684;&#x5185;&#x5BB9;&#x3002;&#x77E9;&#x9635;&#x53D8;&#x91CF;&#x529F;&#x80FD;&#x5C31;&#x53EF;&#x4EE5;&#x751F;&#x6548;
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

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

                    @Override
                    public Pet convert(String source) {
                        // &#x554A;&#x732B;,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;
                    }
                });
            }
        };
    }
</string,>

处理器带@ResponseBody的处理流程

  • 获取到该处理器的HandlerMapping
  • 通过HandlerMapping获取到相对应的HandlerAdapter
  • 通过HandlerAdapter调用处理器的方法进行处理
  • 获取到处理器方法的 参数解析器返回值处理器
  • 遍历处理器方法的参数列表获取到一个个参数,并都用参数解析器进行解析
  • 调用方法执行,执行后获取到方法的 返回值
  • 通过返回值去 匹配相对应的返回值处理器,如果是标注了@ResponseBody的方法,会由 RequestResponseBodyMethodProcessor
  • 匹配到了对应的返回值解析器后,会调用该返回值处理器的 handleReturnValue方法来完成返回值的处理
  • 通过 NegotiationManager协商管理器获取到浏览器所需要的MediaType,使用 ContentNegotiationStrategy内容协商策略来获取浏览器能够接收到Media类型,有 基于请求头的内容协商策略,还有基于 请求参数的(需要配置)。 List<mediatype> acceptableTypes</mediatype>
  • 再获取到全部已经配置好的消息转换器,判断是否能够将返回值类型进行转换(调用canWrite()方法),如果能够将返回值类型进行转换,则将该转换器支持的Media存储到一个集合中。 List<mediatype> producibleTypes</mediatype>
  • 根据浏览器能够接收到(有权重) List<mediatype> acceptableTypes</mediatype>集合与 List<mediatype> producibleTypes</mediatype>进行匹配,获取到最终能够达成一致的MediaType集合 List<mediatype> mediaTypesToUse</mediatype>,再对该集合一个排序 MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
  • 然后获取 List<mediatype> mediaTypesToUse</mediatype>中的第一个MediaType, selectedMediaType
  • 再次遍历所有的 消息转换器MessageConverter、判断转换器不仅要支持转换对应的返回值类型,还要能支持选择的MediaType
  • 拿到最后匹配的消息转换器后,调用转换器的write方法,将结果响应给浏览器。

视图解析器原理分析

  • DispatcherServlet接收到一个请求
  • 获取到相对应的HandlerMapping
  • 通过HandlerMapping获取到对应的HandlerAdapter
  • HandlerAdapter执行目标方法获取到一个ModelAndView
  • 进行请求结果派发processDispatchResult(1087行),而后执行render渲染方法
  • 在执行渲染方法时,根据内容协商管理器中的协商策略最终获取到能够处理该视图的解析器
  • 最后调用视图解析器的render方法完成对页面视图的解析,最终返回结果

JAVA入门基础_从零开始的培训_SpringBoot2入门
JAVA入门基础_从零开始的培训_SpringBoot2入门

处理器方法异常时的处理流程分析

  • DispatchService在执行处理器方法时,一旦抛出异常 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  • 那么方法本应该返回的ModelAndView就会为null
  • 捕获该异常后,将会把捕获到的异常赋值个另一个变量 Exception dispatchException
  • 接下来依然 执行派发结果,并且派发结果是会将该异常 传入
  • 派发结果的执行会判断传入的异常是否会null,如果不为null,则调用执行获取异常的方法 mv = processHandlerException(request, response, handler, exception);
  • 该方法中会循环当前所有的处理异常解析器,判断哪个异常解析器可以处理该异常
  • 如果有异常解析器可以处理该异常,则执行该异常处理器的resolveException方法,获取到一个ModelAndView,然后根据该ModelAndView进行处理
  • 如果没有异常解析器可以处理该异常,则最终将异常抛出
  • 然后根据Servlet机制会默认向服务器端转发一个/error的请求来进行处理。

JAVA入门基础_从零开始的培训_SpringBoot2入门

SpringBoot为我们自动配置的异常处理规则

  • SpringBoot通过自动配置为我们添加了一个异常处理映射器BasicErrorController</li> <li>可以通过错误代码返回到指定的页面,默认为静态资源路径或者模板路径</li> <li>/static/error/4xx.html 、/templates/error/5xx.html</li> </ul> <h4>方法抛出异常又没有异常解析器处理的处理机制</h4> <ul> <li>会默认重新向服务器发送一个/error请求</li> </ul> <h2>SpringBoot的常用功能</h2> <h3>引入原生的Servlet、Filter、Listener等</h3> <h4>引入原生的这些组件无法使用到spring的拦截器、编码过滤器等</h4> <h4>通过@ServletComponentScan的方式</h4> <ul> <li>(1)正常的编写一个Servlet</li> </ul> <pre><code>@WebServlet("/my") public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("你好呀"); } } </code></pre> <ul> <li>(2)在SpringBoot的启动类上加注解来扫描</li> </ul> <pre><code>@SpringBootApplication @ServletComponentScan("com.codestars.config") </code></pre> <h4>通过注册ServletRegistrationBean的方式</h4> <ul> <li>(1)写一个Servlet</li> </ul> <pre><code>public class MyServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().println("你好呀"); } } </code></pre> <ul> <li>(2)编写一个spring的配置类</li> </ul> <pre><code>@Configuration(proxyBeanMethods = true) public class MyConfig implements WebMvcConfigurer { @Bean public ServletRegistrationBean myServletRegistration() { Servlet servlet = new MyServlet(); return new ServletRegistrationBean<>(servlet, "/myhh"); } } </code></pre> <p>SpringBoot整合MyBatisPlus</p> <h2>MyBatisPlus简介</h2> <h3>简介</h3> <p>在MyBatis的基础上进行封装,在不影响原有MyBatis功能的情况下实现功能的增强与扩展。</p> <h3>特性</h3> <ul> <li>无侵入</li> <li>损耗小</li> <li>强大的CRUD操作,内置 <strong>通用Mapper</strong>, <strong>通用Service</strong>,通过少量配置完成大部分单表的CRUD操作,还有功能强大的 <strong>条件构造器</strong></li> <li>支持ActiveRecord模式,实体类只需要继承Model类即可进行强大的CRUD操作</li> <li>支持自定义全局通用操作</li> <li>内置代码生成器:2个依赖</li> <li>分页插件支持多种数据库</li> <li>内置性能分析插件</li> <li>内置全局拦截插件</li> </ul> <h3>框架结构</h3> <p><img alt="JAVA入门基础_从零开始的培训_SpringBoot2入门" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230605/2344320-20220903124910999-1146813474.png" /></p> <h3>代码及文档地址</h3> <ul> <li>官方地址: <a href="http://mp.baomidou.com">http://mp.baomidou.com</a></li> <li>代码发布地址:</li> <li>Github: <a href="https://github.com/baomidou/mybatis-plus">https://github.com/baomidou/mybatis-plus</a></li> <li>Gitee: <a href="https://gitee.com/baomidou/mybatis-plus">https://gitee.com/baomidou/mybatis-plus</a></li> <li>文档发布地址: <a href="https://baomidou.com/pages/24112f">https://baomidou.com/pages/24112f</a></li> </ul> <h2>配置MyBatisPlus的开发环境</h2> <h3>创建一张数据库表,添加点数据</h3> <pre><code> CREATE DATABASE mybatis_plus /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use mybatis_plus; CREATE TABLE ( bigint(20) NOT NULL COMMENT '主键ID', varchar(30) DEFAULT NULL COMMENT '姓名', int(11) DEFAULT NULL COMMENT '年龄', varchar(50) DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    INSERT INTO user (id, name, age, email) VALUES
    (1, ‘Jone’, 18, ‘test1@baomidou.com’),
    (2, ‘Jack’, 20, ‘test2@baomidou.com’),
    (3, ‘Tom’, 28, ‘test3@baomidou.com’),
    (4, ‘Sandy’, 21, ‘test4@baomidou.com’),
    (5, ‘Billie’, 24, ‘test5@baomidou.com’);

    创建一个SpringBoot项目

    修改pom文件引入需要的依赖

        <dependencies>
            <dependency>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-starter</artifactid>
            </dependency>
            <dependency>
                <groupid>org.projectlombok</groupid>
                <artifactid>lombok</artifactid>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupid>org.springframework.boot</groupid>
                <artifactid>spring-boot-starter-test</artifactid>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupid>com.baomidou</groupid>
                <artifactid>mybatis-plus-boot-starter</artifactid>
                <version>3.5.2</version>
            </dependency>
        </dependencies>
    

    修改application.yaml文件、开启别名、日志等

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
        username: root
        password: abc123
        type: com.zaxxer.hikari.HikariDataSource
    mybatis-plus&#x914D;&#x7F6E;&#x6587;&#x4EF6;
    #mybatis-plus:
     # &#x8FD9;&#x4E2A;&#x5C31;&#x662F;&#x9ED8;&#x8BA4;&#x503C;&#xFF0C;&#x626B;&#x63CF;xml&#x6587;&#x4EF6;&#x6240;&#x5728;&#x7684;&#x4F4D;&#x7F6E;
     mapper-locations: classpath*:/mapper/**/*.xml
     global-config:
       db-config:
         # &#x8BBE;&#x7F6E;&#x8868;&#x7684;&#x524D;&#x7F00;
         table-prefix: t_
     # &#x914D;&#x7F6E;&#x679A;&#x4E3E;&#x626B;&#x63CF;&#xFF0C;3.5&#x4EE5;&#x540E;&#x5DF2;&#x5F03;&#x7528;
     type-enums-package:
    mybatis-plus:
      type-aliases-package: com.codestars.pojo
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

    修改启动类,添加Mapper接口扫描

    @SpringBootApplication
    // &#x5982;&#x679C;&#x8FDB;&#x884C;&#x8BE5;&#x914D;&#x7F6E;&#xFF0C;&#x5219;&#x9700;&#x8981;&#x5728;&#x6BCF;&#x4E2A;Mapper&#x4E0A;&#x6DFB;&#x52A0;@Mapper&#x6CE8;&#x89E3;
    @MapperScan("com.codestars.mapper")
    public class StudyMybatisPlusApplication {
    

    添加一个Mapper接口

    public interface UserMapper extends BaseMapper<user> {
        /**
         * &#x6839;&#x636E;id&#x67E5;&#x8BE2;&#x7528;&#x6237;
         * @return
         */
        User selectUserById();
    }
    </user>
    

    添加一个Service和ServiceImpl(Service中调用的依然是BaseMapper中的方法)

    public interface UserService extends IService<user> {
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<usermapper, user> implements UserService {
    }
    </usermapper,></user>
    

    在resources下添加一个mapper文件夹(选做)

    • mybatisplus默认将类路径下的mapper文件夹作为Mapper映射文件
    <mapper namespace="com.codestars.mapper.UserMapper">
    
        <select id="selectUserById" resulttype="User">
            select * from user where id = #{id}
        </select>
    </mapper>
    

    JAVA入门基础_从零开始的培训_SpringBoot2入门

    测试简单的CRUD

    IService中的新增方法

    JAVA入门基础_从零开始的培训_SpringBoot2入门
    @SpringBootTest
    class StudyMybatisPlusApplicationTests {
        @Autowired
        private UserMapper userMapper;
    
        @Autowired
        private UserService userService;
    
        @Test
        void testBaseMapperXml() {
            // 1&#x3001;&#x6D4B;&#x8BD5;&#x4F7F;&#x7528;Mapper.xml&#x6620;&#x5C04;&#x7684;&#x65B9;&#x5F0F;&#x8FD8;&#x662F;&#x5426;&#x53EF;&#x7528;
            User user = userMapper.selectUserById(1);
            System.out.println(user);
        }
    
        /**
         * &#x6D4B;&#x8BD5;&#x63D2;&#x5165;&#x65B9;&#x6CD5;
         */
        @Test
        void testInsert(){
            // &#x63D2;&#x5165;&#x5355;&#x6761;&#x8BB0;&#x5F55;
            User user = new User();
            user.setName("&#x5F20;&#x4E09;");
            user.setEmail("1@qq.com");
            user.setAge(55);
            boolean save = userService.save(user);
            System.out.println("&#x662F;&#x5426;&#x65B0;&#x589E;&#x6210;&#x529F;&#xFF1A;" + save);
    
            // &#x63D2;&#x5165;&#x6279;&#x91CF;&#x6570;&#x636E;
            List<user> userList = Arrays.asList(
                    new User(null,"&#x674E;&#x56DB;1",11,"2@qq.com"),
                    new User(null,"&#x674E;&#x56DB;2",11,"2@qq.com"),
                    new User(null,"&#x674E;&#x56DB;3",11,"2@qq.com")
            );
            userService.saveBatch(userList);
        }
    }
    </user>
    
    测试IService中的删除方法

    JAVA入门基础_从零开始的培训_SpringBoot2入门
        /**
         * &#x6D4B;&#x8BD5;&#x5220;&#x9664;&#x65B9;&#x6CD5;
         */
        @Test
        void testDelete(){
            // &#x6839;&#x636E;id&#x5220;&#x9664;&#x8BB0;&#x5F55;
            boolean b = userService.removeById(1);
            System.out.println("id&#x4E3A;1&#x7684;&#x7528;&#x6237;&#x662F;&#x5426;&#x5220;&#x9664;&#xFF1A; " + b);
    
            // &#x6839;&#x636E;id&#x6279;&#x91CF;&#x5220;&#x9664;
            boolean b1 = userService.removeByIds(Arrays.asList(1, 2, 3));
            System.out.println("&#x6279;&#x91CF;&#x5220;&#x9664;&#x662F;&#x5426;&#x6210;&#x529F;&#xFF1A;" + b1);
        }
    
    测试IService中的更新方法

    JAVA入门基础_从零开始的培训_SpringBoot2入门
        /**
         * &#x6D4B;&#x8BD5;&#x5220;&#x9664;&#x65B9;&#x6CD5;
         */
        @Test
        void testUpdate(){
            // &#x6839;&#x636E;&#x5B9E;&#x4F53;&#x7C7B;&#x4E2D;&#x7684;id&#x8FDB;&#x884C;&#x4FEE;&#x6539;
            User user = new User(1L, "&#x738B;&#x516D;",111,"444@qq.com");
            userService.updateById(user);
        }
    
    测试IService中的查询方法

    JAVA入门基础_从零开始的培训_SpringBoot2入门
    JAVA入门基础_从零开始的培训_SpringBoot2入门
        /**
         * &#x6D4B;&#x8BD5;&#x5220;&#x9664;&#x65B9;&#x6CD5;
         */
        @Test
        void testSelect(){
            // 1&#x3001;&#x83B7;&#x53D6;&#x8868;&#x4E2D;&#x6240;&#x6709;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x8F6C;&#x6362;&#x4E3A;List&#x96C6;&#x5408;
            List<user> list = userService.list();
            System.out.println(list);
    
            // 2&#x3001;&#x83B7;&#x53D6;&#x8868;&#x4E2D;&#x6240;&#x6709;&#x7684;&#x6570;&#x636E;&#xFF0C;&#x8F6C;&#x6362;&#x4E3A;Map&#x96C6;&#x5408;
            List<map<string, object>> maps = userService.listMaps();
            System.out.println(maps);
    
            // 3&#x3001;&#x83B7;&#x53D6;&#x6240;&#x6709;&#x7684;&#x8BB0;&#x5F55;id
            List<object> objects = userService.listObjs();
            System.out.println("----------" + objects);
    
            // 4&#x3001;&#x901A;&#x8FC7;id&#x67E5;&#x8BE2;&#x8BB0;&#x5F55;
            User byId = userService.getById(4);
            System.out.println(byId);
        }
    </object></map<string,></user>
    

    对于实体类的常用注解

    @TableName

    当实体类的名称 无法与数据表的名称对应时,需要使用该注解指定数据表的名称。

    @TableName("t_user")
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    

    @TableId(主键字段及主键策略)

    • mybaitsplus 默认会将数据库中的id字段作为主键
    • 如果数据库中的字段名和实体类中的属性名不为id,则mybatisplus无法确认id的字段,通过id获取记录等方法将会直接报错
      org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
    • 这个时候可以通过@TableId来指定数据库主键的列
        // type = IdType.AUTO(&#x81EA;&#x52A8;&#x9012;&#x589E;&#xFF0C;&#x6570;&#x636E;&#x5E93;&#x4E2D;&#x7684;&#x4E3B;&#x952E;&#x5FC5;&#x987B;&#x662F;&#x81EA;&#x589E;&#x7684;)&#xFF0C;IdType.ASSIGN_ID&#x96EA;&#x82B1;&#x7B97;&#x6CD5;&#xFF08;&#x9ED8;&#x8BA4;&#xFF09;
        @TableId(value = "uid",type = IdType.ASSIGN_ID)
    

    @TableFiled

    • 同样可以指定与数据库中匹配的字段
    • 可以指定是否为一个数据库字段
        // exist=true&#x4EE3;&#x8868;&#x4E3A;&#x4E00;&#x4E2A;&#x6570;&#x636E;&#x5E93;&#x5B57;&#x6BB5;&#xFF0C;&#x5426;&#x5219;&#x4E0D;&#x4E3A;&#x4E00;&#x4E2A;&#x6570;&#x636E;&#x5E93;&#x5B57;&#x6BB5;
        @TableField(exist = true)
    

    @TableLogic

    • 当数据库中的记录不需要被真正的删除,只是进行逻辑删除时,可以给逻辑删除的字段添加该注解。
    • 0 为没有被逻辑删除的记录、1为被逻辑删除的字段。接下来进行的数据增删改查时,都会 默认添加上一个where条件,该字段等于0
    • 将数据库中添加一个int类型的is-deleted字段
    • 为实体类添加上@TableLogic注解
        @TableLogic
        private Integer isDeleted;
    
    • 接下来一旦执行删除操作, *只是将数据表中的is-deleted字段修改为1

    @Version

    • 当需要使用到乐观锁时,需要用到该字段,作为版本控制
    • 添加数据表的字段,添加一个int类型的version,默认值可以为0
    • 为实体类中添加一个字段与其对应并添加@Version注解
    • 测试代码
        @Test
        public void testVersionLock() {
            // id&#x4E3A;3&#x7684;&#x7528;&#x6237;&#x5DE5;&#x8D44;&#x9ED8;&#x8BA4;&#x4E3A;3000&#x5143;&#xFF0C; &#x8001;&#x603B;&#x8BA9;&#x7532;&#x4E3A;&#x5176;&#x589E;&#x52A0;1000&#xFF0C;&#x53C8;&#x8BA9;&#x4E59;&#x4E3A;&#x5176;&#x51CF;&#x5C11;500
            // &#x7532;&#x548C;&#x4E59;&#x540C;&#x65F6;&#x83B7;&#x53D6;&#x5230;&#x4E86;id&#x4E3A;3&#x7684;&#x7528;&#x6237;&#x4FE1;&#x606F;
            User zhangsan = userService.getById(3);
            User lisi = userService.getById(3);
    
            // &#x8001;&#x603B;&#x8BA9;&#x7532;&#x4FEE;&#x6539;&#x4E86;id&#x4E3A;3&#x7684;&#x7528;&#x6237;&#x7684;&#x5DE5;&#x8D44;&#xFF0C;&#x8BA9;&#x5176;&#x5DE5;&#x8D44;&#x589E;&#x52A0;1000&#x5143;
            zhangsan.setMoney(zhangsan.getMoney() + 1000);
            userService.updateById(zhangsan);
    
            // &#x8001;&#x603B;&#x89C9;&#x5F97;&#x7ED9;&#x7684;&#x592A;&#x591A;&#x4E86;&#xFF0C;&#x8BA9;&#x4E59;&#x5C06;id&#x4E3A;3&#x7684;&#x7528;&#x6237;&#x5DE5;&#x8D44;&#x51CF;&#x5C11;500&#x5143;&#x3002;
            lisi.setMoney(lisi.getMoney() - 500);
            boolean flag = userService.updateById(lisi);
            if (!flag) {
                // &#x5982;&#x679C;&#x5931;&#x8D25;&#x8BF4;&#x660E;&#x6CA1;&#x4FEE;&#x6539;&#x6210;&#x529F;&#xFF0C;&#x91CD;&#x65B0;&#x4FEE;&#x6539;&#xFF0C;&#x5176;&#x5B9E;&#x53EF;&#x4EE5;&#x53EF;&#x4EE5;&#x6539;&#x6210;while
                lisi = userService.getById(3);
                lisi.setMoney(lisi.getMoney() - 500);
                userService.updateById(lisi);
            }
    
            // &#x603B;&#x88C1;&#x73B0;&#x5728;&#x83B7;&#x53D6;&#x5458;&#x5DE5;id&#x4E3A;3&#x7684;&#x5DE5;&#x8D44;&#x65F6;
            User byId = userService.getById(3);
            System.out.println(byId);
        }
    

    开启乐观锁功能和分页功能

    @Configuration
    public class MyConfig {
    
        @Bean
        public MybatisPlusInterceptor myMybatisPlusInterceptor() {
            MybatisPlusInterceptor myMybatisPlusInterceptor = new MybatisPlusInterceptor();
            // &#x6DFB;&#x52A0;&#x5206;&#x9875;&#x62E6;&#x622A;&#x5668;
            myMybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            // &#x6DFB;&#x52A0;&#x4E50;&#x89C2;&#x9501;&#x673A;&#x5236;
            myMybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    
            return myMybatisPlusInterceptor;
        }
    }
    

    条件构造器

    条件构造器的体系结构

    JAVA入门基础_从零开始的培训_SpringBoot2入门

    QueryWrapper

    and条件拼接(默认)

        @Test
        public void testQueryWrapper() {
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x666E;&#x901A;and&#x6761;&#x4EF6;&#x62FC;&#x63A5;&#xFF08;&#x9ED8;&#x8BA4;&#xFF09;&#xFF0C;&#x83B7;&#x53D6;name=&#x5F20;&#x4E09;&#xFF0C;age&#x5927;&#x4E8E;10&#x7684;&#x7528;&#x6237;
            queryWrapper.eq("name", "&#x5F20;&#x4E09;")
                    .gt("age", 10);
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    组装排序条件

        @Test
        public void testQueryWrapper() {
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x6309;&#x7167;&#x5E74;&#x9F84;&#x964D;&#x5E8F;&#x6392;&#x5E8F;&#xFF0C;&#x518D;&#x6309;&#x7167;uid&#x5347;&#x5E8F;&#x6392;&#x5E8F;
            queryWrapper.orderByDesc("name")
                    .orderByAsc("uid");
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    条件的优先级(and或or中写lambda表达式)

        @Test
        public void testQueryWrapper() {
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x666E;&#x901A;and&#x6761;&#x4EF6;&#x62FC;&#x63A5;&#xFF08;&#x9ED8;&#x8BA4;&#xFF09;&#xFF0C;&#x83B7;&#x53D6;&#xFF08;&#x5E74;&#x9F84;&#x5927;&#x4E8E;20&#x5E76;&#x4E14;&#x7528;&#x6237;&#x540D;&#x4E2D;&#x5305;&#x542B;&#x6709;a&#xFF09;&#x6216;&#x90AE;&#x7BB1;&#x4E3A;null&#x7684;&#x7528;&#x6237;&#x4FE1;&#x606F;
            queryWrapper.and(query -> query.gt("age", 20)
                                           .like("name", "a"))
                        .or()
                        .isNull("email");
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    组装select子句(查询指定字段)

        @Test
        public void testQueryWrapperSelect() {
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x6307;&#x5B9A;&#x9700;&#x8981;&#x67E5;&#x8BE2;&#x7684;&#x5B57;&#x6BB5;
            queryWrapper.select("name", "age","email");
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    子查询的运用(了解)

        @Test
        public void testQueryWrapperSelect() {
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x67E5;&#x8BE2;uid&#x5C0F;&#x4E8E;3&#x7684;&#x7528;&#x6237;&#x4FE1;&#x606F;
            queryWrapper.inSql("uid", "select uid from t_user where uid = 3");
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    实现条件匹配,例如xml文件中的if等

        @Test
        public void testQueryWrapperIf() {
            String uid = null;
            String name = "&#x5F20;&#x4E09;";
            Integer age = 10;
    
            QueryWrapper<user> queryWrapper = new QueryWrapper();
            // 1&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x5224;&#x65AD;&#x662F;&#x5426;&#x8FDB;&#x884C;sql&#x62FC;&#x63A5;
            queryWrapper.eq(StringUtils.isNotBlank(uid),"uid", uid)
                        .like(StringUtils.isNotBlank(name),"name", name)
                        .eq(age != null, "age", age);
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    LambdaQueryWrapper(防止数据库字段写错)

        @Test
        public void testQueryWrapperLambda() {
    
            LambdaQueryWrapper<user> queryWrapper = new LambdaQueryWrapper();
            // 1&#x3001;&#x83B7;&#x53D6;name&#x4E3A;&#x5F20;&#x4E09;&#x5E76;&#x4E14;&#x5E74;&#x9F84;&#x7B49;&#x4E8E;20&#x7684;&#x7528;&#x6237;&#x4FE1;&#x606F;
            queryWrapper.eq(User::getName, "&#x5F20;&#x4E09;")
                        .gt(User::getAge, 20);
    
            // 2&#x3001;&#x6839;&#x636E;&#x6761;&#x4EF6;&#x6784;&#x9020;&#x5668;&#x67E5;&#x8BE2;&#x6240;&#x6709;&#x6570;&#x636E;
            List list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    </user>
    

    UpdateQueryWrapper

        @Test
        // @Transactional
        public void testUpdateWrapper() {
    
            UpdateWrapper<user> updateWrapper = new UpdateWrapper();
            // &#x5C06;uid = 1&#x7684;&#x7528;&#x6237;&#x7684;&#x59D3;&#x540D;&#x4FEE;&#x6539;&#x4E3A;&#x201C;&#x674E;&#x56DB;&#x201C;
            updateWrapper.set("name","&#x674E;&#x56DB;")
                         .eq("uid",1);
    
            userService.update(updateWrapper);
        }
    </user>
    

    LambdaQueryWrapper

        @Test
        // @Transactional
        public void testUpdateWrapper() {
    
            LambdaUpdateWrapper<user> updateWrapper = new LambdaUpdateWrapper();
            // &#x5C06;uid = 1&#x7684;&#x7528;&#x6237;&#x7684;&#x59D3;&#x540D;&#x4FEE;&#x6539;&#x4E3A;&#x201C;&#x674E;&#x56DB;&#x201C;
            updateWrapper.set(User::getName,"&#x674E;&#x56DB;")
                         .eq(User::getUid,1);
    
            userService.update(updateWrapper);
        }
    </user>
    

    分页插件

    配置分页插件拦截器

    @Configuration
    public class MyConfig {
    
        @Bean
        public MybatisPlusInterceptor myMybatisPlusInterceptor() {
            MybatisPlusInterceptor myMybatisPlusInterceptor = new MybatisPlusInterceptor();
            // &#x6DFB;&#x52A0;&#x5206;&#x9875;&#x62E6;&#x622A;&#x5668;
            myMybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            // &#x6DFB;&#x52A0;&#x4E50;&#x89C2;&#x9501;&#x673A;&#x5236;
            myMybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    
            return myMybatisPlusInterceptor;
        }
    }
    

    使用Service中已经提供的分页方法

        @Test
        public void testPage() {
            // &#x7B2C;&#x4E00;&#x9875;&#x3001;&#x4E00;&#x9875;2&#x6761;&#x8BB0;&#x5F55;
            Page<user> page = new Page<user>(1,2);
            userService.page(page);
    
            System.out.println("&#x6570;&#x636E;&#x7684;List&#x96C6;&#x5408;&#xFF1A;" + page.getRecords());
            System.out.println("&#x603B;&#x9875;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getPages());
            System.out.println("&#x603B;&#x8BB0;&#x5F55;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getTotal());
            System.out.println("&#x5F53;&#x524D;&#x9875;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getCurrent());
            System.out.println("&#x662F;&#x5426;&#x6709;&#x4E0A;&#x4E00;&#x9875;&#xFF1A;boolean&#x7C7B;&#x578B;&#xFF1A;" + page.hasPrevious());
            System.out.println("&#x662F;&#x5426;&#x6709;&#x4E0B;&#x4E00;&#x9875;&#xFF1A;boolean&#x7C7B;&#x578B;&#xFF1A;" + page.hasNext());
        }
    </user></user>
    

    通过自己编写的Mapper.xml的方式

    • 编写Mapper接口
        /**
         * &#x6839;&#x636E;&#x5173;&#x952E;&#x5B57;&#x5206;&#x9875;
         * @param page
         * @param keyword
         * @return
         */
        List<user> selectMyPage(@Param("page") Page<user> page, @Param("keyword") String keyword);
    </user></user>
    
    • 编写XML文件
        <select id="selectMyPage" resulttype="com.codestars.pojo.User">
            select * from t_user
            <where>
                <if test="keyword != null and keyword != ''">
                    name like ""#{keyword}""
                </if>
            </where>
        </select>
    
    • 测试类
        @Test
        void testMapperPage() {
            Page<user> page = new Page<>(1, 3);
            List<user> userList = userMapper.selectMyPage(page, null);
            // &#x65E0;&#x6CD5;&#x81EA;&#x52A8;&#x5C06;&#x6570;&#x636E;&#x5C01;&#x88C5;&#x5230;page&#x5F53;&#x4E2D;
            page.setRecords(userList);
    
            System.out.println("&#x6570;&#x636E;&#x7684;List&#x96C6;&#x5408;&#xFF1A;" + page.getRecords());
            System.out.println("&#x603B;&#x9875;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getPages());
            System.out.println("&#x603B;&#x8BB0;&#x5F55;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getTotal());
            System.out.println("&#x5F53;&#x524D;&#x9875;&#x6570;&#xFF1A;long&#x7C7B;&#x578B;&#xFF1A;" + page.getCurrent());
            System.out.println("&#x662F;&#x5426;&#x6709;&#x4E0A;&#x4E00;&#x9875;&#xFF1A;boolean&#x7C7B;&#x578B;&#xFF1A;" + page.hasPrevious());
            System.out.println("&#x662F;&#x5426;&#x6709;&#x4E0B;&#x4E00;&#x9875;&#xFF1A;boolean&#x7C7B;&#x578B;&#xFF1A;" + page.hasNext());
        }
    </user></user>
    

    通用枚举

    • 如果实体类中的某个变量是枚举,例如性别属性
    • 那么可以在定义的枚举中,把映射到数据库字段的值添加@EnumValue注解
    public enum GenderEnum {
        MAN(0,"&#x7537;"),
        WOMAN(1,"&#x5973;");
    
        @EnumValue
        private Integer num;
    
        private String gender;
    
        GenderEnum(Integer num, String gender) {
            this.num = num;
            this.gender = gender;
        }
    }
    
    • 实体类的gender变量
        private GenderEnum gender;
    
    • 扫描通用枚举(mybatisplus3.5之后不需要)
    &#x914D;&#x7F6E;&#x626B;&#x63CF;&#x901A;&#x7528;&#x679A;&#x4E3E;
    type-enums-package: com.codestars.config
    

    多数据源

    加入动态数据源场景启动器

            <dependency>
                <groupid>com.baomidou</groupid>
                <artifactid>dynamic-datasource-spring-boot-starter</artifactid>
                <version>3.5.0</version>
            </dependency>
    

    在application.yml中配置多个数据源

    spring:
      datasource:
        dynamic:
          primary: master
          strict: false # &#x662F;&#x5426;&#x4E3A;&#x4E25;&#x683C;&#x6A21;&#x5F0F;&#xFF0C;&#x5982;&#x679C;&#x662F;&#x4E25;&#x683C;&#x6A21;&#x5F0F;&#xFF0C;&#x672A;&#x5339;&#x914D;&#x5230;&#x6570;&#x636E;&#x6E90;&#x76F4;&#x63A5;&#x62A5;&#x9519;
          datasource:
            master:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
              username: root
              password: abc123
              type: com.zaxxer.hikari.HikariDataSource
            slave_1:
              driver-class-name: com.mysql.cj.jdbc.Driver
              url: jdbc:mysql://localhost:3306/mybatis_plus_2?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
              username: root
              password: abc123
              type: com.zaxxer.hikari.HikariDataSource
    

    在用于访问数据库的Mapper或者ServiceImpl上添加@DS注解指定需要访问的数据源

    @DS("slave_1")
    public interface AccountMapper extends BaseMapper<account> {
    }
    
    @Service
    @DS("master")
    public class UserServiceImpl extends ServiceImpl<usermapper, user> implements UserService {
    }
    
    </usermapper,></account>
    

    MyBatisPlus代码生成器

    • 添加依赖
            <dependency>
                <groupid>com.baomidou</groupid>
                <artifactid>mybatis-plus-generator</artifactid>
                <version>3.5.1</version>
            </dependency>
            <dependency>
                <groupid>org.freemarker</groupid>
                <artifactid>freemarker</artifactid>
                <version>2.3.31</version>
            </dependency>
    
    • 执行如下代码
    package com.codestars.study_mybatis_plus;
    import com.baomidou.mybatisplus.generator.FastAutoGenerator;
    import com.baomidou.mybatisplus.generator.config.OutputFile;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    
    import java.util.Collections;
    
    /**
     * @author codeStars
     * @date 2022/9/3 15:53
     */
    public class TestCode {
        public static void main(String[] args) {
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "abc123")
                            .globalConfig(builder -> {
                                builder.author("codestars") // &#x8BBE;&#x7F6E;&#x4F5C;&#x8005;
                                        // .enableSwagger() // &#x5F00;&#x542F; swagger &#x6A21;&#x5F0F;
                                        .fileOverride() // &#x8986;&#x76D6;&#x5DF2;&#x751F;&#x6210;&#x6587;&#x4EF6;
                                        .outputDir("D://mybatis_plus"); // &#x6307;&#x5B9A;&#x8F93;&#x51FA;&#x76EE;&#x5F55;
                            })
                            .packageConfig(builder -> {
                                builder.parent("com.codestars") // &#x8BBE;&#x7F6E;&#x7236;&#x5305;&#x540D;
                                        .moduleName("mybatisplus") // &#x8BBE;&#x7F6E;&#x7236;&#x5305;&#x6A21;&#x5757;&#x540D;
                                                                    // &#x8BBE;&#x7F6E;mapperXml&#x751F;&#x6210;&#x8DEF;&#x5F84;
                                        .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus"));
    
                            })
                            .strategyConfig(builder -> {
                                builder.addInclude("t_user") // &#x8BBE;&#x7F6E;&#x9700;&#x8981;&#x751F;&#x6210;&#x7684;&#x8868;&#x540D;
                                        .addTablePrefix("t_", "c_"); // &#x8BBE;&#x7F6E;&#x8FC7;&#x6EE4;&#x8868;&#x524D;&#x7F00;
                            })
                            .templateEngine(new FreemarkerTemplateEngine()) // &#x4F7F;&#x7528;Freemarker&#x5F15;&#x64CE;&#x6A21;&#x677F;&#xFF0C;&#x9ED8;&#x8BA4;&#x7684;&#x662F;Velocity&#x5F15;&#x64CE;&#x6A21;&#x677F;
                            .execute();
        }
    }
    

    SpringBoot整合Knife4j

    引入依赖

        <properties>
            <knife4j.version>2.0.9</knife4j.version>
        </properties>
    
        <dependency>
            <groupid>com.github.xiaoy   min</groupid>
            <artifactid>knife4j-spring-boot-starter</artifactid>
            <version>${knife4j.version}</version>
        </dependency>
    

    添加配置类

    @Configuration
    @EnableSwagger2WebMvc
    public class Knife4jConfiguration {
    
        @Bean(value = "dockerBean")
        public Docket dockerBean() {
            //&#x6307;&#x5B9A;&#x4F7F;&#x7528;Swagger2&#x89C4;&#x8303;
            Docket docket=new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(new ApiInfoBuilder()
                    //&#x63CF;&#x8FF0;&#x5B57;&#x6BB5;&#x652F;&#x6301;Markdown&#x8BED;&#x6CD5;
                    .description("# Knife4j RESTful APIs")
                    // .termsOfServiceUrl("https://doc.xiaominfo.com/")
                    .contact("xiaoymin@foxmail.com")
                    .version("1.0")
                    .build())
                    //&#x5206;&#x7EC4;&#x540D;&#x79F0;
                    .groupName("&#x4E00;&#x4E2A;&#x5C0F;&#x9879;&#x76EE;")
                    .select()
                    //&#x8FD9;&#x91CC;&#x6307;&#x5B9A;Controller&#x626B;&#x63CF;&#x5305;&#x8DEF;&#x5F84;
                    .apis(RequestHandlerSelectors.basePackage("com.woniu.controller"))
                    .paths(PathSelectors.any())
                    .build();
            return docket;
        }
    }
    

    项目访问地址

    http://localhost:8080/myContext/doc.html

    • 别忘了修改成自己的端口号,以及上下文
    • 之所以能访问,是以为该启动器为我们配置了一个Filter
      JAVA入门基础_从零开始的培训_SpringBoot2入门

    Original: https://www.cnblogs.com/itdqx/p/16645470.html
    Author: code_Stars
    Title: JAVA入门基础_从零开始的培训_SpringBoot2入门

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/594014/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • JVM垃圾回收器

    上篇我们知道垃圾回收机制,接下来,我们具体到垃圾回收器,看看JVM到底有哪些垃圾回收器。 一.GC性能指标 不可能三角 吞吐量:运行用户代码的时间占总运行时间的比例 暂停时间:进行…

    Java 2023年6月7日
    070
  • 安装Linux8.3.2011

    镜像地址:http://mirrors.aliyun.com/centos/8.3.2011/isos/x86_64/ 非DVD镜像安装时的安装源地址:http://mirrors…

    Java 2023年6月6日
    0107
  • Spring 源码(2)Spring IOC 容器 前戏准备工作

    Spring 最重要的方法refresh方法 根据上一篇文章 https://www.cnblogs.com/redwinter/p/16141285.html Spring Be…

    Java 2023年6月14日
    080
  • 通过过滤器实现前后端分离的跨域问题

    跨域指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。在做前后端分离项目的时候就需要解决此问题。 创建过滤器解决跨域问…

    Java 2023年6月14日
    083
  • 【Java中的线程】java.lang.Thread 类分析

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月9日
    093
  • SpringBoot异步任务获取HttpServletRequest

    在使用框架日常开发中需要在controller中进行一些异步操作减少请求时间,但是发现在使用@Anysc注解后会出现Request对象无法获取的情况,本文就此情况给出完整的解决方案…

    Java 2023年6月15日
    073
  • 解析协同办公“协同”为何意,数字化办公又如何轻松“破题”?

    “协同办公”这个词在我们的工作场上出现的几率非常高,但如果要问协同办公协同的到底是什么,是怎样协同的,相信很多人是回答不出来的。 偶然间想到这个问题,于是便…

    Java 2023年6月5日
    094
  • spring bean依赖注入

    实例化bean对象之后,即在applyMergedBeanDefinitionPostProcessors方法中调用MergedBeanDefinitionPostProcesso…

    Java 2023年6月9日
    077
  • Java_day01

    – 简单性- 面向对象- 可移植性- 高性能- 分布式- 动态性- 多线程- 安全性- 健壮性 Java成功的原因 个人认为在于语言本身的优势之外还有个人电脑的普及、互联网的发展等…

    Java 2023年6月5日
    092
  • SQL表的创建

    1.使用鼠标创建表 1,进入SQL进行连接 2,在左边会有一个对象资源管理器,右键数据库,在弹出的窗口中选择新建数据库 3,给这个包取个名字,在这个界面可以给这个表选择存储地方,如…

    Java 2023年6月8日
    078
  • 线程池如何观测?这个方案让你对线程池的运行情况了如指掌!

    今天我们来聊一个比较实用的话题,动态可监控可观测的线程池实践。 这是个全新的开源项目,作者提供了一种非常好的思路解决了线程池的可观测问题。 这个开源项目叫: DynamicTp 地…

    Java 2023年6月8日
    089
  • 第三方API对接如何设计接口认证?

    一、前言 在与第三方系统做接口对接时,往往需要考虑接口的安全性问题,本文主要分享几个常见的系统之间做接口对接时的认证方案。 二、认证方案 例如订单下单后通过 延时任务 对接 物流系…

    Java 2023年6月6日
    085
  • JAVA 8与JAVA 11到底该怎么选?

    很多初学Java的小伙伴经常咨询: 到底该安装哪个版本的JDK比较好? Java 8到底还够不够用? Java 11究竟有什么改进? 是不是Java版本越新越好? …&…

    Java 2023年5月29日
    0103
  • Java面向对象(六)

    Java面向对象(六) Java面向对象(六) – 十九、包装类 19.1 八种基本类型包装类 19.2 基本类型、包装类与 String 类间的转换。 19.3 基本…

    Java 2023年6月9日
    077
  • LRU least recently used 与LinkedHashMap

    LRU ,最近最少使用淘汰算法,用于存储 限量limit的数据,不超过 limit的数据将直接存储,若超过limit,则将”最老的数据” 淘汰掉。使用Lin…

    Java 2023年6月9日
    079
  • 消息队列MQ核心原理全面总结(11大必会原理)

    消息队列已经逐渐成为分布式应用场景、内部通信、以及秒杀等高并发业务场景的核心手段,它具有低耦合、可靠投递、广播、流量控制、最终一致性 等一系列功能。 无论是 RabbitMQ、Ro…

    Java 2023年6月15日
    092
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球