【java高级进阶笔记4】之约定优于配置设计范式及Spring Boot源码剖析

2022-07-27,,,,

1、springboot基础

1.1 Spring的优缺点及SpringBoot如何解决Spring的缺点

1.2.1 Spring的优缺点

优点

  • Spring是Java企业版(Java Enterprise Edition,JEE,也称J2EE)的轻量级代替品
  • Spring为企业级Java开发提供了一种相对简单的方法,无需开发重量级的Enterprise JavaBean(EJB)
  • 通过依赖注入和面向切面编程,用简单的Java对象实现了EJB的功能
    缺点
  • Spring会使用大量的XML配置
  • 需要导入大量类库的坐标,而且还需要分析与之有依赖关系的其他库的坐标
  • 依赖的版本出错后,会出现一些jar不兼容的问题

1.2.1 SpringBoot如何解决这些缺点

SpringBoot基于约定优于配置的思想对上述Spring的缺点进行的改善和优化

起步依赖

  • 起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能
  • 简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能

自动配置

  • 自动配置指的是springboot会自动将一些配置类的bean注册进ioc容器,在需要的地方使用@autowired或者@resource等注解来使用它

总结:springboot可以简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发部署效率

1.2 SpringBoot概念

什么是springBoot

  • Spring Boot 的设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用特定方式来进行配置,从而使开发人员不再需要定义样板化的配置
  • Spring Boot 其实不是什么新的框架,它默认配置了很多框架的使用方式。

约定优于配置

  • 约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式
  • 本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置
  • 约定优于配置简单来理解,就是遵循约定

1.3 创建springBoot工程编写入门案例

1.3.1 使用Idea创建工程

  • File->New->Project

  • 选择Spring Initializr,选择jdk版本,然后点击next

  • 修改group、Artifact、版本,然后点击next

  • 选择Web,Spring Web,选择SpringBoot版本,点击next

  • 修改名称和本地路径,点击finish

1.3.2 编写入门案例

  • 新创建工程的目录结构,如下
  • 新建DemoController
package com.lagou.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 该注解等同于Spring中@Controller+@ResponseBody注解
public class DemoController {

  @RequestMapping("/demo")
  public String demo() {
    System.out.println("测试");
    return "hello springboot222!";
  }
}
  • 运行主程序启动类SpringbootDemoApplication,项目启动成功后,浏览器访问

1.4 全局配置文件

Spring Boot使用一个application.properties或者application.yaml的文件作为全局配置文件
该文件存放在src/main/resource目录或者类路径的/config,一般会选择resource目录

1.4.1 application.properties配置文件

  • 新建Person类,定义属性,增加@Component()和@ConfigurationProperties注解
package com.lagou.demo.projo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

@Component()
@ConfigurationProperties(prefix = "person")
public class Person {
  private int id; // id
  private String name; // 名称
  private List hobby; // 爱好
  private String[] family; // 家庭成员
  private Map map;
  private Pet pet;
  // set/get方法和toString方法省略
}
  • 新建宠物Pet类
package com.lagou.demo.projo;

/** 宠物 */
public class Pet {
  private String name;
  private String type;
  // set/get方法和toString方法省略
}
  • resources目录下创建application.properties
person.id=1
person.name=zhangsan
person.hobby=吃饭,睡觉,打豆豆
person.family=father,mother
person.map.k1=value1
person.map.k2=value2
person.pet.name=小黑
person.pet.type=dog
  • SpringbootDemoApplicationTests新增测试方法
package com.lagou.demo;

import com.lagou.demo.projo.Person;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) // 测试启动器,并加载Spring Boot测试注解
@SpringBootTest // 标记为Spring Boot单元测试类,并加载项目的ApplicationContext上下文环境
class SpringbootDemoApplicationTests {

  @Autowired private Person person;
  
  @Test
  public void contextLoads() {
    System.out.println(person);
  }
}
  • 测试执行结果

1.4.2 application.yaml配置文件

  • 将application.properties中内容注释,然后新建application.yaml文件,其他不用调整
