SpringBoot 入门到放弃

492 阅读12分钟

SpringBoot

SpringBoot 案例实现

  • 使用Spring Initializr方式构建Spring Boot项目

    image-20210113223647514.png

    image-20210113223859214.png

    image-20210113224150281.png

    image-20210113224248215.png

  • 项目结构

    此时springboot会自动创建一个项目的文件结构如下

    image-20210113224901766.png

  • 创建一个用于Web访问的Controller

    image-20210113225217716.png

  • 运行项目

    image-20210113225518153.png

  • 测试类编写

    image-20210113230513433.png

spring boot热部署

1.添加spring-boot-starter-test测试依赖启动器 在项目的pom.xml文件中添加spring-boot-starter-test测试依赖启动器,示例代码如下 :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

2 设置idea

先开启自动编译

image-20210113230950901.png

在项目任意页面中使用组合快捷键“Ctrl+Shift+Alt+/”打开Maintenance选项框,选中并打开Registry页面,具体如图

列表中找到“compiler.automake.allow.when.app.running”,将该选项后的Value值勾选,用于指定IDEA工具在程序运行过程中自动编译,最后单击【Close】按钮完成设置

image-20210113231708327.png

image-20210113233312985.png

配置文件属性值的注入

1 使用@ConfigurationProperties注入属性

Spring Boot提供的@ConfigurationProperties注解用来快速、方便地将配置文件中的自定义属性值批量注入到某个Bean对象的多个对应属性中。假设现在有一个配置文件,如果使用

@Component
@ConfigurationProperties(prefix = "person") // 配置文件提示需要
public class Person {
    private int id; //id
    // get/set方法
}

2 使用@Value注入属性

@Value注解是Spring框架提供的,用来读取配置文件中的属性值并逐个注入到Bean对象的对应属性中,Spring Boot框架从Spring框架中对@Value注解进行了默认继承,所以在Spring Boot框架中还可以使用该注解读取和注入配置文件属性值。

@Component
public class Student {
    @Value("${person.id}")
    private int id;
    @Value("${person.name}")
    private String name; //名称
    
    // toString方法
}

自定义配置

(1)打开Spring Boot项目的resources目录,在项目的类路径下新建一个test.properties自定义配置文件,在该配置文件中编写需要设置的配置属性

#对实体类对象MyProperties进行属性配置
test.id=110
test.name=test

(2)创建一个配置类MyProperties,提供test.properties自定义配置文件中对应的属性,并根据@PropertySource注解的使用进行相关配置

@Configuration // 自定义配置类
@PropertySource("classpath:test.properties") // 指定自定义配置文件位置和名称
@EnableConfigurationProperties(MyProperties.class) // 开启对应配置类的属性注入功能
@ConfigurationProperties(prefix = "test") // 指定配置文件注入属性前缀
public class MyProperties {
    private int id;
    private String name;
    // 省略属性getXX()和setXX()方法
// 省略toString()方法

}

使用@Configuration编写自定义配置类

通常使用@Configuration注解定义一个配置类,Spring Boot会自动扫描和识别配置类,从而替换传统Spring框架中的XML配置文件。

  • 定义配置类

    public class MyService {
    }
    
    @Configuration
    public class MyConfig {
    
        @Bean(name = "iService") // 将返回值对象作为组件添加到容器中、表示id默认是方法名--myService 也可以指定name=""
        public MyService myService(){
            return new MyService();
        }
    }
    
  • 测试类

    @Autowired
    ApplicationContext context;
    @Test
    public void testmyConfig(){
        System.out.println(context.containsBean("iService"));
    }
    

随机数设置及参数间引用

在Spring Boot配置文件中设置属性时,除了可以像前面示例中显示的配置属性值外,还可以使用随机值和参数间引用对属性值进行设置。下面,针对配置文件中这两种属性值的设置方式进行讲解。

1随机值设置

