4.SpringBoot2原理篇

1.自动配置

1.1 bean加载方式(复习)

1.1.1 方式一

XML方式声明bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--xml方式声明自己开发的bean-->
    <bean id="cat" class="com.example.bean.Cat"/>
    <bean class="com.example.bean.Dog"/>

    <!--xml声明第三方开发的bean-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>

1.1.2 方式二

XML+注解方式声明bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.example"/>
</beans>

使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean

@Service
public class BookServiceImpl implements BookService {
    
    
}

使用@Bean定义第三方bean,并将所在类定义为配置类或Bean

@Component
public class DbConfig {
    
    
    @Bean
    public DruidDataSource getDataSource(){
    
    
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

1.1.3 方式三

注解方式声明配置类

@Configuration
@ComponentScan("com.example")
public class SpringConfig {
    
    
    @Bean
    public DruidDataSource getDataSource(){
    
    
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

@Configuration配置项如果不用于被扫描可以省略

扩展1
初始化实现FactoryBean接口的类,实现对bean加载到容器之前的批处理操作

public class BookFactoryBean implements FactoryBean<Book> {
    
    
    public Book getObject() throws Exception {
    
    
        Book book = new Book();
        // 进行book对象相关的初始化工作
        return book;
    }
    public Class<?> getObjectType() {
    
    
        return Book.class;
    }
}
public class SpringConfig8 {
    
    
    @Bean
    public BookFactoryBean book(){
    
    
        return new BookFactoryBean();
    }
}

扩展2
加载配置类并加载配置文件(系统迁移)

@Configuration
@ComponentScan("com.example")
@ImportResource("applicationContext-config.xml")
public class SpringConfig2 {
    
    
}

扩展3
使用proxyBeanMethods=true可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的

@Configuration(proxyBeanMethods = false)
public class SpringConfig3 {
    
    
    @Bean
    public Book book(){
    
    
        System.out.println("book init ...");
        return new Book();
    }
}
public class AppObject {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
        SpringConfig3 config = ctx.getBean("Config", Config.class);
        config.book();
        config.book();
    }
}

1.1.4 方式四

使用@Import注解导入要注入的bean对应的字节码

@Import(Dog.class)
public class SpringConfig5 {
    
    
}

被导入的bean无需使用注解声明为bean

public class Dog {
    
    
}

此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用

扩展4
使用@Import注解导入配置类

@Import(DbConfig.class)
public class SpringConfig {
    
    
}

1.1.5 方式五

使用上下文对象在容器初始化完毕后注入bean

public class AppImport {
    
    
    public static void main(String[] args) {
    
    
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig5.class);
        ctx.register(Cat.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
    
    
            System.out.println(name);
        }
    }
}

1.1.6 方式六

导入实现了ImportSelector接口的类,实现对导入源的编程式处理

public class MyImportSelector implements ImportSelector {
    
    
    public String[] selectImports(AnnotationMetadata metadata) {
    
    
        boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Import");
        if(flag){
    
    
            return new String[]{
    
    "com.example.domain.Dog"};
        }
        return new String[]{
    
    "com.example.domain.Cat"};
    }
}

1.1.7 方式七

导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
    
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService", beanDefinition);
    }
}

1.1.8 方式八

导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    
    
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService", beanDefinition);
    }
}

1.2 bean加载控制(复习)

bean的加载控制指根据特定情况对bean进行选择性加载以达到适用于项目的目标

编程式
根据任意条件确认是否加载bean

public class MyImportSelector implements ImportSelector {
    
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        try {
    
    
            Class<?> clazz = Class.forName("com.example.ebean.Mouse");
            if(clazz != null) {
    
    
                return new String[]{
    
    "com.example.bean.Cat"};
            }
        } catch (ClassNotFoundException e) {
    
    
            return new String[0];
        }
        return null;
    }
}

注解式
使用@Conditional注解的派生注解设置各种组合条件控制bean的加载

① 匹配指定类

public class SpringConfig {
    
    
    @Bean
    @ConditionalOnClass(Mouse.class)
    public Cat tom(){
    
    
        return new Cat();
    }
}

② 未匹配指定类

public class SpringConfig {
    
    
    @Bean
    @ConditionalOnClass(Mouse.class)
    @ConditionalOnMissingClass("com.itheima.bean.Wolf")
    public Cat tom(){
    
    
        return new Cat();
    }
}

③ 匹配指定类型的bean

@Import(Mouse.class)
public class SpringConfig {
    
    
    @Bean
    @ConditionalOnBean(Mouse.class)
    public Cat tom(){
    
    
        return new Cat();
    }
}

