当前位置: 首页 > article >正文

spring-ioc-基础知识

基础篇不涉及后置处理器,BeanDefinition等和 Spring 加载原理相关的知识,重点在充分应用 Spring 提供的 ioc 的特性 -> 怎么创建一个好用的bean

引入

ioc 是怎么来的

接口->实现类需求变更  -> 使用静态工厂来创建

当缺少实现类编译无法通过 -> 使用反射读取类的全限定名,延迟加载时间

全限定名写死了 -> 外部化配置,把beanName和全限定名存储在配置文件中,静态工厂根据配置文件去读取

每次获取类都是创建一个新的 -> 使用缓存把创建出出来的对象存储起来

入门

使用 Spring根据xml配置文件来创建一个对象,并且完成属性的注入

依赖查找

我们用 ClassPathXmlApplicationContext这个BeanFactory的实现来创建Spring的容器,然后指定加载的bean的xml配置文件,然后根据配置文件中bean的id或者类型来拿到我们需要装配出来的bean对象

<bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"></bean>
public static void main(String[] args) throws Exception {
    BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");
    Person person = (Person) factory.getBean("person");
    System.out.println(person);
}
  1. 通过bean的id查找 getBean(String name),需要强转
  2. 通过类型查找 getBean(Class<T> requiredType),不需要强转(已给定类型)

依赖注入

我们上一个例子中创建出来的 bean 是没有属性的空白bean,我们想创建出来有给定初始值的bean

我们先来看看一个基础类型的赋值(注入)

public class Person {
    private String name;
    private Integer age;
    // getter and setter ......
}
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
    <property name="name" value="test-person-byset"/>
    <property name="age" value="18"/>
</bean>

我们再来一个复杂类型,比如要注入我们刚才装配好的Person

public class Cat {
    private String name;
    private Person master;
    // getter and setter ......
}
<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
  <property name="name" value="test-cat"/>
  <property name="master" ref="person"/>
</bean>

:::info
基础类型 <property name="name" value="xxx"/>

引用类型 <property name="name" ref="xxx"/>

:::

ioc基础

依赖查找高级 & ApplicationContext 与 BeanFactory

根据接口类型获取所有实现bean

我们一个接口有多个实现类

想拿到一个接口的所有实现类或者bean???

我们这里要改用 ApplicationContext接口了,BeanFactory下并没有这种方法

如何拿到一个接口的全部实现类 -> ApplicationContext.getBeansOfType()

拿到全部bean -> ApplicationContext.getBeanDefinitionNames()

根据被注解标注的类

@Color
public class Black {
    
}

Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);

ApplicationContext.Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> var1)

获取容器中所有 bean

String[] beanNames = ctx.getBeanDefinitionNames();

延迟查找(缺省加载)

需要依赖容器中某些bean,但是这些bean不存在的时候也要使用默认/缺省的逻辑来处理

// 判断是否存在 bean
ctx.containsBean("dog")

// 下面的代码会报Bean没有定义 NoSuchBeanDefinitionException
// Dog dog = ctx.getBean(Dog.class);
    
// 这一行代码不会报错
// 只有真正调用 dogProvider.getObject 真正获取包装里面的bean才会报错
ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);

// 存在则返回,不存在返回null
Dog dog = dogProvider.getIfAvailable();
if (dog == null) {
    dog = new Dog();
}

// java8 后函数式编程
// 给了一个找不到则返回默认的方法
Dog dog = dogProvider.getIfAvailable(Dog::new);
// 更多的也可以在存在之后执行一个 Consumer 方法(单个输入参数->默认传入当前对象,无输出参数)
dogProvider.ifAvailable(dog -> System.out.println(dog));

ApplicationContextBeanFactory