person:
  id: 2
  name: lisi
  hobby: [eat,sleep,play]
  family: [father,mother]
  map: {k1: v1,k2: v2}
  pet: {type: cat,name: 小白}
  • 执行结果

    注:application.yml文件使用 “key:(空格)value”格式配置属性,使用缩进控制层级关系,key:后面必须有空格

1.5 配置文件属性值的注入

1.5.1 使用@ConfigurationProperties注入属性,1.4已经实现

1.5.2 使用@Value注入属性,直接在属性上加注解

@Value("${person.id}")
private int id; // id

@Value("${person.name}")
private String name; // 名称

1.6 自定义配置

1.6.1 使用@PropertySource加载配置文件

  • resources目录下创建test.properties
test.id=12
test.name=lili
  • 创建MyProperties配置类
package com.lagou.demo.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration // 自定义配置类
@PropertySource("classpath:test.properties") // 指定自定义配置文件位置和名称
@EnableConfigurationProperties(MyProperties.class) // 启对应配置类的属性注入功能
@ConfigurationProperties(prefix = "test")
public class MyProperties {
  private Integer id;
  private String name;
  // set/get方法和toString方法省略
}
  • SpringbootDemoApplicationTests中新增
@Autowired private MyProperties myProperties;

@Test
public void testMyProperties() {
  System.out.println(myProperties);
}

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

  • 创建MyService类
package com.lagou.demo.config;

public class MyService {
    
}
  • 创建MyConfig类
package com.lagou.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 定义该类是一个配置类
public class MyConfig {
  @Bean
  public MyService myService() {
    return new MyService();
  }
}
  • SpringbootDemoApplicationTests中新增
@Autowired private ApplicationContext applicationContext;

@Test
public void testMyConfig() {
  System.out.println(applicationContext.containsBean("myService"));
}
  • 控制台打印结果为true,说MyConfig 已经放入到ioc容器中了

1.7 随机数设置及参数间引用

1.7.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]之间的随机整数

1.7.2 参数间引用

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

注:后一个配置的属性值中直接引用先前已经定义过的属性,在多个具有相互关联的配置属性中,只需要对其中一处属性预先配置,其他地方都可以引用,省去了后续多处修改的麻烦

2、SpringBoot原理深入及源码剖析

2.1 依赖管理

2.1.2 为什么导入dependency时不需要指定版本?

  1. 项目的pom.xml有父pom依赖(spring-boot-starter-parent.pom)
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
  • 父pom依赖(spring-boot-starter-parent.pom)的部分代码如下
<!-- 依赖父pom -->
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.2.RELEASE</version><relativePath/>
</parent>

<!-- 定义jdk版本和编码方式 -->
<properties>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <resource.delimiter>@</resource.delimiter>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.target>${java.version}</maven.compiler.target>
</properties>

<!-- 默认加载的配置文件 -->
<resources>
  <resource>
    <filtering>true</filtering>
    <directory>${basedir}/src/main/resources</directory>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>

<!-- 各种插件 -->
<pluginManagement>
  <plugins>
    <plugin>
    	......
    </plugin>
    ......
   </plugins>
</pluginManagement>
  • 父pom依赖(spring-boot-starter-parent.pom)的功能
    1)定义了java编译版本为1.8
    2)使用UTF-8格式编码
    3)继承spring-boot-dependencies,这里面定义了依赖的版本,也正是因为继承了这个依赖,所以我们pom文件中不需要写版本号
    4)执行打包操作的配置
    5)自动化的资源过滤
    6)自动化的插件配置
    7)针对application.propertiesapplication.yml的资源的过滤,通过profile定义的不同环境的配置文件,例如application-dev.propertiesapplication-dev.yml
  1. pring-boot-starter-parent.pom文件也有自己的父pom文件spring-boot-dependencies.pom
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.2.RELEASE</version><relativePath/>
</parent>
  • 爷爷pom依赖(spring-boot-dependencies.pom)的部分代码如下