④ 匹配指定名称的bean

@Import(Mouse.class)
public class SpringConfig {
    
    
    @Bean
    @ConditionalOnBean(name="com.itheima.bean.Mouse")
    public Cat tom(){
    
    
        return new Cat();
    }
}
@Import(Mouse.class)
public class SpringConfig {
    
    
    @Bean
    @ConditionalOnBean(name="jerry")
    public Cat tom(){
    
    
        return new Cat();
    }
}

⑤ 匹配指定环境

@Configuration
@Import(Mouse.class)
public class MyConfig {
    
    
    @Bean
    @ConditionalOnClass(Mouse.class)
    @ConditionalOnMissingClass("com.itheima.bean.Dog")
    @ConditionalOnNotWebApplication
    public Cat tom(){
    
    
        return new Cat();
    }
}
public class SpringConfig {
    
    
    @Bean
    @ConditionalOnClass(name = "com.mysql.jdbc.Driver")
    public DruidDataSource dataSource(){
    
    
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

总结:使用@ConditionalOn***注解为bean的加载设置条件

1.3 bean依赖属性配置

环境准备

  1. 创建新的springboot项目
  2. 导入lombok依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
  1. Cat和Mouse实体类
package com.example.bean;

import lombok.Data;

@Data
public class Cat {
    
    
    private String name;
    private Integer age;
}
package com.example.bean;

import lombok.Data;

@Data
public class Mouse {
    
    
    private String name;
    private Integer age;
}

实验

  1. 将业务功能bean运行需要的资源抽取成独立的属性类(******Properties),设置读取配置文件信息
package com.example.bean;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "cartoon")
public class CartoonProperties {
    
    
    private Cat cat;
    private Mouse mouse;
}
  1. 配置文件中使用固定格式为属性类注入数据
cartoon:
  cat:
    name: "Tom_2"
    age: 5
  mouse:
    name: "jerry_2"
    age: 6
  1. 使用@EnableConfigurationProperties注解设定使用属性类时加载bean
package com.example.bean;

import lombok.Data;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.util.StringUtils;

@Data
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse {
    
    
    private Cat cat;
    private Mouse mouse;

    private CartoonProperties cartoonProperties;

    public CartoonCatAndMouse(CartoonProperties cartoonProperties){
    
    
        this.cartoonProperties = cartoonProperties;
        cat = new Cat();
        cat.setName(cartoonProperties.getCat()!=null &&
                StringUtils.hasText(cartoonProperties.getCat().getName()) ?
                cartoonProperties.getCat().getName() : "Tom");
        cat.setAge(cartoonProperties.getCat()!=null &&
                cartoonProperties.getCat().getAge()!=null ?
                cartoonProperties.getCat().getAge() : 3);

        mouse = new Mouse();
        mouse.setName(cartoonProperties.getMouse()!=null &&
                StringUtils.hasText(cartoonProperties.getMouse().getName()) ?
                cartoonProperties.getMouse().getName() : "Jerry");
        mouse.setAge(cartoonProperties.getMouse()!=null &&
                cartoonProperties.getMouse().getAge()!=null ?
                cartoonProperties.getMouse().getAge() : 4);
    }

    public void play(){
    
    
        System.out.println(cat.getAge()+"岁的"+cat.getName()+"和"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
    }
}
  1. 需要使用时通过@Import加载
package com.example;

import com.example.bean.CartoonCatAndMouse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@SpringBootApplication
@Import(CartoonCatAndMouse.class)
public class SpringbootPropertiesApplication {
    
    

    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext ctx = SpringApplication.run(SpringbootPropertiesApplication.class, args);
        CartoonCatAndMouse bean = ctx.getBean(CartoonCatAndMouse.class);
        bean.play();
    }
}

1.4 自动配置原理

  1. 收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表——>(技术集A)
  2. 收集常用技术(技术集A)的使用参数,整理开发过程中每个技术的常用设置列表——>(设置集B)
  3. 初始化SpringBoot基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
  4. 技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载
  5. 技术集A中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对)
  6. 设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量
public final class SpringFactoriesLoader {
    
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}

总的来说,SpringBoot中包含了很多常用的技术,且写有条件控制注解,当添加依赖后在符合控制条件的情况下,将会加载该技术相关配置的bean,而且每种技术配备有默认参数,可以通过配置文件覆盖默认参数。

1.5 变更自动配置

  1. 自定义自动配置(META-INF/spring.factories)
    在resources包下创建META-INF/spring.factories
#Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.bean.CartoonCatAndMouse