ApplicationContextBeanFactory的子接口,扩展了一些其他功能

  • AOP 的支持( AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
  • 配置元信息( BeanDefinition 、Environment 、注解等 )
  • 资源管理( Resource 抽象 )
  • 事件驱动机制( ApplicationEvent 、ApplicationListener )
  • 消息与国际化( LocaleResolver )
  • Environment 抽象( SpringFramework 3.1 以后)

注解驱动Ioc和组件扫描

xml驱动的ioc使用的是ClassPathXmlApplicationContext,注解驱动的ioc需要使用AnnotationConfigApplicationContext

配置类(对应之前的xml)如何编写

@Configuration
public class QuickstartConfiguration {
    @Bean
    public Person person() {
        Person person = new Person();
        person.setName("person");
        person.setAge(123);
        return person;
    }
    @Bean
    public Cat cat() {
        Cat cat = new Cat();
        cat.setName("test-cat-anno");
        // 直接拿上面的person()方法作为返回值即可,相当于ref
        cat.setMaster(person());
        return cat;
    }
}
  • 配置类使用 @Configuration 标注
  • 每一个需要创建出来的bean,使用一个 @Bean 注解标注的方法,方法返回值对应需要创建出来Bean的类型,默认方法名是bean的id(也可以在 @Bean 注解中再次指定)
  • 方法体中可以具体去实现bean的创建逻辑包括依赖注入
  • 复杂类型注入使用方法的返回值

组件扫描

除了使用 @Bean 来手动指定 Bean 的注册以外,还可以使用组件扫描来根据类路径批量加载 Bean

@Configuration
@ComponentScan("com.linkedbear.spring.annotation.c_scan")
public class ComponentScanConfiguration {
    
}

// package com.linkedbear.spring.annotation.c_scan.bean;
@Component
public class Cat {
    private final String name;
    private final Person master;

    @Autowired // spring 4.3 后可省略
    public Cat(Person master, @Value("test-cat") String name) {
        this.master = master;
        this.name = name;
    }
}

组件扫描 @ComponentScan -> 扫描所有 @Componnet 及其派生注解(比如@Service …)的类

组件扫描的方式的依赖注入有多种方式,我们目前使用构造器注入

简单类型使用 @Value,复杂类型,默认按照类型注入,如果存在多个@Qualifier指定bean名称

依赖注入->属性注入

之前的例子中,我们简单的详细的介绍了bean的创建,但是对于依赖注入讲的还是比较简单的,接下来我们详细的介绍属性注入的方式(影响注入时机)

setter 属性注入

无参构造器 + set 方法

xml -> 之前用的最多的方式

<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
    <property name="name" value="test-person-byset"/>
    <property name="age" value="18"/>
</bean>

注解

@Bean
public Person person() {
    Person person = new Person();
    person.setName("test-person-anno-byset");
    person.setAge(18);
    return person;
}

构造器注入

需要添加全参构造器

xml

<bean id="person" class="com.linkedbear.spring.basic_di.b_constructor.bean.Person">
    <constructor-arg index="0" value="test-person-byconstructor"/>
    <constructor-arg index="1" value="18"/>
</bean>

注解

@Bean
public Person person() {
    return new Person("test-person-anno-byconstructor", 18);
}

组件扫描简单类型注入

@Value 简单属性注入

@Value("0")
private Integer order;

我们在 @Value 中引用的值不仅可以来自与常量,还可以使用到配置文件中的值,或者使用 SpEL 表达式来引用更加复杂的情况(其他bean的值或者计算属性)

PropertySource配置文件注入

@PropertySource({properties配置文件的位置}) 可引入配置文件中的kv

// properties 文件
// red.name=red-value-byproperties

@PropertySource("classpath:basic_di/value/red.properties")
public class InjectValueConfiguration {
    @Value("${red.name}")
    private String name;
}

properties 文件(先转换成一个一个map)等配置项信息会读取到 Environment 对象中

补充-> SpEL表达式

在注入时可以使用 #{} 来引用其他bean的属性值,或者使用复杂的计算属性

自动注入机制&复杂类型注入

在使用组件扫描的时候,如果要注入复杂类型怎么办呢,这里就涉及到自动注入的机制了,本小节来单独介绍,当然,自动注入机制在注解驱动开发中也有其他的应用场景

@Autowired

注入的三个位置

@Component
public class Dog {
    // 属性上标注
    @Autowired
    private Person person;

    // 构造器上标注
    @Autowired
    public Dog(Person person) {
        this.person = person;
    }

    // setter 方法上标注
    @Autowired
    public void setPerson(Person person) {
        this.person = person;
    }
}

注入的bean不存在

注入的bean不存在->报错 NoSuchBeanDefinitionException->标注 @Autowired(required = false) 可以避免->只是一种异常处理策略,与延迟注入做区分,因为无法延迟使用注入对象的时机,后面延迟注入会详细说明

配置类中使用自动注入机制

对应前面 注解驱动,直接调用 @Bean 方法标注的方法

@Configuration
public class InjectConfiguration {
    
    @Bean
    @Autowired // 可不标注
    public Dog dog(Person person) {
        Dog dog = new Dog();
        dog.setPerson(person);
        return dog;
    }
}

解决多个相同类型的bean注入冲突

多个相同类型的bean注入->报错 NoUniqueBeanDefinitionException
->标注 @Qualifier("beanName") 可以指定

@Autowired
@Qualifier("cat")
private Animal animal;

->标注 @Primary 可以指定优先级

@Primary // 指定同类型被默认注入的bean
@Bean
public Animal cat() {
    return new Cat();
}

->使用不同的变量名

@Autowired
private Animal cat;
@Autowired
private Animal dog;

@Autowired 注入逻辑分析

  1. 优先按照类型查找
  2. 如果存在多个相同类型的bean,再按照名称查找
  3. 如果存在多个相同类型的bean,且没有指定名称,报错

注入多个相同类型的bean

使用 List 或者 Set 注入

@Autowired
private List<Animal> animals; // 会注入所有类型为Animal的bean

JSR250-@Resouce 注入

@Resouce 注入 -> 根据名称注入 -> @Autowired + @Qualifier -> JSR250规范

JSR330-@Inject 注入

需要额外导入依赖

<!-- jsr330 -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

@Injecct 注入 -> 根据类型注入 -> @Autowired -> JSR330规范 ( 需要导jar包 ) 不依赖spring

补充介绍-复杂类型注入

数组集合等类型,如何注入呢,这里不涉及到新的核心机制,仅仅是语法介绍

private String[] names;
private List<String> tels;
private Set<Cat> cats;
private Map<String, Object> events;
private Properties props;

xml 复杂类型注入

<property name="names">
    <array>
        <value>张三</value>
        <value>三三来迟</value>
    </array>
</property>

<property name="tels">
    <list>
    <value>13888</value>
    <value>15999</value>
    </list>
</property>

<property name="cats">
    <set>
        <bean class="com.linkedbear.spring.basic_di.g_complexfield.bean.Cat"/>
        <ref bean="mimi"/>
    </set>
</property>
<property name="events">

<map>
    <entry key="8:00" value="起床"/>
    <!-- 撸猫 -->
    <entry key="9:00" value-ref="mimi"/>
    <!-- 买猫 -->
    <entry key="14:00">
        <bean class="com.linkedbear.spring.basic_di.g_complexfield.bean.Cat"/>
    </entry>
    <entry key="18:00" value="睡觉"/>
</map>
</property>
<property name="props">
<props>
    <prop key="sex"></prop>
    <prop key="age">18</prop>
</props>
</property>

注解

SpEL 表达式处理

@Component
public class Person2 {

    @Value("#{new String[] {'张三', '张仨'}}")
    private String[] names;

    @Value("#{{'333333', '3333', '33'}}")
    private List<String> tels;

    // 引用现有的Bean,以及创建新的Bean
    @Value("#{{@miaomiao, new com.linkedbear.spring.basic_di.g_complexfield.bean.Cat()}}")
    private Set<Cat> cats;

    @Value("#{{'喵喵': @miaomiao.name, '猫猫': new com.linkedbear.spring.basic_di.g_complexfield.bean.Cat().name}}")
    private Map<String, Object> events;

    @Value("#{{'123': '牵着手', '456': '抬起头', '789': '我们私奔到月球'}}")
    private Properties props;
}

如果是 @Bean 的方式,手动 创建出需要的集合类型,set 进去就可以了

回调注入机制&延迟注入

核心 -> Aware 接口

接口名用途
BeanFactoryAware回调注入 BeanFactory
ApplicationContextAware回调注入 ApplicationContext(与上面不同,后续 IOC 高级讲解)
EnvironmentAware回调注入 Environment(后续IOC高级讲解)
ApplicationEventPublisherAware回调注入事件发布器
ResourceLoaderAware回调注入资源加载器(xml驱动可用)
BeanClassLoaderAware回调注入加载当前 Bean 的 ClassLoader
BeanNameAware回调注入当前 Bean 的名称

@Autowired 也可以实现除去最后两个bean注入以外的全部,因为最后两个bean和当前bean有关

@Component
public class AwaredTestBean implements ApplicationContextAware, BeanNameAware {
    
    private String beanName;
    private ApplicationContext ctx;
    
    public String getName() {
        return beanName;
    }
    
    public void printBeanNames() {
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
    }
    
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.ctx = ctx;
    }
    
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

在bean初始化的时候会把对应的对象注入进去(注入到重写的setApplicationContext,和setBeanName形参中,然后我们就可以在这个函数中执行我们手动赋值的操作,这个函数会在bean生命周期中自动调用)

补充-延迟注入

改为注入 ObjectProvider

setter

@Component
public class Dog {

    private Person person;

    @Autowired
    public void setPerson(ObjectProvider<Person> person) {
        // 有Bean才取出,注入
        this.person = person.getIfAvailable();
    }
}

构造器

@Component
public class Dog {

    private Person person;

    @Autowired
    public Dog(ObjectProvider<Person> person) {
        // 如果没有Bean,则采用缺省策略创建
        this.person = person.getIfAvailable(Person::new);
    }
}

属性注入 -> 不能注入确定类型,只能注入 ObjectProvider<需要的bean类型>,这个成员变量用的时候就需要get一次,因为类型不是确定的,一般不使用

    @Autowired
    private ObjectProvider<Person> person;
    
    @Override
    public String toString() {
        // 每用一次都要getIfAvailable一次
        return "Dog{" + "person=" + person.getIfAvailable(Person::new) + '}';
    }

补充-依赖注入QA

q1 setter 注入如何体现可变性? -> 相比如构造器注入,在bean创建的时候就指定好了,setter注入可以在bean创建后再注入,就算是存在了bean,也可以再次注入

q2 参数注入 发生时机?-> 构造器注入 > 参数注入 > setter 注入(setter 注入中也可能会用到参数注入的一些值)

q3 字段注入如果发生在setter注入之前,那么如果一个beanA依赖另一个beanB,beanA中字段注入beanB,但是 beanB使用setter注入的,beanA中还能拿到beanB中setter注入的值么?

spring 先会完成所有无依赖的bean的实例化,然后再去实例化有依赖的bean,所以 beanA中字段注入beanB,beanB使用setter注入的,beanA中拿到的是beanB的实例化对象,而不是setter注入的对象
进一步的,我们所说的这三种方式的顺序是针对某一个bean的实例化来说的,对于不同bean的实例化,会按照bean之间的依赖关系进行

q3-plus 引申->循环依赖问题,beanA 初始化依赖 beanB,但beanB初始化又依赖beanA
构造器注入无法解决
但是 setter 注入和字段注入,使用单例模式的时候,spring 通过提前曝光来处理, beanB setter 执行之前,会先拿到 beanA 的一个引用
beanA字段注入拿到beanB的引用,最后 beanA 拿到 beanB 的 setter 注入完成后的新值。spring会保证bean在被使用之前是被完全初始化的。

bean 的类型

分为普通bean和FactoryBean

工厂bean FactoryBean -> 用来创建一些初始化流程比较复杂的bean

public interface FactoryBean<T> {
    // 返回创建的对象
    @Nullable
    T getObject() throws Exception;

    // 返回创建的对象的类型(即泛型类型)
    @Nullable
    Class<?> getObjectType();

    // 创建的对象是单实例Bean还是原型Bean,默认单实例
    default boolean isSingleton() {
        return true;
    }
}

我们创建一个工厂bean,用于指定某一个类型的bean的生产逻辑,后续就可以直接从spring容器中按照我们顶底好的生产逻辑,直接拿到我们想要的类型的bean了

public class ToyFactoryBean implements FactoryBean<Toy> {
    
    public ToyFactoryBean() {
        System.out.println("ToyFactoryBean 初始化了。。。");
    }
    
    private Child child;
    
    @Override
    public Toy getObject() throws Exception {
        switch (child.getWantToy()) {
            case "ball":
                return new Ball("ball");
            case "car":
                return new Car("car");
            default:
                return null;
        }
    }
    
    @Override
    public Class<Toy> getObjectType() {
        return Toy.class;
    }
    
    public void setChild(Child child) {
        this.child = child;
    }
}

@Configuration
public class BeanTypeConfiguration {
    
    @Bean
    public Child child() {
        return new Child();
    }
    
    @Bean
    public ToyFactoryBean toyFactory() {
        ToyFactoryBean toyFactory = new ToyFactoryBean();
        /// 给工厂bean注入属性
        toyFactory.setChild(child());
        return toyFactory;
    }
}

public class BeanTypeAnnoApplication {
    
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(BeanTypeConfiguration.class);

        // 可以直接拿目标生产的bean,工厂bean本身对用户是不可见的
        Map<String, Toy> toys = ctx.getBeansOfType(Toy.class);
        toys.forEach((name, toy) -> {
            System.out.println("toy name : " + name + ", " + toy.toString());
        });

        // 测试工厂bean创建出来bean的作用域
        Toy toy1 = ctx.getBean(Toy.class);
        Toy toy2 = ctx.getBean(Toy.class);
        System.out.println(toy1 == toy2);
    
        ToyFactoryBean factoryBean = ctx.getBean(ToyFactoryBean.class);
        System.out.println(factoryBean);
        System.out.println(ctx.getBean("toyFactory"));
        // 拿工厂bean本身需要加 &
        System.out.println(ctx.getBean("&toyFactory"));
    }
}
  • FactoryBean 加载时机 -> 伴随 ApplicationContext 被加载
  • FactoryBean 创建bean的时机 -> 延迟生产,等到getBean的时候再去加载
  • 生产出来的bean是否单例 -> 默认单例,可重写 isSingleton() 来改变生产的bean的作用域
  • 可通过 & 取出 BeanFactory 本体

