黑马SpringBoot --原理篇

目录

    • 15.自动配置
      • 15.1bean加载方式
        • 15.1.1bean的加载方式(一)
        • 15.1.2bean的加载方式(二)
        • 15.1.3bean的加载方式(三)
          • 15.1.3.1bean的加载方式——扩展1
          • 15.1.3.2bean的加载方式——扩展2
          • 15.1.3.3bean的加载方式——扩展3
        • 15.1.4bean的加载方式(四)
        • 15.1.5bean的加载方式(五)
        • 15.1.6bean的加载方式(六)
        • 15.1.7bean的加载方式(七)
        • 15.1.8bean的加载方式(八)
      • 15.2bean加载控制
        • 15.2.1bean加载控制方式(编程式)
        • 15.2.2bean加载控制方式(注解式)
      • 15.3bean依赖属性配置
      • 15.4自动配置原理
      • 15.5变更自动配置
    • 16.自定义starter
      • 16.1案例:统计独立IP访问次数
      • 16.2自定义starter
        • 16.2.1定时任务
        • 16.2.2定义属性类,加载对应属性
        • 16.2.3自定义bean名称
        • 16.2.4自定义拦截器
      • 16.3辅助功能开发
        • 16.3.1导入配置处理器坐标
    • 17.核心原理
      • 17.1SpringBoot启动流程
      • 17.2容器类型选择
      • 17.3监听器

15.自动配置

15.1bean加载方式

笔记小结

Bean的加载方式

  1. xml+
  2. xml:context+注解(@Component+4个@Bean)
  3. 配置类+扫描+注解(@Component+4个@Bean)
    @Bean定义FactoryBean接口
     @ImportResource
     @Configuration注解的proxyBeanMethods属性
  4. @Import导入bean的类
     @Import导入配置类
  5. AnnotationConfigApplicationContext调用register方法
  6. @Import导入ImportSelector接口
  7. @Import导入ImportBeanDefinitionRegistrar接口
  8. @Import导入BeanDefinitionRegistryPostProcessor接口
15.1.1bean的加载方式(一)

XML方式声明bean


<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">
    
    <bean id="bookService" 
          class="com.itheima.service.impl.BookServiceImpl" 
          scope="singleton"/>
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
beans>

若此时定义bean时未添加id,那么spring在获取打印时会自动添加一个标识符

获取

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml");
        //通过id方式进行获取
        Object dataSource = ctx.getBean("dataSource");
        System.out.println(dataSource);
        //通过获取类的方式进行获取
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);
        //通过获取全部bean的方式进行获取
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }

    }

ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext1.xml”);获取xml文件中的bean配置

获取bean可通过三种方式,id,类,全部

黑马SpringBoot --原理篇_第1张图片

15.1.2bean的加载方式(二)

XML+注解方式声明bean


<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.itheima"/>

添加一个命名空间,告诉spring需要去哪里寻找注解

黑马SpringBoot --原理篇_第2张图片

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

    @Component("cat")
    public class Cat {
    }
    
    @Service("dog")
    public class Dog {
    }
    
    @Controller("mouse")
    public class Mouse {
    }
    

    补充,若不在注解中指定名称,则加载时,spring会自动将类名(小写)转换为bean的名称

    例如

    @Repository
    public class Dragon {
    }
    

    此时bena的名称为dragon

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

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

    配置第三方的Bean不可能在名称上直接添加@Component等注解,因此可新建一个方法,并添加注解@Bean

    补充:此时任然可以在@Bean后添加名称,例如@Bean(“dataSource”),也可直接省略

    此处在类名称上添加@Component,推荐使用@Configuration来定义Bean。虽然@Configuration和@Component几乎没有区别,因为@Configration可以使用代理,然后可以告诉程序员,这个类是专门做配置的,所以推荐使用

    @Component为多例,@Configuration为单例

获取

public class App2 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

黑马SpringBoot --原理篇_第3张图片

15.1.3bean的加载方式(三)

注解方式声明配置类

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

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

获取

public class App3 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(DbConfig.class) {
        };
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

spring提供心的方法名获取ApplicationContext,此时新建AnnotationConfigApplicationContext(DbConfig.class)传入类名即可

补充:若使用AnnotationConfigApplicationContext方法,则加载的类会自动转换为Bean。比如现在所加载的类为DbConfig.class,则加载出来的Bean对象就包含dbConfig这个Bean