<!-- 定义了版本变量 -->
<properties>
  <activemq.version>5.15.11</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.77</appengine-sdk.version>
  <artemis.version>2.10.1</artemis.version>
  <aspectj.version>1.9.5</aspectj.version>
  <assertj.version>3.13.2</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.1</awaitility.version>
  <bitronix.version>2.1.4</bitronix.version>
  ......
  ......
</properties>

<!-- 定义引入依赖jar的版本 -->
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <version>2.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-test</artifactId>
      <version>2.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-test-autoconfigure</artifactId>
      <version>2.2.2.RELEASE</version>
    </dependency>
    ......
  	......
	</dependencies>
</dependencies>
  • 爷爷pom依赖(spring-boot-dependencies.pom)的作用
    1)该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理

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

2.1.2 项目运行依赖的JAR包是从何而来?

  1. 项目的pom.xml中引入spring-boot-starter-web依赖
  • 依赖引入
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 作用
    1)spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖
    2)这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理
  1. Spring Boot官方提供的依赖
  2. 针对官方为提供依赖的,如MyBatis、Druid等技术框架,也主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等

2.2 自动配置(启动流程)

2.2.1 自动配置概念

能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目

2.2.2 SpringBoot应用启动的入口

package com.lagou.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // 扫描spring组件并自动配置spring Boot
public class SpringbootDemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringbootDemoApplication.class, args);
  }
}
  • 启动入口:使用@SpringBootApplication注解标注类中的main()方法
  • @SpringBootApplication能够扫描Spring组件并自动配置Spring Boot

2.2.3 @SpringBootApplication注解

@Target(ElementType.TYPE)    //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) ///表示注解的生命周期,Runtime运行时
@Documented 表示注解可以记录在javadoc中
@Inherited   //表示可以被子类继承该注解

@SpringBootConfiguration  标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
  • 这是一个组合注解
  • 前4个是注解的元数据信息
  • 后3个是三个核心注解:@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan

2.2.3 三个核心注解之@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration// 配置IOC容器
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
  • 内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类
  • @SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类

2.2.4 三个核心注解之@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class) //可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {
}
  • 这也是一个组合注解
  • @AutoConfigurationPackage,自动配置包,会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中

@AutoConfigurationPackage代码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@Import(AutoConfigurationPackages.Registrar.class)  //  默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
public @interface AutoConfigurationPackage {
}

将Registrar这个组件类导入到容器中,Registrar.java是AutoConfigurationPackages.java的内容类,主要功能是将主程序类所在包及所有子包下的组件到扫描到spring容器中,核心代码如下(注:AutoConfigurationPackages.java有三个内部类:BasePackages.java、PackageImport.java、Registrar.java)

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        } else {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            beanDefinition.setRole(2);
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }

    }

    private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
        String[] existing = (String[])((String[])constructorArguments.getIndexedArgumentValue(0, String[].class).getValue());
        Set<String> merged = new LinkedHashSet();
        merged.addAll(Arrays.asList(existing));
        merged.addAll(Arrays.asList(packageNames));
        return StringUtils.toStringArray(merged);
    }

// 内部类Registrar.java
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

	// 获取的是项目主程序启动类所在的目录
	// metadata:注解标注的元数据信息
	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
		register(registry, new PackageImport(metadata).getPackageName());
	}

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


// 内部类PackageImport.java
private static final class PackageImport {
    private final String packageName;

    PackageImport(AnnotationMetadata metadata) {
        this.packageName = ClassUtils.getPackageName(metadata.getClassName());
    }

    String getPackageName() {
        return this.packageName;
    }

    public boolean equals(Object obj) {
        return obj != null && this.getClass() == obj.getClass() ? this.packageName.equals(((AutoConfigurationPackages.PackageImport)obj).packageName) : false;
    }

    public int hashCode() {
        return this.packageName.hashCode();
    }

    public String toString() {
        return "Package Import " + this.packageName;
    }
}