bean 的作用域

单例->默认->每次获取的或者注入的都是同一个对象->ApplicationContext 被初始化就会被创建

原型bean->指定 @Scope(“prototype”)->每次获取就会创建一个新的对象->延迟了bean创建的时机

bean 实例化方式

实例化即指 new 出来bean 对象,之前介绍了前两种bean实例化出来的方式,这一节补充介绍静态工厂和实例工厂的方式,作为了解

普通bean

普通的 bean 标签 ,@Bean 注解标注的方式

FactoryBean方式

上一小节介绍过,注意一点 FactoryBean 本身也会作为一个特殊的bean注册到spring容器

静态工厂

这里初始化对象的方法是静态的

public class CarStaticFactory {
    public static Car getCar() {
        return new Car();
    }
}

xml 中指定工厂类,以及静态初始化方法即可,自动根据静态初始化方法判断生产出来bean的类型

<bean id="car2" class="com.linkedbear.spring.bean.c_instantiate.bean.CarStaticFactory" factory-method="getCar"/>

注解驱动创建的写法

@Bean
public Car car2() {
    return CarStaticFactory.getCar();
}

静态工厂不会注册到spring容器,只是需要用到这一点静态方法的逻辑

实例工厂

创建bean的方式不是静态的

public class CarInstanceFactory {
    public Car getCar() {
        return new Car();
    }
}
<bean id="carInstanceFactory" class="com.linkedbear.spring.bean.c_instantiate.bean.CarInstanceFactory"/>
<bean id="car3" factory-bean="carInstanceFactory" factory-method="getCar"/>