黑马SpringBoot --原理篇_第4张图片

15.1.3.1bean的加载方式——扩展1

创建Bean对象,却不返回此Bean对象,而返回其余的Bean对象

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

/*验证为此类添加Bean对象却返回别的类的Bean对象*/
public class DogFactory implements FactoryBean<Dog> {
    /*当为此方法添加Bean时,会返回dog的bean对象,而不是本身dogFactory对象*/
    @Override
    public Dog getObject() throws Exception {
        return new Dog();
    }

    /*返回此对象的类型*/
    @Override
    public Class<?> getObjectType() {
        return Dog.class;
    }

    /*返回此对象是否单例。如果是单例,则返回为同一对象,若是多例,则返回n个对象*/
    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}

当需要加载的Bean对象实现了FactoryBean方法时,再用注解添加@Bean则不会返回Bean对象本身,而是返回getObject()这个方法

public class SpringConfig31 {
    @Bean
    public DogFactory dogFactory() {
        return new DogFactory();
    }
}

获取

public class App31 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig31.class) {
        };
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

黑马SpringBoot --原理篇_第5张图片

15.1.3.2bean的加载方式——扩展2

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

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

在类名之上添加@ImportResource并配置另一个需要加载的xml配置文件名即可

获取

public class App32 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class) {
        };
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

黑马SpringBoot --原理篇_第6张图片

15.1.3.3bean的加载方式——扩展3

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

@Configuration(proxyBeanMethods = false)
public class SpringConfig33 {
    @Bean
    public Book book(){
        System.out.println("book init ...");
        return new Book();
    }
}

当类同时添加@Configuration与 @Bean注解时,可以保障调用此方法得到的对象是全新的

@Configuration的默认值proxyBeanMethods=true

获取

public class App33 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig33.class) {
        };
        SpringConfig33 ctxBean = ctx.getBean("springConfig33", SpringConfig33.class);
        System.out.println(ctxBean.book());
        System.out.println(ctxBean.book());
        System.out.println(ctxBean.book());
    }
}

获取时,需要获取Bean再调用其中Bean中的方法即可

黑马SpringBoot --原理篇_第7张图片

15.1.4bean的加载方式(四)

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

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

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

public class Mouse {
}

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

获取

public class App4 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class) {
        };
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

黑马SpringBoot --原理篇_第8张图片

使用@import注解导入的Bean会数据全路径类名

2.若使用@Import注解导入配置类

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

则被导入的bean无需使用注解声明为bean,其中的Bean方法依旧存在

public class DbConfig {

    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }
}

获取

public class App4 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class) {
        };
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

黑马SpringBoot --原理篇_第9张图片

使用@import注解导入的Bean会数据全路径类名

15.1.5bean的加载方式(五)

使用上下文对象在容器初始化完毕后注入bean,即可覆盖原有的Bean

public class App5 {
    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);
        }
    }
}

注意需要用AnnotationConfigApplicationContext创建对象,且上下文对象在容器初始化完毕后,注入即可覆盖

15.1.6bean的加载方式(六)

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

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata metadata) {
        //使用元数据去做判定
        boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
        if (flag) {
            return new String[]{"org.example.bean.Dog"};
        }
        return new String[]{"org.example.bean.Cat"};
    }
}

判断是否有@Configuration注解,若有则返回Dog的Bean,没有则返回Cat的Bean

补充:AnnotationMetadata,注解元数据。此处的注解元数据为,谁导入了这个MyImportSelector类,这里的元数据就是它

SpringConfig6

@Configuration
@Import(MyImportSelector.class)
public class SpringConfig6 {

}

获取

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

}

黑马SpringBoot --原理篇_第10张图片

15.1.7bean的加载方式(七)

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

public class MyRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata annotation metadata of the importing class,元数据
     * @param registry               current bean definition registry,bean的注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        //通过定义Bean的builder来进行创建
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
        registry.registerBeanDefinition("yellow", beanDefinition);
    }
}

将需要注册的bean添加到registry即可

SpringConfig7

@Import(MyRegistrar.class)
public class SpringConfig7 {

}

获取

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

黑马SpringBoot --原理篇_第11张图片

15.1.8bean的加载方式(八)

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

