IoC容器

简介

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

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

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

  • 例子:

    java
    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.beans与org.springframework.context是IoC容器的核心

  • 依赖:

    xml
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>7.0.0-M9</version>
    </dependency>

元数据配置

上下文

  • Spring的IoC容器通过解析元数据来管理Bean

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

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

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

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

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

Bean的XML配置

  • <bean/>的id、name属性或<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注解
  • 启用组件扫描:

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

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

Bean的Java配置

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

    • @Component(value=""):声明为一般的bean,value可选,表示该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的名字,它可以同@Qualifier的value属性一样被绑定然后用@Qualifier指定注入 因为一般name的命名有一定风格(通常是类名的驼峰命名),而如果需要用含特殊语义的命名,@Qualifier的value属性是一种选择
  • 例子:

    java
    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);
      }
    }