这样springboot就会自动加载CartoonCatAndMouse类,而不需要@Import(CartoonCatAndMouse.class)

  1. 控制SpringBoot内置自动配置类加载
    排除不需要加载的bean
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
      - org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration
@EnableAutoConfiguration(excludeName = "",exclude = {
    
    })

变更自动配置:去除tomcat自动配置(条件激活),添加jetty自动配置(条件激活)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!--web起步依赖环境中,排除Tomcat起步依赖,匹配自动配置条件-->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!--添加Jetty起步依赖,匹配自动配置条件-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>

2.自定义starter

2.1 案例:统计独立IP访问次数

案例:记录系统访客独立IP访问次数

  1. 每次访问网站行为均进行统计
  2. 后台每10秒输出一次监控信息(格式:IP+访问次数)

需求分析

  1. 数据记录位置:Map / Redis

  2. 功能触发位置:每次web请求(拦截器)
    ① 步骤一:降低难度,主动调用,仅统计单一操作访问次数(例如查询)
    ② 步骤二:开发拦截器

  3. 业务参数(配置项)
    ① 输出频度,默认10秒
    ② 数据特征:累计数据 / 阶段数据,默认累计数据
    ③ 输出格式:详细模式 / 极简模式

  4. 校验环境,设置加载条件

2.2 自定义starter

IP计数业务功能开发(自定义starter)

  1. 创建新的springboot(2.5.4)项目,Artifact设置为ip_spring_boot_starter
  2. 添加依赖坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  1. 业务功能开发
package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class IpCountService {
    
    
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();

    @Autowired
    //当前的request对象的注入工作由使用当前starter的工程提供自动装配
    private HttpServletRequest httpServletRequest;

    public void count(){
    
    
        //每次调用当前操作,就记录当前访问的IP,然后累加访问次数
        //1.获取当前操作的IP地址
        String ip = httpServletRequest.getRemoteAddr();
        System.out.println("---------------------------" + ip);
        //2.根据IP地址从Map取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null){
    
    
            ipCountMap.put(ip,1);
        }else{
    
    
            ipCountMap.put(ip,ipCountMap.get(ip) + 1);
        }
    }
}
  1. 自动配置类
package com.example.autoconfig;

import com.example.service.IpCountService;
import org.springframework.context.annotation.Import;

@Import(IpCountService.class)
public class IpAutoConfiguration {
    
    
}
  1. 配置
    在META-INF/spring.factories文件中添加
#Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.autoconfig.IpAutoConfiguration

6.Maven ==> clean ==> instal

在图书馆系统上模拟调用
(链接:https://pan.baidu.com/s/1gb1n2I9YRUsiK5qnvaes6w 提取码:f1zs)

  1. 添加依赖坐标
<dependency>
    <groupId>com.example</groupId>
    <artifactId>ip_spring_boot_starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>
  1. 模拟调用(非最终版)
    在BookController的分页功能上模拟调用
@Autowired
private IpCountService ipCountService;

@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){
    
    
    // TODO 追加ip访问统计
    ipCountService.count();
    IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
    //如果当前页码值大于了总页码值,那么重新执行查询操作,使用最大页码值作为当前页码值
    if( currentPage > page.getPages()){
    
    
        page = bookService.getPage((int)page.getPages(), pageSize,book);
    }
    return new R(true, page);
}

2.2.1 定时任务报表开发

  1. 开启定时任务功能
package com.example.autoconfig;

import com.example.service.IpCountService;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;

@Import(IpCountService.class)
@EnableScheduling
public class IpAutoConfiguration {
    
    
}
  1. 添加ip计数显示及设置定时任务
package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

public class IpCountService {
    
    
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();

    @Autowired
    //当前的request对象的注入工作由使用当前starter的工程提供自动装配
    private HttpServletRequest httpServletRequest;

    public void count(){
    
    
        //每次调用当前操作,就记录当前访问的IP,然后累加访问次数
        //1.获取当前操作的IP地址
        String ip = httpServletRequest.getRemoteAddr();
        //2.根据IP地址从Map取值,并递增
        Integer count = ipCountMap.get(ip);
        if(count == null){
    
    
            ipCountMap.put(ip,1);
        }else{
    
    
            ipCountMap.put(ip,ipCountMap.get(ip) + 1);
        }
    }

    @Scheduled(cron = "0/5 * * * * ?")
    public void print(){
    
    
        System.out.println("        IP访问监控");
        System.out.println("+-----ip-address-----+--num--+");
        for (Map.Entry<String,Integer> entry : ipCountMap.entrySet()){
    
    
            String key = entry.getKey();
            Integer value = entry.getValue();
            System.out.println(String.format("|%18s  |%5d  |",key,value));
        }
        System.out.println("+--------------------+-------+");
    }

