42.SpringBoot—原理篇

目录

一、SpringBoot原理篇。

(1)自动配置。

(1.1)bean加载方式。

(1.1.1)xml方式。(适用自定义bean与第三方bean) 

(1.1.2)注解方式+组件扫描。(适用于自定义bean)

(1.1.3)@Bean方式+将类定义为bean+组件扫描。(适用自定义bean与第三方bean) 

(1.1.4)@Import。

(1.1.5)使用上下文对象注册bean对象。

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

 (1.1.7)导入实现了ImportBeanDefinitionRegistrar接口的类。

(1.1.8)导入实现了BeanDefinitionRegistryPostProcessor接口的类。

 (1.1.9)创建上下文对象时指定配置类。(该方式等于@Import(xxx.class))

(1.1.10)FactoryBean接口(制造接口泛型类型的bean,本质@Bean声明,返回是泛型类型)。

(1.1.11)配置文件+配置类 = 搭配使用。

(1.1.12)@Configuration注解的不一样。

(1.1.13)小结。

(1.2) Bean的加载控制。

(1.2.1)bean加载控制方式。

(1.2.2)bean的加载控制(编程式)。

 (1.2.3)bean的加载控制(注解)。 

(1.3)bean依赖属性配置(自动配置类的属性配置)。

(1.4)自动配置原理。 

(1.5)变更自动配置。

(1.5.1)自定义自动配置类。

(1.5.2)排除自动配置类或依赖。 

(2)自定义starter。

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

(2.1.1)简陋版ip展示。

(2.1.2)增加定时任务、属性配置。

(2.1.3)在注解参数值中的字符串中获取bean对象属性值。

(2.1.4)添加拦截器。 

(2.3)辅助功能开发(yml提示功能开发)。

(3)核心原理。

(3.1)springboot启动流程。

(3.2)监听器类型。

(3.3)对启动流程代码解析。


一、SpringBoot原理篇。

(1)自动配置。

(1.1)bean加载方式。

(1.1.1)xml方式。(适用自定义bean与第三方bean) 

(1.1.2)注解方式+组件扫描。(适用于自定义bean)

(1.1.3)@Bean方式+将类定义为bean+组件扫描。(适用自定义bean与第三方bean) 

(1.1.4)@Import。

注意:@Import导入的bean的名称是全路径类名,其他注解只是类名(首字母小写)。 

(1.1.5)使用上下文对象注册bean对象。

注意:AnnotationConfigApplicationContext对象才有注册bean的方法,其他的对象没有。

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

@Import(MyImportSelector.class)在SpringConfig6上面,所以SpringConfig6类是MyImportSelector类的元数据。

@Configuration
//@ComponentScan(basePackages = "com.itheima")
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        System.out.println("================");
        System.out.println("提示:"+metadata.getClassName());
        System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));
        Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.ComponentScan");
        System.out.println(attributes);
        System.out.println("================");
        //各种条件的判定,判定完毕后,决定是否装在指定的bean
        boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if(flag){
            return new String[]{"com.itheima.bean.Dog"};
        }
        return new String[]{"com.itheima.bean.Cat"};
    }
}

 (1.1.7)导入实现了ImportBeanDefinitionRegistrar接口的类。

@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class SpringConfig8 {
}

(1.1.8)导入实现了BeanDefinitionRegistryPostProcessor接口的类。

理解: 等BeanDefinitionRegistry注册完所有bean之后,BeanDefinitionRegistryPostProcessor才会开始执行,如果遇到同名的则会覆盖之前的。

@Import({BookServiceImpl1.class, MyPostProcessor.class, MyRegistrar2.class, MyRegistrar.class})
public class SpringConfig8 {
}

 (1.1.9)创建上下文对象时指定配置类。(该方式等于@Import(xxx.class))

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);

提示:若创建上下文对象的时候不指定该类(即需要被组件扫描发现是配置类),则需加上@Configuration注解声明该类为配置类。

@ComponentScan({"com.itheima.bean","com.itheima.config"})
public class SpringConfig3 {