public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
    //后置处理并且定义注册
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        //通过定义Bean的builder来进行创建
        BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
        registry.registerBeanDefinition("bookService", beanDefinition);

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}
  • BookServiceImpl1

    @Service("bookService")
    public class BookServiceImpl1 implements BookService {
    
        @Override
        public void check() {
            System.out.println("book service 1.");
        }
    }
    
  • MyRegistrar

    public class MyRegistrar implements ImportBeanDefinitionRegistrar {
    
        /**
         * @param importingClassMetadata annotation metadata of the importing class,元数据
         * @param registry               current bean definition registry,bean的注册
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            //通过定义Bean的builder来进行创建
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
            registry.registerBeanDefinition("bookService", beanDefinition);
        }
    }
    
  • MyRegistrar2

    public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {
    
        /**
         * @param importingClassMetadata annotation metadata of the importing class,元数据
         * @param registry               current bean definition registry,bean的注册
         */
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            //通过定义Bean的builder来进行创建
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();
            registry.registerBeanDefinition("bookService", beanDefinition);
        }
    }
    

SpringConfig8

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

}

获取

public class App8 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);
        //因为我们知道SpringBoot8.class中有bookService,因此我们直接获取获取
        //导入bookService
        BookService bookService = ctx.getBean("bookService", BookService.class);
        //调用Bean中的方法
        bookService.check();
    }
}

在这里插入图片描述

得出结论Bean的覆盖方式是不同的优先级从大到小BeanDefinitionRegistryPostProcessor>ImportBeanDefinitionRegistrar>普通的

补充:若是用的同类的接口进行定义,则同优先级的bean在前后顺序上进行相关覆盖

15.2bean加载控制

黑马SpringBoot --原理篇_第12张图片

15.2.1bean加载控制方式(编程式)

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

//如果有老鼠这个类就加载猫
public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        try {
            Class<?> clazz = Class.forName("org.example.bean.Mouse");
            if (clazz != null) {
                return new String[]{"org.example.bean.Cat"};
            }
        } catch (ClassNotFoundException e) {
            return new String[0];
        }
        return null;
    }
}

SpringBoot

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

获取

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringBoot.class);
        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
        //对beanDefinitionNames进行迭代
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}
15.2.2bean加载控制方式(注解式)

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

  • 匹配指定类
public class SpringConfig {
    @Bean
    @ConditionalOnClass(Mouse.class)
    public Cat tom(){
        return new Cat();
    }
}

若程序中有Mouse的类就会加载cat的Bean

  • 未匹配指定类
public class SpringConfig {
    @Bean
    //@ConditionalOnClass(Mouse.class)
    @ConditionalOnClass(name="Mouse.class")
    @ConditionalOnMissingClass("com.itheima.bean.Wolf")
    public Cat tom(){
        return new Cat();
    }
}

若程序中有Mouse的类,并且不存在此com.itheima.bean包下的Wolf类就就会加载cat的Bean

补充:@ConditionalOnClass写法不同

  • 匹配指定类型的bean
@Import(Mouse.class)
public class SpringConfig {
    @Bean
    @ConditionalOnBean(Mouse.class)
    public Cat tom(){
        return new Cat();
    }
}

若程序中存在Mouse的Bean则加载类

补充:@Import(Mouse.class)的功能是导入全路径类名的Bean

  • 匹配指定名称的bean
@Import(Mouse.class)
public class SpringConfig {
    @Bean
    @ConditionalOnBean(name="com.itheima.bean.Mouse")
    public Cat tom(){
        return new Cat();
    }
}

@ConditionalOnBean写法可以变为name的形式

  • 匹配指定名称的bean
@Import(Mouse.class)
public class SpringConfig {
    @Bean
    @ConditionalOnBean(name="jerry")
    public Cat tom(){
        return new Cat();
    }
}

@ConditionalOnBean写法可以取别名

  • 匹配指定环境
@Configuration
@Import(Mouse.class)
public class MyConfig {
    @Bean
    @ConditionalOnClass(Mouse.class)
    @ConditionalOnMissingClass("com.itheima.bean.Dog")
    @ConditionalOnNotWebApplication
    public Cat tom(){
        return new Cat();
    }
}

@ConditionalOnNotWebApplication指定若当前环境不是web环境则不加载Bean

@ConditionalOnNotWebApplication指定若当前环境是web环境则加载Bean

案例:

当指定了有mysql数据库的驱动时,则加载drudi的Bean