// 内部类BasePackages.java
static final class BasePackages {
    private final List<String> packages;
    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList();
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }

        this.packages = packages;
    }

    List<String> get() {
        if (!this.loggedBasePackageInfo) {
            if (this.packages.isEmpty()) {
                if (AutoConfigurationPackages.logger.isWarnEnabled()) {
                    AutoConfigurationPackages.logger.warn("@EnableAutoConfiguration was declared on a class in the default package. Automatic @Repository and @Entity scanning is not enabled.");
                }
            } else if (AutoConfigurationPackages.logger.isDebugEnabled()) {
                String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
                AutoConfigurationPackages.logger.debug("@EnableAutoConfiguration was declared on a class in the package '" + packageNames + "'. Automatic @Repository and @Entity scanning is enabled.");
            }
            this.loggedBasePackageInfo = true;
        }
        return this.packages;
    }
}
  • @Import(AutoConfigurationImportSelector.class),可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器

2.2.5 三个核心注解之@ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
}
  • @ComponentScan注解扫描的包的根路径由SpringBoot项目主程序启动类所在包位置决定
  • 在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到SpringBoot项目主程序启动类所在包的具体位置

总结:@SpringBootApplication注解的结构

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

2.3 自定义Stater

2.3.1 SpringBoot starter机制

  • SpringBoot由众多Starter组成(一系列的自动化配置的starter插件)
  • starter是SpringBoot非常重要的一部分,可以理解为一个可拔插式的插件
  • 这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息
  • 由SpringBoot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean
  • 例如,使用Reids插件的spring-boot-starter-redis;使用MongoDB插件的spring-boot-starter-data-mongodb

2.3.2 starter的命名规则

  • SpringBoot官方命名规则:spring-boot-starter-xxx
  • 自定义的starter命名规则:xxx-spring-boot-starter

2.3.3 自定义starter及使用的代码实现

自定义starter

  1. 新增maven jar工程,导入依赖,pom.xml文件如下
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.lagou</groupId>
    <artifactId>zdy-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>
</project>
  1. 编写javaBean
package com.lagou.projo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@EnableConfigurationProperties(SimpleBean.class)
@ConfigurationProperties(prefix = "simplebean")
public class SimpleBean {
  private int id;
  private String name;
  // set/get方法和toString方法省略
}
  1. 编写配置类MyAutoConfiguration
package com.lagou.config;

import com.lagou.projo.SimpleBean;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnClass // 当类路径classpath下有指定的类,就会自动配置
public class MyAntoConfiguration {
  static {
    System.out.println("MyAntoConfiguration init");
  }

  @Bean
  public SimpleBean simpleBean() {
    return new SimpleBean();
  }
}
  1. resources下创建/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lagou.config.MyAntoConfiguration

使用starter

  1. 导入自定义starter的依赖
<dependency>
	<groupId>org.lagou</groupId>
	<artifactId>zdy-spring-boot-starter</artifactId>
	<version>1.0-SNAPSHOT</version>
</dependency>
  1. 在全局配置文件中配置属性值
simplebean.id=1
simplebean.name=自定义starter
  1. 编写测试方法
// 测试自定义starter
@Autowired private SimpleBean simpleBean;

@Test
public void zdyStarterTest() {
  System.out.println(simpleBean);
}

2.4 执行原理

2.4.1 入口

每个SpringBoot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个SpringBoot程序

  1. 主程序启动类的main方法
@SpringBootApplication // 扫描spring组件并自动配置spring Boot
public class SpringbootDemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringbootDemoApplication.class, args);
  }
}
  1. SpringApplication的run方法
public static ConfigurableApplicationContext run(Class<?> primarySource,String... args) {
	return run(new Class[]{primarySource}, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources,String[] args) {
	return (new SpringApplication(primarySources)).run(args);
}

SpringApplication.run()方法内部执行了两个操作,执行了两个操作:SpringApplication实例的初始化创建和调用run()启动项目

2.4.1 核心逻辑

2.4.1.1 SpringApplication实例的初始化

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.sources = new LinkedHashSet();
	this.bannerMode = Mode.CONSOLE;
	this.logStartupInfo = true;
	this.addCommandLineProperties = true;
	this.addConversionService = true;
	this.headless = true;
	this.registerShutdownHook = true;
	this.additionalProfiles = new HashSet();
	this.isCustomEnvironment = false;
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");

	//项目启动类 SpringbootDemoApplication.class设置为属性存储起来
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

	//设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
	this.webApplicationType = WebApplicationType.deduceFromClasspath();

	// 设置初始化器(Initializer),最后会调用这些初始化器
	//所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

	// 设置监听器(Listener)
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

	// 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
	this.mainApplicationClass = deduceMainApplicationClass();
}