my.secret=${random.value} // 配置随机值
my.number=${random.int} // 配置随机整数
my.bignumber=${random.long} // 配置随机long类型数
my.uuid=${random.uuid} // 配置随机uuid类型数
my.number.less.than.ten=${random.int(10)} // 配置小于10的随机整数
my.number.in.range=${random.int[1024,65536]} // 配置范围在[1024,65536]之间的随机整数

2参数间引用

app.name=MyApp
app.description=${app.name} is a Spring Boot application

上述参数间引用设置示例中,先设置了“app.name=MyApp”,将app.name属性的属性值设置为了MyApp;接着,在app.description属性配置中,使用${app.name}对前一个属性值进行了引用

如配置

tom.age=${random.int[10,20]}
tom.description=tom的年龄可能是${tom.age}
@Value("${tom.description}")
private String description;
@Test
public void placeholderTest() {
    System.out.println(description);
}

输出结果为:tom的年龄可能是13

springboot原理

依赖管理

问题1: 为什么导入dependency时不需要指定版本?

在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-starter-parent和spring-boot-starter-web

在pom.xml中包含了

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

进入父工程

spring-boot-starter-parent依赖

包含了

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.4.1</version>
</parent>

在进入父工程spring-boot-dependencies包含了

<properties>
  <activemq.version>5.16.0</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.83</appengine-sdk.version>
  <artemis.version>2.15.0</artemis.version>
  <aspectj.version>1.9.6</aspectj.version>
  <assertj.version>3.18.1</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.3</awaitility.version>
  <bitronix.version>2.1.4</bitronix.version>
  <build-helper-maven-plugin.version>3.2.0</build-helper-maven-plugin.version>
  <byte-buddy.version>1.10.18</byte-buddy.version>
  <caffeine.version>2.8.8</caffeine.version>
  <cassandra-driver.version>4.9.0</cassandra-driver.version>
  ***
</properties>

从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.2版本相匹配的版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。

需要说明的是,如果pom.xml引入的依赖文件不是 spring-boot-starter-parent管理的,那么在pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号。

问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运行依赖的JAR包是从何而来的?

在pom.xml中包含了

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

spring-boot-starter-web依赖 包含了相关jar包依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.1</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.4.1</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.4.1</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.2</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.2</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,我们可以打开Spring Boot官方文档,搜索“Starters”关键字查询场景依赖启动器

可以在官网查看:docs.spring.io/spring-boot…

image-20210114225000129.png

自动配置(启动流程)

问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?

总结 因此springboot底层实现自动配置的步骤是:

  1. springboot应用启动;
  2. @SpringBootApplication起作用;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的件导入到springboot创建管理的容器中;
  5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程

@SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:

|- @SpringBootConfiguration
    |- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
    |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
    |- @Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定义的bean添加到IOC容器中
|- @ComponentScan //包扫描

自定义starter

SpringBoot starter机制

SpringBoot由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行,也是因为starter。

starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。

例如,你想使用Reids插件,那么可以使用spring-boot-starter-redis;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb

为什么要自定义starter

开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要将其在pom中引用依赖即可,SpringBoot为我们完成自动装配

自定义starter的命名规则

SpringBoot提供的starter以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的starter使用xxx-spring-boot-starter 命名规则。以区分SpringBoot生态提供的starter

整个过程分为两部分:

  • 自定义starter
  • 使用starter

自定义starter

(1)新建maven jar工程,工程名为zdy-spring-boot-starter,导入依赖:

(2)编写javaBean :SimpleBean.java

(3)编写配置类MyAutoConfiguration

@Configuration
@ConditionalOnClass/*(SimpleBean.class)*/   //@ConditionalOnClass:当类路径classpath下有指定的类的情况下进行自动配置
public class MyAutoConfiguration {
    @Bean
    public SimpleBean simpleBean() {
        return new SimpleBean();
    }
}

注意:类上需要加注解@ConditionalOn**等条件注解,如:@ConditionalOnClass表示该配置类必须保证类路径下有指定的类才可以初始化,否则不能初始化,避免初始化时出现空指针的问题。因为配置中如果使用了指定的类,但是容器中没有的时候,就会出现异常。当不指定任何类,则表示该类一定会让springboot完成自动配置。