    public static void main(String[] args) {
    
    
        new IpCountService().print();
    }
}
  1. 重新安装,然后在图书管理系统中测试效果

2.2.2 使用属性配置设置功能参数

  1. 定义属性类,加载对应属性
package com.example;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    
    
    /**
    * 日志显示周期
    */
    private Long cycle = 5L;

    /**
    * 是否周期内重置数据
    */
    private Boolean cycleReset = false;

    /**
    * 日志输出模式    detail:详细模式 simple:极简模式
    */
    private String model = LogModel.DETAIL.value;

    public enum LogModel{
    
    
        DETAIL("detail"),
        SIMPLE("simple");
        private String value;
        LogModel(String value){
    
    
            this.value = value;
        }

        public String getValue(){
    
    
            return value;
        }
    }

    public Long getCycle() {
    
    
        return cycle;
    }

    public void setCycle(Long cycle) {
    
    
        this.cycle = cycle;
    }

    public Boolean getCycleReset() {
    
    
        return cycleReset;
    }

    public void setCycleReset(Boolean cycleReset) {
    
    
        this.cycleReset = cycleReset;
    }

    public String getModel() {
    
    
        return model;
    }

    public void setModel(String model) {
    
    
        this.model = model;
    }
}
  1. 设置加载Properties类为bean
package com.example.autoconfig;

import com.example.IpProperties;
import com.example.service.IpCountService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;

@Import(IpCountService.class)
@EnableConfigurationProperties(IpProperties.class)
@EnableScheduling
public class IpAutoConfiguration {
    
    
}
  1. 根据配置切换设置
    @Scheduled(cron = "0/5 * * * * ?")
    public void print(){
    
    
        //模式切换
        if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){
    
    
            //明细模式
            System.out.println("        IP访问监控");
            System.out.println("+-----ip-address-----+--num--+");
            for (Map.Entry<String,Integer> entry : ipCountMap.entrySet()){
    
    
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(String.format("|%18s  |%5d  |",key,value));
            }
            System.out.println("+--------------------+-------+");
        }else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
    
    
            //极简模式
            System.out.println("        IP访问监控");
            System.out.println("+-----ip-address-----+");
            for (String key: ipCountMap.keySet()){
    
    
                System.out.println(String.format("|%18s  |",key));
            }
            System.out.println("+--------------------+");
        }

        //周期内重置数据
        if(ipProperties.getCycleReset()){
    
    
            ipCountMap.clear();
        }
    }
  1. 重新安装
    在图书管理系统中修改配置
tools:
  ip:
    cycle-reset: true
    model: simple

查看重置数据和模式切换功能是否成功

添加参数控制定时任务执行时间间隔

  1. 自定义bean名称
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    
    
    ...
}
  1. 放弃配置属性创建bean方式,改为手工控制
package com.example.autoconfig;

import com.example.IpProperties;
import com.example.service.IpCountService;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableScheduling;

@Import({
    
    IpCountService.class,IpProperties.class})
//@EnableConfigurationProperties({IpProperties.class})
@EnableScheduling
public class IpAutoConfiguration {
    
    
}
  1. 使用#{beanName.attrName}读取bean的属性
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
    
    
    ...
}
  1. 重新安装
    在图书管理系统中测试,修改cycle参数
tools:
  ip:
    cycle: 1

2.2.3 拦截器开发

  1. 自定义拦截器
package com.example.interceptor;

import com.example.service.IpCountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

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

public class IpCountInterceptor implements HandlerInterceptor {
    
    
    @Autowired
    private IpCountService ipCountService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        ipCountService.count();
        return true;
    }
}
  1. 设置核心配置类,加载拦截器
package com.example.config;

import com.example.interceptor.IpCountInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(ipCountInterceptor()).addPathPatterns("/**");
    }

    @Bean
    public IpCountInterceptor ipCountInterceptor(){
    
    
        return new IpCountInterceptor();
    }
}
  1. 重装
    在图书管理系统中测试效果
    删除BookController中追加的ip访问统计
    然后测试效果

2.3 辅助功能开发