2.4.1.1 调用SpringApplication.run()方法启动项目

public ConfigurableApplicationContext run(String... args) {
  // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	// 初始化应用上下文和异常报告集合
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	// 配置 headless 属性
	configureHeadlessProperty();


	//   (1)获取并启动监听器
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
	    // 创建  ApplicationArguments 对象 初始化默认应用参数类
		// args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

		//(2)项目运行环境Environment的预配置
		// 创建并配置当前SpringBoot应用将要使用的Environment
		// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

		configureIgnoreBeanInfo(environment);
		// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
		Banner printedBanner = printBanner(environment);

		// (3)创建Spring容器
		context = createApplicationContext();
		// 获得异常报告器 SpringBootExceptionReporter 数组
		//这一步的逻辑和实例化初始化器和监听器的一样,
		// 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
		exceptionReporters = getSpringFactoriesInstances(
				SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);


		// (4)Spring容器前置处理
		//这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);

		// (5):刷新容器
		refreshContext(context);

		// (6):Spring容器后置处理
		//扩展接口,设计模式中的模板方法,默认为空实现。
		// 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
		afterRefresh(context, applicationArguments);
		// 停止 StopWatch 统计时长
		stopWatch.stop();
		// 打印 Spring Boot 启动的时长日志。
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// (7)发出结束执行的事件通知
		listeners.started(context);

		// (8):执行Runners
		//用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
		//Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
		//Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
		callRunners(context, applicationArguments);
	} catch (Throwable ex) {
	    // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

  //   (9)发布应用上下文就绪事件
	//表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
	// 这样整个Spring Boot项目就正式启动完成了。
	try {
		listeners.running(context);
	} catch (Throwable ex) {
          // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
          handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	 //返回容器
	return context;
}

3、SpringBoot高级进阶

3.1 SpringBoot数据访问SpringData

  • SpringData是Spring提供的一个用于简化数据库访问、支持云服务的开源框架
  • 是一个伞形项目,包含了大量关系型数据库及非关系型数据库的数据访问解决方案
  • 其设计目的是使我们可以快速且简单地使用各种数据访问技术
  • Spring Boot默认采用整合SpringData的方式统一处理数据访问层
  • 通过添加大量自动配置,引入各种数据访问模板xxxTemplate以及统一的Repository接口,从而达到简化数据访问层的操作

常见数据库依赖启动器

名称 描述
spring-boot-starter-data-jpa 使用Spring Data JPA与Hibernate
spring-boot-starter-data-mongodb 使用MongoDB和Spring Data MongoDB
spring-boot-starter-data-neo4j 使用Neo4j图数据库和Spring Data Neo4j
spring-boot-starter-data-redis 使用Redis键值数据存储与Spring Data Redis和Jedis客户端

3.1.1 Spring Boot整合MyBatis

  1. 添加mybatis依赖启动器
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.3</version>
</dependency>
  1. 创建表
# 创建表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基础入门', 'Spring Boot基础入门...');
INSERT INTO t_article VALUES ('2', 'Spring Boot基础进阶', 'Spring Boot基础进阶...');

# 创建表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');
  1. application.properties配置文件
# 数据库连接配置
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
  1. 实体类Comment.javaArticle.java
package com.lagou.projo;

public class Comment {
  private Integer id;
  private String content;
  private String author;
  private Integer aId;
  // set/get方法和toString方法省略
}
package com.lagou.projo;

  public class Article {
    private Integer id;
    private String title;
    private String content;
    // set/get方法和toString方法省略
}
  1. 两种不同方式整合mybatis

1)注解方式整合Mybatis