(4)resources下创建/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xiewz.config.MyAutoConfiguration

注意:META-INF是自己手动创建的目录,spring.factories也是手动创建的文件,在该文件中配置自己的自动配置类

使用自定义starter

(1)导入自定义starter的依赖

(2)在全局配置文件中配置属性值

(3)编写测试方法

执行原理

通过main方法启动

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

进入run方法,执行两件事:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   //SpringApplication的启动由两部分组成:
   //1. 实例化SpringApplication对象
   //2. run(args):调用run方法
   return new SpringApplication(primarySources).run(args);
}

构造SpringApplication对象,主要做的事有:

设置初始化器(Initializer),最后会调用这些初始化器
设置监听器(Listener)
初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类

run(args)方法中的事有:

(1)获取并启动监听器
(2)项目运行环境Environment的预配置
(3)创建Spring容器
(4)Spring容器前置处理
(5):刷新容器
(6):Spring容器后置处理
(7)发出结束执行的事件通知
(8):执行Runners
(9) 发布应用上下文就绪事件
最后再返回容器

springboot启动流程图.png

SpringBoot数据访问

1. Spring Boot整合MyBatis

  • 数据准备

      # 创建数据库
      CREATE DATABASE springbootdata;
      # 选择使用数据库
      USE springbootdata;
      # 创建表t_article并插入相关数据
      DROP TABLE IF EXISTS t_article;
      CREATE TABLE t_article (
          id INT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '文章id',
          title VARCHAR ( 200 ) DEFAULT NULL COMMENT '文章标题',
          content LONGTEXT COMMENT '文章内容',
          PRIMARY KEY ( id ) 
      ) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;
      INSERT INTO t_article VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...');
      INSERT INTO t_article VALUES ('2', 'Spring Cloud基础入门', '从入门到精通讲解...');
      # 创建表t_comment并插入相关数据
      DROP TABLE IF EXISTS t_comment;
      CREATE TABLE t_comment (
          id INT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '评论id',
          content LONGTEXT COMMENT '评论内容',
          author VARCHAR ( 200 ) DEFAULT NULL COMMENT '评论作者',
          a_id INT ( 20 ) DEFAULT NULL COMMENT '关联的文章id',
          PRIMARY KEY ( id ) 
      ) ENGINE = INNODB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8;
      INSERT INTO t_comment VALUES ('1', '很全、很详细', 'luccy', '1');
      INSERT INTO t_comment VALUES ('2', '赞一个', 'tom', '1');
      INSERT INTO t_comment VALUES ('3', '很详细', 'eric', '1');
      INSERT INTO t_comment VALUES ('4', '很好,非常详细', '张三', '1');
      INSERT INTO t_comment VALUES ('5', '很不错', '李四', '2');
    
  • 创建项目,引入相应的启动器

    image20210116013835450.png

  • 编写与数据库表t_comment和t_article对应的实体类Comment和Article

    public class Comment {
        private Integer id;
        private String content;
        private String author;
        private Integer aId;
        // 省略属性getXX()和setXX()方法
        // 省略toString()方法
    }
    
    public class Article {
        private Integer id;
        private String title;
        private String content;
        // 省略属性getXX()和setXX()方法
        // 省略toString()方法
    }
    
  • 编写配置文件

    application.properties配置文件中进行数据库连接配置

    # MySQL数据库连接配置
    spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=root
    

注解方式整合Mybatis

(1)创建一个用于对数据库表t_comment数据操作的接口CommentMapper

@Mapper //标识该接口是mybatis的接口文件,并且让springboot能够扫描到该接口,生成该接口的代理对象,存到容器中
public interface CommentMapper {
    @Select("SELECT * FROM t_comment WHERE id =#{id}")
    public Comment findById(Integer id);
}

(2)编写测试方法

@Autowired
private CommentMapper commentMapper;

@Test
void contextLoads() {
    System.out.println(commentMapper.findById(1));
}

此时结果没有aId的值

image20210116015656766.png

发现数据库中使用下划线,代码中需要转换为驼峰命名,所以需要添加配置到application.properties