public class SpringConfig {
    @Bean
    @ConditionalOnClass(name = "com.mysql.jdbc.Driver")
    public DruidDataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        return ds;
    }
}

15.3bean依赖属性配置

笔记小结

  1. 业务bean的属性可以为其设定默认值
  2. 当需要设置时通过配置文件传递属性
  3. 业务bean应尽量避免设置强制加载,而是根据需要导入后加载,降 低spring容器管理bean的强度

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

@ConfigurationProperties(prefix = "cartoon")
public class CartoonProperties {
    private Cat cat;
    private Mouse mouse;
    public Cat getCat() {
        return cat;
    }
    public void setCat(Cat cat) {
        this.cat = cat;
    }
    public Mouse getMouse() {
        return mouse;
    }
    public void setMouse(Mouse mouse) {
        this.mouse = mouse;
    }
}

需要使用@EnableConfigurationProperties注解读取yml文件终端配置

补充:get或者set方法,可以用@Data注解来代替

配置文件中使用固定格式为属性类注入数据

cartoon:
    cat:
        name: "图多盖洛"
        age: 5
    mouse:
        name: "泰菲"
        age: 1

定义业务功能bean,通常使用@Import导入,解耦强制加载bean

@Component
@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");
        
        public void play(){
            System.out.println(cat.getAge()+"岁的"+cat.getName()+
                               "与"+mouse.getAge()+"岁的"+mouse.getName()+"打起来了");
        }
    }

补充:使用@EnableConfigurationProperties(CartoonProperties.class)可以强制将所导入的类纳入springboot容器的管理

运行程序时使用@Import注解

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

运行bean中的方法

补充:此时用@ComponentScan(“com.example.bean”)加载扫描Bean是扫描不出来的,因为代码中并没有添加任何一个变为Bean所管理的注解等。

当使用@Import注解时,就会将CartoonCatAndMouse强制转换为全类名的Bean,依次springboot当使用时就可以对Bean进行管理,不使用时则不加载Bean,可以达到减少Springboot管理的效果

15.4自动配置原理

  1. 收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表——>(技术集A)
  2. 收集常用技术(技术集A)的使用参数,整理开发过程中每个技术的常用设置列表——>(设置集B)
  3. 初始化SpringBoot基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
  4. 将技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载
  5. 将技术集A中具有使用条件的技术约定出来,设置成按条件加载,由开发者决定是否使用该技术(与初始化环境比对 )
  6. 将设置集B作为默认配置加载(约定大于配置),减少开发者配置工作量
  7. 开放设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置

自动装配中,会导入两个核心类

@Import(AutoConfigurationPackages.Registrar.class) //设置当前所在的配置包为扫描包,后续要针对当前的包进行扫描
@Import(AutoConfigurationImportSelector.class) //将技术集A中的配置,进行加载

@Import(AutoConfigurationPackages.Registrar.class)

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

Registrar中的此方法可以加载当包中的Bean,并进行创建

@Import(AutoConfigurationImportSelector.class)

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
            ……
        }

此包中实现的方法可以分为三类。第一类:DeferredImportSelector,第二类: BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware,第三类:Ordered

第一类,延迟导入,也就是最后再导入技术集A中的常用技术

public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
}

黑马SpringBoot --原理篇_第13张图片

导入实现的是技术集A的配置列表便于设置集B来进行设置(实现了第5点)

在导入的META-INF/spring.factories里的META-INF中,拿redis来举例

@Configuration 设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置 (proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
}

​ @ConditionalOnClass代表若添加了RedisOperations.class配置,则会自动加载此类,达到了设置集B来进行的自动配置,若yml文件内没有配置,则启用技术集A中的配置(实现了第7点)

第二类,以Aware(发现,发觉)结尾的接口,所实现的方法都 长一个样。例如,现在是以ApplicationContextAware的接口,它一定会有个方法叫你setApplicationContext。当任何一个SpringBoot环境中的Bean实现了一个接口后,就可在当前Bean中所在类里去使用这个对象

第三类,为导入的Bean进行排序。例如,现在加载的Bean需要依赖前面所管理的Bean,有先后顺序,则需要设置。

15.5变更自动配置

笔记小小结

  1. 通过配置文件exclude属性排除自动配置
  2. 通过注解@EnableAutoConfiguration属性排除自动配置项
  3. 启用自动配置只需要满足自动配置条件即可
  4. 可以根据需求开发自定义自动配置项