数据库操作接口CommentMapper.java

@Mapper
public interface CommentMapper {
  @Select("select * from t_comment where id = #{id}")
  public Comment selectById(Integer id);
}

单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
class Springboot03DataApplicationTests {
  @Autowired private CommentMapper commentMapper;

  @Test
  void testSelectCommentById() {
    Comment coment = commentMapper.selectById(1);
    System.out.println(coment);
  }
}

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

数据操作的接口ArticleMapper.java

@Mapper
public interface ArticleMapper {
  public Article findById(Integer id);
}

resources目录创建文件夹mapper,mapper目录下创建映射文件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.lagou.dao.ArticleMapper">
    <select id="findById" parameterType="java.lang.Integer" resultType="com.lagou.projo.Article">
        select * from t_article where id = #{id}
    </select>
</mapper>

application.properties配置文件中配置MyBatis的xml配置文件路径

#配置MyBatis的xml配置文件路径
mybatis.mapper-locations=classpath:mapper/*.xml

单元测试

@Autowired private ArticleMapper articleMapper;

@Test
public void testSelectArticleById() {
  Article article = articleMapper.findById(1);
  System.out.println(article);
}

注:主程序启动类上配置@MapperScan,数据操作的接口上可以不用配置@Mapper

@SpringBootApplication
@MapperScan("com.lagou.dao") // 扫描包路径下的所有mapper接口
public class Springboot03DataApplication {
  public static void main(String[] args) {
    SpringApplication.run(Springboot03DataApplication.class, args);
  }
}

3.1.2 Spring Boot整合JPA

  1. 添加jpa依赖启动器
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  1. 创建ORM实体类JpaComment.java
package com.lagou.projo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity(name = "t_comment")
public class JpaComment {

  /** 主键 */
  @Id
  @Column(name = "id")
  private Integer id;

  /** 内容 */
  @Column(name = "content")
  private String content;

  /** 作者 */
  @Column(name = "author")
  private String author;

  /** 关联文章的id */
  @Column(name = "a_id")
  private Integer aId;
  // set/get方法和toString方法省略
}
  1. 创建Repository接口 CommentRepository.java
package com.lagou.dao;

import com.lagou.projo.JpaComment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<JpaComment, Integer> {
}
  1. 单元测试
@Autowired
private CommentRepository commentRepository;

@Test
public void testJpa() {
  Optional<JpaComment> byId = commentRepository.findById(1);
  JpaComment jpaComment = byId.get();
  System.out.println(jpaComment);
}

3.1.3 Spring Boot整合Redis

  1. 添加redis依赖启动器
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
  1. application.properties配置文件
spring.redis.host=
spring.redis.port=
spring.redis.password=
  1. 创建实体对象Person.javaAddress.java
package com.lagou.domain;

import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import javax.persistence.Id;
@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/get方法和toString方法省略
}
package com.lagou.domain;

import org.springframework.data.redis.core.index.Indexed;

public class Address {
  @Indexed // 标识对应属性在Redis数据库中生成二级索引
  private String city;
  @Indexed // 标识对应属性在Redis数据库中生成二级索引
  private String country;
  // get/get方法和toString方法省略
}
  1. 单元测试RedisTest.java
package com.lagou;

import com.lagou.dao.PersonRepository;
import com.lagou.domain.Address;
import com.lagou.domain.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Collections;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
  @Autowired private PersonRepository personRepository;

  /** 新增方法 */
  @Test
  public void testSave() {
    Person person = new Person();
    person.setId("1");
    person.setFirstname("张");
    person.setLastname("三丰");

    Address address = new Address();
    address.setCountry("中国");
    address.setCity("上海");
    person.setAddress(address);

    Person save = personRepository.save(person);
    System.out.println(save);
  }

  /** 查询方法 */
  @Test
  public void testQuery() {
    Iterable<Person> allById = personRepository.findAllById(Collections.singleton("1"));
    for (Person person : allById) {
      System.out.println(person);
    }

    List<Person> list = personRepository.findByAddress_City("上海");
    for (Person person : list) {
      System.out.println(person);
    }

    List<Person> persons = personRepository.findByFirstname("张");
    for (Person person : persons) {
      System.out.println(person);
    }
  }
}