需要先把实例工厂类注册到spring容器,再去指定这个bean是实例工厂bean,并且这个实例工厂bean的实例化方法

注解驱动写法

@Bean
public Car car3(CarInstanceFactory carInstanceFactory) {
    return carInstanceFactory.getCar();
}

先注册工厂,再用工厂指定创建bean逻辑

bean 的生命周期

spring 可以在bean对象初始化完成以后,以及销毁之前执行一些自定义逻辑 ->spring 在初始化和销毁阶段会使用回调机制干预bean的生命周期

创建/实例化 new Person() ->
初始化 对 person 对象做一些初始化操作 ->
运行使用期 ->
销毁阶段 bean 的销毁 对象销毁前做一些特殊操作 ->
回收阶段

三种实现机制

  1. init-method&destroy-method

xml

<bean class="com.linkedbear.spring.lifecycle.a_initmethod.bean.Cat"
      init-method="init" destroy-method="destroy">
    <property name="name" value="mimi"/>
</bean>

注解驱动

@Bean(initMethod = "init", destroyMethod = "destroy")
public Dog dog() {
    Dog dog = new Dog();
    dog.setName("wangwang");
    return dog;
}
  1. @PostConstruct&@PreDestroy JSR250规范 -> 组件扫描
@PostConstruct
public void addInk() {
    System.out.println("钢笔中已加满墨水。。。");
    this.ink = 100;
}
    