自定义自动配置(META-INF/spring.factories)

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.bean.CartoonCatAndMouse

可以将需要自动配置的东西放入这个文件内,这样当springboot启动时就无需对springboot进行相关的配置

控制SpringBoot内置自动配置类加载

spring:
   autoconfigure:
     exclude: 
      - org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
      - org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration 
@EnableAutoConfiguration(excludeName = "",exclude = {})

可在添加注解的时候使用属性排除。

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

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        
        <exclusions>
            <exclusion>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-tomcatartifactId>
            exclusion>
        exclusions>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jettyartifactId>
    dependency>
dependencies>

若是tomcat的变更配置,需要使用exclusions来进行内核的排除,并指定新的内核。不可使用配置exclude的方式

16.自定义starter

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

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

记录系统访客独立IP访问次数(需求分析

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

  2. 功能触发位置:每次web请求(拦截器)

    ① 步骤一:降低难度,主动调用,仅统计单一操作访问次数(例如查询)

    ② 步骤二:开发拦截器

  3. 业务参数(配置项)

    ① 输出频度,默认10秒

    ② 数据特征:累计数据 / 阶段数据,默认累计数据

    ③ 输出格式:详细模式 / 极简模式

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

第一步:业务功能开发

public class IpCountService {
    //计数集合
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
    @Autowired
    private HttpServletRequest request;
    public void count(){
        String ipAddress = request.getRemoteAddr();
        if(ipCountMap.containsKey(ipAddress)){
            ipCountMap.put(ipAddress,ipCountMap.get(ipAddress) + 1);
        }else{
            ipCountMap.put(ipAddress,1);
        }
        System.out.println("IP:"+ipAddress);
    }
}

第二步:自动配置类

/*自动配置无需配置@Configuration注解,而改为通过配置文件的方式进行*/
public class IpAutoConfiguration {

    @Bean
    public IpCountService ipConfigService() {
        return new IpCountService();
    }
}

第三步:配置(META-INF/spring.factories)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.ip.autoconfigure.IpAutoConfiguration

模拟调用(非最终版)

@RestController
@RequestMapping("/books")
public class 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);
    }
}
  1. 使用自动配置加载业务功能
  2. 切记使用之前先clean后install安装到maven仓库,确保资源更新

因为maven中需要进行展示

     
        <dependency>
            <groupId>com.examplegroupId>
            <artifactId>ip_spring_boot_startartifactId>
            <version>0.0.1-SNAPSHOTversion>
        dependency>

16.2自定义starter

  1. 完成业务功能定时显示报表
  2. String.format()
16.2.1定时任务

开启定时任务功能

@EnableScheduling
public class IpAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

设置定时任务

public class IpCountService {
    //计数集合
    private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
    @Scheduled(cron = "0/10 * * * * ?")
    public void print(){
        System.out.println(" IP访问监控");
        System.out.println("+-----ip-address-----+--num--+");
        for(Map.Entry<String,Integer> info :ipCountMap.entrySet()){
            String key = info.getKey();
            Integer count = info.getValue();
            String lineInfo = String.format("|%18s |%6d |",key,count);
            System.out.println(lineInfo);
        }
        System.out.println("+--------------------+-------+");
    }
}

@Scheduled(cron = “0/10 * * * * ?”)开启定时器

ipCountMap.entrySet 拿到map的key和value集合

16.2.2定义属性类,加载对应属性
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
    /** 日志显示周期 */
    private long cycle = 10L;
    /** 是否周期内重置数据 */
    private Boolean cycleReset = false;
    /** 日志输出模式 detail:明细模式 simple:极简模式 */
    private String model = LogModel.DETAIL.value;
    public enum LogModel {
        DETAIL("detail"),
        SIMPLE("simple");
        private String value;
        private LogModel(String value) { this.value = value; }
        public String getValue() { return value; }
    }
}

枚举 方法

使用@ConfigurationProperties(prefix = “tools.ip”)注解前,需要加载将此类加载为Bean

设置加载Properties类为bean

