本文重点在于充分应用 Spring 提供的 IoC 特性,介绍如何创建一个好用的 Bean。基础篇不涉及后置处理器、 BeanDefinition 以及 Spring 加载原理相关的知识。

引入

ioc 的起源

  • ** 接口与实现类的需求变更 ** :最初的静态工厂模式。
  • ** 反射机制 ** :延迟加载,避免编译时依赖。
  • ** 外部化配置 ** :将 Bean 的全限定名存储在配置文件中。
  • ** 缓存机制 ** :存储已创建的 Bean 对象。

入门

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

依赖查找

使用 ClassPathXmlApplicationContext 创建 Spring 容器,并根据配置文件中的 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,并注入属性值。

基础类型注入

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>

引用类型注入

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

:::

基础

依赖查找高级

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

Map<String, Object> beans = ctx.getBeansOfType(SomeInterface.class);

根据被注解标注的类

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

获取容器中所有 bean

String[] beanNames = ctx.getBeanDefinitionNames();

延迟查找(缺省加载)

ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
Dog dog = dogProvider.getIfAvailable(Dog::new);

ApplicationContext 与 BeanFactory

  • ApplicationContextBeanFactory 的子接口,提供了更多功能。
  • AOP 支持、配置元信息、资源管理、事件驱动机制、国际化、Environment 抽象等。

注解驱动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 {
    
}

@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名称

依赖注入

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("0")
private Integer order;

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

PropertySource配置文件注入

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

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

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

补充-> SpEL表达式

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

自动注入机制-@Autowired

@Autowired


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

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

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

注入的bean不存在

@Autowired(required = false)
private Person person;

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

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

@Primary
@Bean
public Animal cat() {
    return new Cat();
}

@Autowired 注入逻辑分析

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

注入多个相同类型的bean

使用 List 或者 Set 注入

@Autowired
private List<Animal> animals; // 会注入所有类型为Animal的bean
@Resource@Inject 注入
  • @Resource 根据名称注入。
  • @Inject 根据类型注 入,需要额外导入 JSR330 依赖。

回调注入-Aware接口&InitializingBean回调

使用 Aware 接口实现回调注入。 回调注入不依赖于框架的扩展,属于内置功能,如果我们需要一个 不依赖于后处理器 就可以注入的场景,可以选它。@Autowired(其他依赖后处理器的注入机制)依赖于后处理器,某些情况下会失效。

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

InitializingBean可以执行一些初始化操作

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(MyBean.class);

    @Override
    public void setBeanName(String name) {
        log.debug("当前bean " + this + " 名字叫:" + name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.debug("当前bean " + this + " 容器是:" + applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("当前bean " + this + " 初始化");
    }

    @Autowired
    public void aaa(ApplicationContext applicationContext) { // 没有后处理器无法执行
        log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);
    }

    @PostConstruct
    public void init() { // 没有后处理器无法执行
        log.debug("当前bean " + this + " 使用@PostConstruct 初始化");
    }
}

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

:::color1
@Autowired 失效场景

config 配置类中创建 BeanFactoryPostProcessor ,此时,config 配置类的创建实际提前了,BeanPostProcessor 还未创建,所以依赖于BeanPostProcessor的 @Autowired 自动注入无法生效

:::

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 本体

:::color1
在 Spring 发展阶段中重要,但目前已经很鸡肋的接口 FactoryBean 的使用要点:

  1. 作用是用于创建较为复杂的产品如 SqlSessionFactory ,但 @Bean 已具备等价功能。
  2. 使用较为古怪,一不留神就会用错:
    a. 被 Factory 创建的 Bean
    • 会认为创建、依赖注入、 Aware 接口回调、前初始化这些都是 FactoryBean 的职责,这些流程都不会走。
    • 唯有后初始化的流程会走,也就是产品可以被代理增强。
    • 单例的产品不会存储于 BeanFactorysingletonObjects 成员中,而是另一个 factoryBeanObjectCache 成员中。
  • b. 按名字获取时,拿到的是产品对象,名字前加 & 获取的是工厂对象。
    工厂管理的 Bean 在初始化后可以被增强,其他阶段不会。

:::

bean 的作用域

  • 单例 -> 默认 -> 每次获取的或者注入的都是同一个对象 -> ApplicationContext 被初始化就会被创建。
  • 原型 Bean -> 指定 @Scope("prototype") -> 每次获取就会创建一个新的对象 -> 延迟了 Bean 创建的时机。

bean 实例化方式

spring对bean的多种对象创建模式的支持

普通 Bean

通过 <bean> 标签或 @Bean 注解创建。

FactoryBean 方式

通过实现 FactoryBean 接口创建。

静态工厂

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

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 的生命周期

bean 生命周期时序图
在这里插入图片描述

生命周期回调机制

  1. init-methoddestroy-method
  2. @PostConstruct@PreDestroy
  3. InitializingBeanDisposableBean 接口。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