@PreDestroy
public void outwellInk() {
    System.out.println("钢笔中的墨水都放干净了。。。");
    this.ink = 0;
}
  1. InitializingBean&DisposableBean 接口
@Component
public class Pen implements InitializingBean, DisposableBean {
    
    private Integer ink;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("钢笔中已加满墨水。。。");
        this.ink = 100;
    }
    
    @Override
    public void destroy() throws Exception {
        System.out.println("钢笔中的墨水都放干净了。。。");
        this.ink = 0;
    }
    
    @Override
    public String toString() {
        return "Pen{" + "ink=" + ink + '}';
    }
}

原型bean对象的生命周期

原型bean初始化不在spring容器初始化完成之前

原型bean的初始化行为与单例bean一致(init-method)

原型Bean的销毁不包括destroy-method

单例与原型bean生命周期对比

单例bean

bean初始化方法 -> set -> init-method -> @PostConstruct -> InitializingBean -> spring 容器初始化完成 -> bean使用
-> destroy-method -> @PreDestroy -> DisposableBean -> spring 容器销毁

原型 bean
spring 容器初始化完成 -> bean初始化方法 -> set -> init-method -> @PostConstruct -> InitializingBean -> bean使用
-> spring 容器销毁

ioc进阶

事件机制-了解

spring 当中事件监听模式的实现,我们可以创建一些 Listener 监听一些事件,自定义一些处理逻辑