#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true

使用配置文件的方式整合MyBatis

(1)创建一个用于对数据库表t_article数据操作的接口ArticleMapper

@Mapper
public interface ArticleMapper {
    //根据id查询对应的文章
    public Article selectArticle(Integer id);
}

(2)创建XML映射文件

resources目录下创建一个统一管理映射文件的包mapper,并在该包下编写与ArticleMapper接口方应的映射文件ArticleMapper.xml

<?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.xiewz.mapper.ArticleMapper">
    <select id="selectArticle" parameterType="int" resultType="com.xiewz.pojo.Article">
    select * from t_article where id = #{id}
   </select>
</mapper>

(3)配置XML映射文件路径。

先在全局配置文件application.properties 中配置xml配置文件的路径:

#配置mybatis的xml映射配置文件路径
mybatis.mapper-locations=classpath:mapper/*.xml
#配置mybatis映射配置文件中实体类别名
mybatis.type-aliases-package=com.xiewz.pojo

设置别名后,xml中的配置可以为:resultType="article"

(4)编写单元测试进行接口方法测试

@Autowired
private ArticleMapper articleMapper;

@Test
void articleMapperTest() {
    System.out.println(articleMapper.selectArticle(1));
}

最后:在启动类上加注解:@MapperScan("com.xiewz.mapper")后,就不需要在每个Mapper接口上添加@Mapper了。

2 Spring Boot整合JPA

(1)添加Spring Data JPA依赖启动器。

<!--Spring Data JPA依赖启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

(2)编写ORM实体类。

@Entity
@Table(name = "t_comment")
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String content;
    private String author;
    @Column(name = "a_id")
    private Integer aId;
    // get/set/toString 省略
}

(3)编写Repository接口 :CommentRepository

public interface CommentRepository extends JpaRepository<Comment,Integer> {
}

(4)测试

// 测试整合JPA
@Autowired
private CommentRepository commentRepository;

@Test
public void commentRepositoryTest(){
    Optional<Comment> byId = commentRepository.findById(1);
    // if(byId.isPresent()){}
    System.out.println(byId.get());
}

3 Spring Boot整合Redis

(1)添加Spring Data Redis依赖启动器。

<!--Spring Data Redis依赖启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)编写实体类。

@RedisHash(value = "persons") //指定实体类对象在redis中的存储空间
public class Person {

    @Id // 用来标识实体类主键  字符串形式的hashkey标识唯一的实体类对象id
    private String id;
    @Indexed // 用来标识对应属性在redis中生成二级索引
    private String firstname;
    @Indexed
    private String lastname;
    private Address address;
    // get/set/toString 省略
}

public class Address {
    @Indexed
    private String city; //城市
    @Indexed
    private String country; //国家
    // get/set/toString 省略
}

(3)编写Repository接口。

public interface PersonRepository extends CrudRepository<Person,String> {
    // 根据城市信息查询对应的人
    List<Person> findByAddress_City(String name);
}

只能继承底层的CrudRepository,不用继承JpaRepository。因为JpaRepository是jpa整合的接口

(4)Redis数据库连接配置。

#redis服务器地址
spring.redis.host=127.0.0.1
#redis服务器连接端口
spring.redis.port=6379
#redis服务器连接密码
spring.redis.password=

(5)编写单元测试进行接口方法测试

// 测试整合Redis
@Autowired
private PersonRepository personRepository;
@Test
public void savePerson(){
    Person person = new Person();
    person.setFirstname("张");
    person.setLastname("三");

    Address address = new Address();
    address.setCity("北京");
    address.setCountry("中国");
    person.setAddress(address);

    // 向redis数据库中添加了数据
    personRepository.save(person);

}
@Test
public void personRepositoryTest(){
    List<Person> list = personRepository.findByAddress_City("北京");
    for (Person person : list) {
        System.out.println(person);
    }
}

image20210116025148104.png

执行savePerson()方法后,数据保存在redis,可以使用可视化工具RedisDesktopManager来查看

image20210116030135660.png