Spring framework: IoC容器

IoC容器

简介

  • IoC(Inversion of Control,控制反转)容器的原则是使原本有相互依赖的各个部分解耦,实现松耦合地运作 在传统的组合中,上层的模块在内部定义某些对象的类型,控制权在上层的模块中,如果需要修改内部的业务逻辑则会违反开闭原则 而IoC的思想则是使上层的模块将控制权“反转”给一个第三方,例如开发者自己或者一个IoC容器,由外部来控制并决定向上层模块提供什么样的实现类

  • IoC容器的常见的实现方法是DI(Dependency Injection,依赖注入),即上层模块所依赖的其它对象仅通过构造方法工厂方法setter方法暴露注入入口,容器在创建对象时控制注入对象的实现类

  • 当我们用到IoC容器时,说明一个类的实例往往是全局单例的,只要明白这个点,就能理解为什么会发生注入冲突 但同样可以实现非单例的实例化

  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person {
    private Eye e = new BlackEye(); // 在后续改动时违反开闭原则
    }

    class Person {
    private Eye e;

    public Person(Eye e) {
    this.e = e;
    }
    }
    Person = new Person(new BlackEye()); // 在外部注入
  • 而在Spring框架中,这个“外部注入”不需要由开发者手动地注入,而是通过声明式注解由Spring内置的IoC容器自动注入

  • org.springframework.beansorg.springframework.contextIoC容器的核心

  • 依赖:

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.0-M9</version>
    </dependency>

元数据配置

上下文

  • SpringIoC容器通过解析元数据来管理Bean

    元数据通常包括以下内容:

    • Bean的类型
    • Beanid
    • Bean的依赖注入
    • Bean的作用域
    • Bean的生命周期行为(以回调形式存在)
    • Bean的懒加载、协作者等其它元数据
  • ApplicationContext:应用上下文接口的实例表示一个IoC容器,它会自动创建、配置、组装所有的Bean

    • 继承BeanFactory接口:该接口提供一系列和Bean有关的方法 T getBean(String name, Class<T> clazz):获取它管理的类型为T、名字为nameBean
    • 继承MessageSource接口:该接口提供国际化处理信息的能力 String getMessage(String msg, Object[] args, Locale locale):根据locale以及可选的选项args解析msg
  • 创建一个ApplicationContext实例: 创建ApplicationContext实例同时意味着许多配置,在不依靠外部框架(如Spring BootTomcat)的情况下,ApplicationContext只能通过new创建 Spring提供了两种构造ApplicationContext实例的方式,分别为基于XML的配置、基于Java的配置 基于XML的配置:实现类为ClassPathXmlApplicationContext,构造方法需要的参数是String...,即一系列xml配置文件的路径 基于Java的配置:实现类为AnnotationConfigApplicationContext,构造方法需要的参数是Class<?>...,即一系列@Configuration注解的配置类

  • 基于XML、基于注解、基于Java的关系

    • 三种方式都由Spring控制实例的注入
    • XML是最早的Spring提供的配置方式,它是外部的、支持非侵入式地修改元数据配置
    • Spring 2.0开始逐步地提供一系列注解来方便分散化、简洁化配置,提供了基于注解的配置,位于org.springframework.stereotypeorg.springframework.beans.factory.annotations包中 在基于注解的配置里,开发者对实例的创建和注入的控制方式和XML形式类似,只是停留在声明Bean上,目的是简化配置代码书写并提供编译时的类型检查 例如stereotype包中的@Service@Repository@Component,它们的修饰表示声明这个类属于某种Bean,即配置是什么的元数据 例如beans.factory.annotations包中的@Autowired@Qualifier,它们修饰表示指示如何创建Bean 这种方式的配置需要在XMLJava中启用组件扫描来纳入管理
    • Spring 3.0开始,提供org.springframework.context.annotations来替代整个XML形式的配置,并提供了新的ApplicationContext的实现类,例如@Configuration@Bean 尽管注入仍由Spring框架管理,但这种方式允许开发者十分灵活地决定如何创建一个实例,允许使用Java代码进行带有分支判断地创建实例
    • 第二种方式和另外两种方式并不是互斥的,第一种方式和第三种方式对应着两种不同的实现类,而第二种方式则是作为一种方便的声明式API存在,适用于结构简单的Bean的声明 现代应用中常使用第二种与第三种方式混合的配置方案,但并不意味着XML配置完全无用,它的外部、非侵入性正是它的特点