    @Bean
    public DogBean dog(){
        return new DogBean();
    }
}
public class App3 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig3.class);
        String[] names = ctx.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        System.out.println(ctx.getBean("dog"));
    }
}

(1.1.10)FactoryBean接口(制造接口泛型类型的bean,本质@Bean声明,返回是泛型类型)。

 注意:虽然返回是DogFactoryBean类型,但实际上返回的是泛型的类型。(原理暂不清楚)

public class DogFactoryBean implements FactoryBean<Dog> {
    @Override
    public Dog getObject() throws Exception {
        Dog d = new Dog();
        //.........
        return d;
    }
    @Override
    public Class<?> getObjectType() {
        return Dog.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}
@ComponentScan({"com.itheima.bean","com.itheima.config"})
public class SpringConfig3 {
    //该方法与下面的方法只能存在一个。(因为两个方法返回值都是Dog类型)
    @Bean
    public DogFactoryBean dog(){
        return new DogFactoryBean();
    }
    //这个返回的是Dog类型(Dog是接口),该方法与下面的方法只能存在一个。(因为两个方法返回值都是Dog类型)
//    @Bean
//    public Dog dog(){
//        return new Dog();
//    }

}

提示:传递的泛型是Book,即@Bean出来的实际是Book对象。 

(1.1.11)配置文件+配置类 = 搭配使用。

提示:个人觉得,跟配置文件中注解扫描该配置类,然后上下文对象中指定配置文件是一样的。

ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfig2.xml");

(1.1.12)@Configuration注解的不一样。

@Configuration默认创建的是代理对象,在获取代理对象后,调用该对象里面的方法(有@Bean标记的方法)的时候先去IOC容器中获取返回(即得到的都是同一个对象)。

注意:proxyBeanMethods属性默认是ture,如果是false,则不是代理对象。

(1.1.13)小结。

注意:@Import导入的bean的名称是全路径类名,其他注解只是类名(首字母小写)。  

提示:6、7、8这上使用@Import导入实现接口的类,实际该类不会被注册到容器里面,只会注册接口方法中定义的需要被注册到容器的bean。 

(1.2) Bean的加载控制。

(1.2.1)bean加载控制方式。

提示:能够对bean的加载进行控制的基本都是编程式的。


(1.2.2)bean的加载控制(编程式)。

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