3.2 SpringBoot缓存管理

3.2.1 默认缓存管理

  • Spring框架支持透明地向应用程序添加缓存对缓存进行管理,核心是将缓存应用于操作数据的方法,减少操作数据的执行次数,同时不会对程序本身造成任何干扰
  • SpringBoot继承了Spring框架的缓存管理功能,通过使用@EnableCaching注解开启基于注解的缓存支持,Spring Boot就可以启动缓存管理的自动化配置

代码实现

  1. 创建springBoot工厂,Dependencies依赖选择项中添加SQL模块中的JPA依赖、MySQL依赖和Web模块中的Web依赖

  2. application.properties文件

#数据库连接
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=

#显示使用JPA进行数据库查询的SQL语句
spring.jpa.show-sql=true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
#解决乱码
server.servlet.encoding.force-response=true
  1. 实体类Article.java
package com.lagou.projo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity(name="t_article")
public class Article {
    @Id
    @Column(name="id")
    private Integer id;
    @Column(name="title")
    private String title;
    @Column(name="content")
    private String content;
	// set/get方法和toString方法省略
}
  1. Repository接口ArticleRepository.java
package com.lagou.dao;

import com.lagou.projo.Article;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ArticleRepository extends JpaRepository<Article, Integer> {
}
  1. service层ArticleService.javaArticleServiceImpl.java
package com.lagou.service;

import com.lagou.projo.Article;

public interface ArticleService {
    /**
     * 根据id查询文章信息
     * @param id
     * @return
     */
    public Article findById(Integer id);
}
package com.lagou.service.impl;

import com.lagou.dao.ArticleRepository;
import com.lagou.projo.Article;
import com.lagou.service.ArticleService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleRepository articleRepository;

    @Cacheable(value = "article")
    @Override
    public Article findById(Integer id) {
        Optional<Article> optional = articleRepository.findById(id);
        return optional.get();
    }
}
  1. controller层ArticleController.java
package com.lagou.controller;

import com.lagou.projo.Article;
import com.lagou.service.ArticleService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("article")
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    @RequestMapping("findById")
    @ResponseBody
    public Article findById(Integer id) {
        Article article = articleService.findById(1);
        return article;
    }
}
  1. 主程序启动类Springboot05CacheApplication.java
package com.lagou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching // 开启SpringBoot基于注解的缓存管理支持
@SpringBootApplication
public class Springboot05CacheApplication {
  public static void main(String[] args) {
    SpringApplication.run(Springboot05CacheApplication.class, args);
  }
}

说明:
1)启动类加@EnableCaching注解,开启基于注解的缓存支持
2)service层方法加@Cacheable注解,用于类或方法上,方法结果进行缓存存储;先进行缓存查询,如果为空则进行方法查询,并将结果进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据,@Cacheable注解提供了以下属性:

属性名 说明
value/cacheNames 指定缓存空间的名称,必配属性。这两个属性二选一使用
key 指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式
keyGenerator 指定缓存数据的key的生成器,与key属性二选一使用
cacheManager 指定缓存管理器
cacheResolver 指定缓存解析器,与cacheManager属性二选一使用
condition 指定在符合某条件下,进行数据缓存
unless 指定在符合某条件下,不进行数据缓存
sync 指定是否使用异步缓存。默认false

3)前端多次请求查询,只会执行一次查询脚本

3.2.2 整合Redis缓存实现

3.2.2.1 基于注解的Redis缓存实现

  1. 添加Spring Data Redis依赖启动器
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. application.properties文件增加redis配置
#redis配置
spring.redis.host=
spring.redis.port=
spring.redis.password=
  1. 实体类Comment.java