// 基于xml
// 实现 ApplicationListener 接口 -> 扩展 自定义事件需要继承 ApplicationEvent
// @FunctionalInterface
// public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
//     void onApplicationEvent(E event);
// }

@Component
public class ContextRefreshedApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!");
    }
}

// 基于注解
@Component
public class ContextClosedApplicationListener {
    @EventListener
    public void onContextClosedEvent(ContextClosedEvent event) {
        System.out.println("ContextClosedApplicationListener监听到ContextClosedEvent事件!");
    }
}

public static void main(String[] args) throws Exception {
    System.out.println("准备初始化IOC容器。。。");
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
            "com.linkedbear.spring.event.a_quickstart");
    System.out.println("IOC容器初始化完成。。。");
    ctx.close();
    System.out.println("IOC容器关闭。。。");
}

准备初始化IOC容器。。。
ContextRefreshedApplicationListener监听到ContextRefreshedEvent事件!
IOC容器初始化完成。。。
ContextClosedApplicationListener监听到ContextClosedEvent事件!
IOC容器关闭。。。
  • 监听器也是一个bean
  • 监听器在spring容器初始化的时候创建好,在容器销毁时一起销毁
  • 可以监听一下系统内置的时间

Spring 系统事件
<font style="color:#DF2A3F;">ContextRefreshedEvent</font> 容器刷新事件, 所有单例的bean初始化完毕
ContextStartedEvent 需要 start 之后
ContextClosedEvent ioc 容器已经关闭,但是bean没有被完全销毁
ContextStoppedEvent