//使得IpProperties.class注册为Bean//使得IpProperties.class注册为Bean
@EnableConfigurationProperties(IpProperties.class)
//开启定时任务
@EnableScheduling
public class IpAutoConfiguration {
    @Bean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

补充:@EnableConfigurationProperties(IpProperties.class)配置注解可以使得IpProperties.class变为Bean

根据配置切换设置

public class IpCountService {
    @Autowired
    private IpProperties ipProperties;
    @Scheduled(cron = "0/10 * * * * ?")
    public void print(){
        //模式切换
        if(ipProperties.getMode().equals(IpProperties.LogModel.DETAIL.getValue())){
            //明细模式
            System.out.println(" IP访问监控");
            System.out.println("+-----ip-address-----+--num--+");
            for(Map.Entry<String,Integer> info :ipCountMap.entrySet()){
                String lineInfo = String.format("|%18s |%6d |", info.getKey(), info.getValue());
                System.out.println(lineInfo);
            }
            System.out.println("+--------------------+-------+");

        }else if(ipProperties.getMode().equals(IpProperties.LogModel.SIMPLE.getValue())){
            //极简模式
            System.out.println(" IP访问监控");
            System.out.println("+-----ip-address-----+");
            for(Map.Entry<String,Integer> info :ipCountMap.entrySet()){
                String lineInfo = String.format("|%18s |", info.getKey());
                System.out.println(lineInfo);
            }
            System.out.println("+--------------------+");

        }
        //周期内重置数据
        if(ipProperties.getCycleReset()){
            ipCountMap.clear();
        }
    }
}

配置信息

#tools:
#  ip:
#    cycle: 10
#    cycleRest: false
#    model: "detail"
16.2.3自定义bean名称
@Component("ipProperties")
@ConfigurationProperties(prefix = "tools.ip")
public class IpProperties {
}

放弃配置属性创建bean方式,改为手工控制

//@EnableConfigurationProperties(IpProperties.class)
@EnableScheduling
@Import(IpProperties.class)
public class IpAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public IpCountService ipCountService(){
        return new IpCountService();
    }
}

注意:需要放弃掉@EnableConfigurationProperties(IpProperties.class)注解加载Bean的方式,而换为@Import(IpProperties.class),因为@EnableConfigurationProperties注解会将@Component(“ipProperties”)注解变为全类名导入名称的Bean,而并不是ipProperties名称的Bean

使用#{beanName.attrName}读取bean的属性

@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
}
16.2.4自定义拦截器
public class IpInterceptor implements HandlerInterceptor {
    
    @Autowired
    IpCountService countService;

    /**
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  chosen handler to execute, for type and/or instance evaluation
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        countService.count();
        countService.print();
        return true;
    }
}

拦截器,注册之后需要加载拦截器,才可以进行核心配置类的设定

重写preHandle方法,在请求发生前执行

重写postHandle方法,在请求发生后执行

重写afterCompletion方法,在整个请求完成之后,也就是DispatcherServlet渲染了视图执行

设置核心配置类,加载拦截器

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Bean
    public IpInterceptor interceptor() {
        return new IpInterceptor();
    }


    /**
     * 1.使用addInterceptor的方法进行添加时,需要加上@Configuration注解
     * 2.对添加的所有路径进行拦截需要用到 addPathPatterns 方法
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor()).addPathPatterns("/**");
    }
}

注意:自定义拦截器的功能应当添加到功能层内,而不应当方法在整个项目中

黑马SpringBoot --原理篇_第14张图片

16.3辅助功能开发

16.3.1导入配置处理器坐标
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-configuration-processorartifactId>
    <optional>trueoptional>
dependency>

将生成在target的META-INF文件中的json文件复制在项目目录中,并删除源文件,否则会生成两套提示内容

黑马SpringBoot --原理篇_第15张图片

进行自定义提示功能开发

"hints": [
    {
        "name": "tools.ip.mode",
        "values": [
            {
                "value": "detail",
                "description": "明细模式."
            },
            {
                "value": "simple",
                "description": "极简模式."
            }
        ]
    }
]

在json文件的hints中可以生成yml的提示信息

17.核心原理

17.1SpringBoot启动流程

  1. 初始化各种属性,加载成对象
    • 读取环境属性(Environment)
    • 系统配置(spring.factories)
    • 参数(Arguments、application.properties)
  2. 创建Spring容器对象ApplicationContext,加载各种配置
  3. 在容器创建前,通过监听器机制,应对不同阶段加载数据、更新数据的需求
  4. 容器初始化过程中追加各种功能,例如统计时间、输出日志等

17.2容器类型选择

工作流程.txt

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);
                # 监听器执行了对应的操作步骤

17.3监听器

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

你可能感兴趣的:(Java,spring,boot,java,spring)