package com.lagou.projo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity(name="t_comment")
public class Comment {
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name = "content")
    private String content;

    @Column(name = "author")
    private String author;

    @Column(name = "a_id")
    private String aId;
    // set/get方法和toSring方法省略
}

3.2.2.2 基于API的Redis缓存实现

  1. service层ApiCommentService.javaApiCommentServiceImpl.java
package com.lagou.service;

import com.lagou.projo.Comment;

public interface ApiCommentService {

    /**
     * 根据id查询文章信息
     * @param id
     * @return
     */
    public Comment findCommentById(Integer id);

    /**
     * 根据id更新文章信息
     * @param comment
     * @return
     */
    public Comment updateComment(Comment comment);

    /**
     * 根据id删除文章信息
     * @param id
     */
    public void deleteComment(Integer id);
}
package com.lagou.service.impl;

import com.lagou.dao.CommentRepository;
import com.lagou.projo.Comment;
import com.lagou.service.ApiCommentService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

@Service
public class ApiCommentServiceImpl implements ApiCommentService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private CommentRepository commentRepository;

    private static final String PRE_REDIS_COMMENT = "comment_";

    @Override
    public Comment findCommentById(Integer id) {
        Comment comment = (Comment)redisTemplate.opsForValue().get(PRE_REDIS_COMMENT + id);
        if (comment != null) {
            return comment;
        }

        Optional<Comment> optional = commentRepository.findById(id);
        if (optional.isPresent()) {
            comment = optional.get();
            redisTemplate.opsForValue().set(PRE_REDIS_COMMENT + id, comment,1, TimeUnit.DAYS);
            return comment;
        }
        return null;
    }

    @Override
    public Comment updateComment(Comment comment) {
        commentRepository.updateComment(comment.getAuthor(),comment.getId());
        redisTemplate.opsForValue().set(PRE_REDIS_COMMENT + comment.getId(), comment);
        return null;
    }

    @Override
    public void deleteCommentById(Integer id) {
        commentRepository.deleteById(id);
        redisTemplate.delete(PRE_REDIS_COMMENT + id);
    }
}
  • 控制器ApiCommentController.java
package com.lagou.controller;

import com.lagou.projo.Comment;
import com.lagou.service.ApiCommentService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/api")
public class ApiCommentController {

    @Autowired
    private ApiCommentService apiCommentService;

    @RequestMapping("/findById")
    @ResponseBody
    public Comment findById(Integer id){
        System.out.println("findById  start....");
        return apiCommentService.findCommentById(id);
    }

    @RequestMapping("/updateAuthorById")
    public void updateAuthorById(String author, Integer id){
        System.out.println("updateAuthorById  start....");
        Comment comment = apiCommentService.findCommentById(id);
        comment.setAuthor(author);
        apiCommentService.updateComment(comment);
    }

    @RequestMapping("/deleteComentById")
    public void deleteComentById(Integer id){
        System.out.println("deleteComentById  start....");
        apiCommentService.deleteCommentById(id);
    }
}

3.2.3 自定义Redis缓存序列化机制

自定义RedisTemplateRedisCacheManager

package com.lagou.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
public class RedisConfig {

    // 自定义一个RedisTemplate
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 创建JSON格式序列化对象,对缓存数据的key和value进行转换
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        //设置redisTemplate模板API的序列化方式为json
        template.setDefaultSerializer(jackson2JsonRedisSerializer);
        return template;
    }

	// 自定义一个RedisCacheManager
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 分别创建String和JSON格式序列化对象,对缓存数据key和value进行转换
        RedisSerializer<String> strSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jacksonSeial =
                new Jackson2JsonRedisSerializer(Object.class);

        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        // 定制缓存数据序列化方式及时效
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofDays(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(strSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(jacksonSeial))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager
                .builder(redisConnectionFactory).cacheDefaults(config).build();
        return cacheManager;
    }
}

本文地址:https://blog.csdn.net/yuyangzhi10/article/details/109431067

《【java高级进阶笔记4】之约定优于配置设计范式及Spring Boot源码剖析.doc》

下载本文的Word格式文档,以方便收藏与打印。