模块装配

之前我们学习了基于注解的编程式和声明式手动装配,但我们如果想快速引入一个功能模块的一连串bean(mvc场景,dao操作场景,有点像boot里的starter了是吧,但比起Boot复杂的实现,我们先基于做核心的需求做一个demo),手动导入的方式还是太过于复杂,,有没有什么办法可以快速导入一大个功能模块的bean呢,或者说涉及一个功能模块给外部调用的时候,如何设计方便模块调用者快速导入需要的bean呢

我们本小节总体想要实现的效果是,设计一个模块,里面有很多个需要引入的bean,调用者想要使用一个自定义注解,就可以引入所有模块设计者想要调用者导入的bean

把每个独立模块所需要的bean进行装配 -> 结合一个自定义注解 @Enabelxxx -> 通过 @Import 注解导入配置类
-> 快速实现某一个模块所需功能的引入

模块装配的实现方式

  1. 基于 @Import 注解 + bean (简单bean)/ 配置类(某些存在bean依赖注入的场景)
@Import({Boss.class, BartenderConfiguration.class})
public @interface EnableTavern {
    
}
  1. @Import + ImportSelector实现类
public class BarImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回类的全限定名
        return new String[] {Bar.class.getName(), BarConfiguration.class.getName()};
    }
}
@Import({Boss.class, BartenderConfiguration.class, BarImportSelector.class})
public @interface EnableTavern {
}
  1. @Import + ImportBeanDefinitionRegistrar实现类 注入的是 BeanDefinition (重点注意,后续关联 BeanDefinitonRegistry 的知识)
public class BarImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 注册bean
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Bar.class);
        registry.registerBeanDefinition("bar", beanDefinitionBuilder.getBeanDefinition());
    }
}

条件装配

我们上一小节学习了如何方便的批量导入某一个模块的bean,但是,如果我们想要更细粒度的控制bean创建的规则呢,目前只能全量导入,比如,我们常见的根据环境(dev,prod)来配置生效的bean,再比如,我们的bean之间存在依赖关系,同样是导入一个酒馆,白酒就要配置白酒的杯子,红酒则需要高脚杯来喝

@Profile 根据当前环境装配

默认值 default

@Profile-> 指定环境下才会装配 ->bean environment.getProfile()

@Configuration
@Profile("city")
public class BartenderConfiguration {
    @Bean
    public Bartender zhangxiaosan() {
        return new Bartender("张小三");
    }
}

public static void main(String[] args) throws Exception {
    // AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TavernConfiguration.class);
    // 会直接完成 refresh,无法控制bean初始化行为
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("city");
        ctx.register(TavernConfiguration.class);
        ctx.refresh();
        Stream.of(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}

通过跟 env 手动设置激活的环境,注意,不能直接用configuration那个构造函数

也可以在vm参数中传入,来指定当前激活环境 -Dspring.profiles.active=city

:::info
实际案例

如何根据环境使用不同数据源,我们同一个bean类型根据环境不同声明多份,然后结合 PropertySource 定义不同的数据源配置文件的读取策略,就可以了

:::

@Bean
    @Profile("dev")
    public DataSource devDataSource() {
        return null;
    }

    @Bean
    @Profile("test")
    public DataSource testDataSource() {
        return null;
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        return null;
    }

@Conditional 根据自定义条件装配

@Conditional(t extends Conditional) 条件注解

我们需要一个 Conditional 接口的实现类,定义matches 的逻辑

public class ExistBossCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getBeanFactory().containsBeanDefinition(Boss.class.getName());
    }
}