BeanXML配置

  • <bean/>idname属性或<alias/>:它们都是可选项,默认由Spring生成一个唯一的id

    • id是唯一标识,若自定义,则必须是唯一的,不允许使用特殊字符,通常使用驼峰命名法
    • name是别名集合(字符串表示),不同别名用,;隔开,允许重复、允许使用类似/的特殊字符
    • 若使用引用,则查找id是否含有匹配项、然后查找name是否含有匹配项、然后查找类型是否匹配,若有二义性则报错
    • <alias name="" alias=""/>:在定义bean标签以外的地方定义它的别名
  • 选择bean的构造器:

    • class属性:用于指定该bean所使用的构造器的来源,并不一定是class类型的
    • 若不提供任何构造方法,则自动根据提供的参数匹配合适的构造方法
    • 使用静态工厂方法:指定factory-method属性,会调用指定class属性这个类内部的factory-method静态方法
    • 使用工厂类实例的工厂方法:不能指定class属性,而是指定factory-bean属性,它是另一个类型为某个工厂类的bean
    • primary布尔属性:标识该<bean/>对应的构造器为默认的创建方法
  • 提供构造参数(依赖注入)来消除同名构造器的二义性:

    • 基于构造器的注入:<constructor-arg/>:作为<bean/>的子标签,表示传递给构造器的参数,为了消除二义性,它有若干属性,不必全部指定而只需要保证不存在歧义即可: name属性表示参数的名称 index属性表示参数的索引(0-idx) type属性表示参数的类型 value属性表示参数的字面值 ref属性表示使用其它bean作为参数
    • 基于setter的注入:要求该bean对应的class含有指定属性的setter方法,则可使用<property/>传递用setter设置的参数 其属性与<constructor-arg/>一致
    • 一般来说,<constructor-arg/>会严格匹配构造器,因此会更安全(不会出现null) <property/>则是在构造后通过调用setter方法进行配置的方案,可能导致某些属性没被注意而为null 前者若想达到可选参数的效果,也可使构造器参数为Optional<T>或使用@Nullable注解
  • 启用组件扫描:

    1
    2
    3
    <beans>
    <context:component-scan base-package="包名" />
    </beans>
  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- primary标识首选 -->
    <bean id="userBean" class="demo.beans.UserBean" primary="true">
    <!-- 根据构造参数唯一确定构造器 -->
    <constructor-arg index="0" type="int" value="10"/>
    <constructor-arg index="1" name="otherBean" ref="otherBean"/>
    <constructor-arg index="2" type="java.lang.String" value="str"/>
    <!-- 后续的setter配置 -->
    <property name="optionalArg" value="1"/>
    </bean>

    <!-- 不带任何参数表示选择默认构造方法, 但这是在primary之后的选择 -->
    <bean id="userBean" class="demo.beans.UserBean">
    </bean>

    <!-- 在外部起别名 -->
    <alias name="userBean" alias="uB"/>

BeanJava配置

  • 声明为具有某种功能的bean(类级修饰):

    • @Component(value=""):声明为一般的beanvalue可选,表示该bean的名称,默认为类名的驼峰命名
    • @Service:声明为服务类,@Component的一种
    • @Repository:声明为持久化类,@Component的一种
    • @Controller:声明为控制器类,@Component的一种
    • 需要用@ComponentScan(basePackages="")启动扫描
  • 依赖注入:

    • @Autowired(required=true):修饰方法或属性,表示自动注入,修饰方法时若修饰构造器或setter则对应构造器注入或setter注入、修饰一般方法则会使bean在初始化完成后调用该方法,(不推荐)修饰属性时表示直接注入 默认强制要求注入,可使required=false或使用Optional<T>或使用@Nullable表示可选 如果含有多个构造方法,基于注解的配置要求至少有一个@Autowired
    • @Qualifier(value=""):修饰类或@Bean方法时给该bean起限定符,修饰属性或形参时表示指定使用value对应的bean注入该属性
    • @Value(value=""):注入简单字面值,可以修饰形参
    • @Primary:标识某个类时表示当某个接口或某个抽象类存在多个实现类时,选择该类作为首选的实现 标识某个@Bean方法时表示当存在多个返回同一类型的方法时首选该方法
  • 基于Java的配置:

    • @Configuration:标识某个类为配置类bean,它也是一个@Component
    • @Bean:标识某个方法为创建某个类型bean的方法,配合@Primary决定首选方法 其name属性表示这个bean的名字,它可以同@Qualifiervalue属性一样被绑定然后用@Qualifier指定注入 因为一般name的命名有一定风格(通常是类名的驼峰命名),而如果需要用含特殊语义的命名,@Qualifiervalue属性是一种选择
  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    package demo.config;

    @Configuration
    @ComponentScan("demo.service")
    public class BeansConfiguration {

    // 默认用方法名作为@Bean的name
    @Bean
    public User user() {
    return new User();
    }

    @Bean
    @Primary
    public User user(int type) {
    return new User(type);
    }
    }

    package demo.service;

    @Service
    public class UserService {
    private final UserRepository userRepo;

    public UserService() {
    }

    @Autowired
    public UserService(UserRepository userRepo, @Value("0") int type) {
    this.userRepo = userRepo;
    }
    }

    package demo;

    public class Main {
    public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(demo.config.BeansConfiguration);
    }
    }