开启yml提示功能

  1. 导入配置处理器坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
  1. Maven重新安装
    然后将target.classes.META-INF.spring-configuration-metadata.json文件拷贝到resources.META-INF下
    去掉上一步骤添加的处理器坐标

  2. 进行自定义提示功能开发
    修改spring-configuration-metadata.json中的"hints"

  "hints": [
    {
    
    
      "name": "tools.ip.model",
      "values": [
        {
    
    
          "value": "detail",
          "description": "详细模式."
        },
        {
    
    
          "value": "simple",
          "description": "极简模式."
        }
      ]
    }
  ]
  1. Maven重新安装,然后在图书管理系统中查看yml配置是否出现提示

3.核心原理

观看黑马程序员的springboot2原理篇视频p169-174,便于理解
视频网址(https://www.bilibili.com/video/BV15b4y1a7yG?p=174&spm_id_from=pageDriver&vd_source=50cc6e53411d643b31628740ae6c2b0a)

SpringBoot启动流程

  1. 初始化各种属性,加载成对象
    读取环境属性(Environment)
    系统配置(spring.factories)
    参数(Arguments、application.properties)

  2. 创建Spring容器对象ApplicationContext,加载各种配置

  3. 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求

  4. 容器初始化过程中追加各种功能,例如统计时间、输出日志等

工作流程

Springboot30StartupApplication【10】->SpringApplication.run(Springboot30StartupApplication.class, args);
    SpringApplication【1332】->return run(new Class<?>[] { primarySource }, args);
        SpringApplication【1343】->return new SpringApplication(primarySources).run(args);
            SpringApplication【1343】->SpringApplication(primarySources)
            # 加载各种配置信息,初始化各种配置对象
                SpringApplication【266】->this(null, primarySources);
                    SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
                        SpringApplication【281】->this.resourceLoader = resourceLoader;
                        # 初始化资源加载器
                        SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
                        # 初始化配置类的类名信息(格式转换)
                        SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath();
                        # 确认当前容器加载的类型
                        SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
                        # 获取系统配置引导信息
                        SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
                        # 获取ApplicationContextInitializer.class对应的实例
                        SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
                        # 初始化监听器,对初始化过程及运行过程进行干预
                        SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass();
                        # 初始化了引导类类名信息,备用
            SpringApplication【1343】->new SpringApplication(primarySources).run(args)
            # 初始化容器,得到ApplicationContext对象
                SpringApplication【323】->StopWatch stopWatch = new StopWatch();
                # 设置计时器
                SpringApplication【324】->stopWatch.start();
                # 计时开始
                SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext();
                # 系统引导信息对应的上下文对象
                SpringApplication【327】->configureHeadlessProperty();
                # 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
                    java.awt.headless=true
                SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args);
                # 获取当前注册的所有监听器
                SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass);
                # 监听器执行了对应的操作步骤
                SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
                # 获取参数
                SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
                # 将前期读取的数据加载成了一个环境对象,用来描述信息
                SpringApplication【333】->configureIgnoreBeanInfo(environment);
                # 做了一个配置,备用
                SpringApplication【334】->Banner printedBanner = printBanner(environment);
                # 初始化logo
                SpringApplication【335】->context = createApplicationContext();
                # 创建容器对象,根据前期配置的容器类型进行判定并创建
                SpringApplication【363】->context.setApplicationStartup(this.applicationStartup);
                # 设置启动模式
                SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
                # 对容器进行设置,参数来源于前期的设定
                SpringApplication【338】->refreshContext(context);
                # 刷新容器环境
                SpringApplication【339】->afterRefresh(context, applicationArguments);
                # 刷新完毕后做后处理
                SpringApplication【340】->stopWatch.stop();
                # 计时结束
                SpringApplication【341】->if (this.logStartupInfo) {
                # 判定是否记录启动时间的日志
                SpringApplication【342】->    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
                # 创建日志对应的对象,输出日志信息,包含启动时间
                SpringApplication【344】->listeners.started(context);
                # 监听器执行了对应的操作步骤
                SpringApplication【345】->callRunners(context, applicationArguments);
                # 调用运行器
                SpringApplication【353】->listeners.running(context);
                # 监听器执行了对应的操作步骤

监听器类型

  1. 在应用运行但未进行任何处理时,将发送 ApplicationStartingEvent。
  2. 当Environment被使用,且上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent。
  3. 在开始刷新之前,bean定义被加载之后发送 ApplicationPreparedEvent。
  4. 在上下文刷新之后且所有的应用和命令行运行器被调用之前发送 ApplicationStartedEvent。
  5. 在应用程序和命令行运行器被调用之后,将发出 ApplicationReadyEvent,用于通知应用已经准备处理请求。
  6. 启动时发生异常,将发送 ApplicationFailedEvent。

猜你喜欢

转载自blog.csdn.net/hutc_Alan/article/details/125236054