metadata 中可以拿到注解中的信息,centext能拿到spring容器的信息(获取其他bean的信息)

public class OnBeanCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 先获取目标自定义注解ConditionalOnBean上的beanNames属性
        String[] beanNames = (String[]) metadata.getAnnotationAttributes(ConditionalOnBean.class.getName()).get("beanNames");
        // 逐个校验IOC容器中是否包含传入的bean名称
        for (String beanName : beanNames) {
            if (!context.getBeanFactory().containsBeanDefinition(beanName)) {
                return false;
            }
        }
        return true;
    }
}

包扫描高级

指定扫描某一个类所在包下所有的类

我们可以指定扫描某一个类所在包下所有的类(是不是有点像,SpringBoot启动类的扫描逻辑)

包扫描的路径 -> @ComponentScan 的 value / basePackages 属性 指定扫描的包名 / basePackageClasses指定扫描的类

@Configuration
@ComponentScan(basePackageClasses = {DemoService.class, DemoComponent.class})
public class BasePackageClassConfiguration {
}

指定包含过滤规则

包扫描的注册规则,默认规则 -> include / exclude 指定包含与不包含策略

可以使用 注解,类型,正则,自定义来定义规则

// 不包含
@Configuration
@ComponentScan(basePackages = "com.linkedbear.spring.annotation.f_typefilter",
               includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = Color.class),
                                @ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.linkedbear.spring.annotation.f_typefilter.+Demo.+"),
                                @ComponentScan.Filter(type = FilterType.CUSTOM, value = GreenTypeFilter.class)},
               excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Animal.class))
public class TypeFilterConfiguration {
    
}

自定义规则 实现 TypeFilter接口 ->metadataReader读取到当前类的信息,metadataReaderFactory读取到指定类的信息

public class GreenTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException {
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        return classMetadata.getClassName().equals(Green.class.getName());
    }
}

资源管理-了解

根接口 InputStreamSource, 能获取到资源的输入流

public interface InputStreamSource {
	InputStream getInputStream() throws IOException;
}

Resource 接口中包含了资源的抽象

资源又可能包含不同的来源 -> 类路径,File, uri 这种来自于网络io等

PropertyResource 解析-了解

  1. 解析 properties 文件
  2. 解析 xml -> 引用的地方加 xml 前缀
    xml 文件需要遵循 一个很复杂的约束,底层和解析 properties 是一样的
  3. 解析 yml -> 引用的地方 加 yml 前缀
@Value("${yml.jdbc.url}")
private String url;

snake-yaml 工具解析


http://www.kler.cn/a/536326.html

相关文章:

  • windows 蓝牙驱动开发-传输总线驱动程序常见问题
  • Rust unresolved import `crate::xxx` 报错解决
  • 32.日常算法
  • 集成Google Maps页面提示[For development purposes only]解决方案
  • 剑指 Offer II 014. 字符串中的变位词
  • HTTP和HTTPS协议详解
  • leetcode_双指针 557. 反转字符串中的单词 III
  • 上传文件报错:the request was rejected because no multipart boundary was found
  • Linux-查看开放端口
  • java---->策略模式
  • Intellij IDEA如何查看当前文件的类
  • HTML之form表单学习
  • go结构体详解
  • 03-移除元素
  • leetcode:1897. 重新分配字符使所有字符串都相等(python3解法)
  • 开发板适配之UART
  • mybatisPlus介绍
  • Java 21 虚拟线程详解
  • 【C#】一维、二维、三维数组的使用
  • 测试中的第一性原理:回归本质的质量思维革命
  • 数据结构之顺序表和链表
  • s1:简单测试-时间规模化
  • Kotlin 使用虚拟线程并在低版本中自动切换到协程
  • zabbix v7.2.3容器运行Proxy代理服务器启用IPV6访问
  • 代码随想录算法【Day38】
  • SQL Server查询计划操作符(7.3)——查询计划相关操作符(6)