 (1.2.3)bean的加载控制(注解)。 

@ConditionalOnClass(Mouse.class)检测是否存在这个类,其实使用类型没意义,需要使用字符串才不报错。(这是关于项目有没有该类)

@ConditionalOnBean(name = "com.itheima.bean.Mouse")bean名为mouse(使用@Component注解标记),这也满足。(这是在IOC容器中有没有该对象)

@ConditionalOnBean(name = "jerry")则名称必须要相同(使用@Component(“jerry”)标记)。

注意:注解需要导入springboot基本依赖。 

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

注意:还可以在类上就行判断是否需要加载为bean: 

@Component("tom")
@ConditionalOnBean(name = "jerry")
//@ConditionalOnWebApplication
@ConditionalOnNotWebApplication
public class Cat {
}

(1.3)bean依赖属性配置(自动配置类的属性配置)。

总结起来就是:如果配置文件中有值则使用,如果没有则使用默认值。 

@EnableConfigurationProperties(CartoonProperties.class) 注解的作用是将 CartoonProperties 类注册为一个 Spring Bean。

(1.4)自动配置原理。 

@Import(AutoConfigurationPackages.Registrar.class):设置启动类所在的包作为扫描包,后续要针对当前的包进行扫描,如果其他starter也是该包名开头,可能也会扫描。(将启动类所在的包进行添加到xxx对象中,xxx对象被注册为bean)

@Import(AutoConfigurationImportSelector.class) :将所有的自动配置类(满足条件的自动配置类)注册为bean

原理:将文件中的自动配置类的全路径名全部加载出来,然后进行判断,如果满足则加载(比如依赖中是否有redis的某个类,如果有则加载redis技术相关配置),不满足则不加载。 

该文件中定义了很多技术的类(自动配置类),其实就是下面说的技术集A。 

(1.5)变更自动配置。

(1.5.1)自定义自动配置类。

使用自定义自动配置后,不需要使用@Import(CartoonCatAndMouse.class)就能自动配置。

(1.5.2)排除自动配置类或依赖。 

(2)自定义starter。

自定义starter:做一个独立项目,有自动配置类,最后在META-INF/spring.factories文件中写上自动配置类,然后执行maven的clean,再执行install(将starter添加到本地仓库)。

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

(2.1.1)简陋版ip展示。

下面的模拟调用是导入了该自定义starter的项目,模拟调用上面的都是自定义starter里面的内容,编写好自定义starter后,需要调用maven中的生命周期执行clean,再执行install(安装到本地仓库中,方便其他需要使用该自定义starter的项目在pom中导入该自定义starter,每次修改自定义starter之后都要重写clean+install,确保仓库中是最新版)。

(2.1.2)增加定时任务、属性配置。

(2.1.3)在注解参数值中的字符串中获取bean对象属性值。

注意:如果主项目基础包是com.itheima,starter的也是com.itheima,那么会出现bean不唯一的问题。(自动配置类-第1次(自动配置类导入了该类),扫描的时候-第2次(扫描com.itheima包的时候扫到了该类)) 。

@Component("ipProperties")

@ConfigurationProperties(prefix = "tools.ip")

@EnableConfigurationProperties(IpProperties.class)

注意:这三个不能一起用,否则@Component("ipProperties")起名字就不起作用,如果使用前面两个注解,那么第三个注解可以换成@Import(IpProperties.class)

(2.1.4)添加拦截器。 

(2.3)辅助功能开发(yml提示功能开发)。

自定义starter的spring-configuration-metadata.json文件展示

{
  "groups": [
    {
      "name": "tools.ip",
      "type": "cn.itcast.properties.IpProperties",
      "sourceType": "cn.itcast.properties.IpProperties"
    }
  ],
  "properties": [
    {
      "name": "tools.ip.cycle",
      "type": "java.lang.Long",
      "description": "日志的显示周期",
      "sourceType": "cn.itcast.properties.IpProperties",
      "defaultValue": 5
    },
    {
      "name": "tools.ip.cycle-reset",
      "type": "java.lang.Boolean",
      "description": "是否周期内重置数据",
      "sourceType": "cn.itcast.properties.IpProperties",
      "defaultValue": false
    },
    {
      "name": "tools.ip.model",
      "type": "java.lang.String",
      "description": "日志输出模式 detail:详细模式  simple:极简模式",
      "sourceType": "cn.itcast.properties.IpProperties"
    }
  ],
  "hints": [
    {
      "name": "tools.ip.model",
      "value": [
        {
          "value": "detail",
          "description": "详细模式"
        },
        {
          "value": "simple",
          "description": "极简模式"
        }
      ]
    }
  ]
}

导入这个坐标之后,执行maven的clean,然后compile编译,然后可以找到target/classer/META-INF/spring-configuration-metadata.json文件,将该文件复制到自定义starter中的resources/META-INF目录下。然后就可以将pom中刚加进入的坐标删掉(否则有重复的提示,因为有两个该json文件)。

 

为tools.ip.mode属性设置提示信息:

(3)核心原理。

(3.1)springboot启动流程。

(3.2)监听器类型。

package com.itheima.listener;

import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

//public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
//
//    @Override
//    public void onApplicationEvent(ApplicationStartingEvent event) {
//        System.out.println("=================================");
//        System.out.println(event.getTimestamp());
//        System.out.println(event.getSource());
//        System.out.println(event.getClass());
//    }
//}
public class MyListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("=============================");
    }
}

(3.3)对启动流程代码解析。

提示:注释是解释上一行代码的。

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】->new 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();
                        # 确认当前容器加载的类型(确认是否是web环境,即有没有导入web依赖)
                        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);
                # 监听器执行了对应的操作步骤

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值