1 Star 0 Fork 0

RingoTangs / spring-lessons

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

一、Spring IoC容器概述

1. 依赖查找

1.1. 环境搭建

<!-- spring ioc 核心依赖 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

在Maven resource 目录下创建 META-INF 文件夹,写 spring 的配置文件。

<!-- 配置文件名为 dependency-look-context.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<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="user" class="thinking.in.spring.ioc.overview.domain.User">
        <property name="id" value="10"></property>
        <property name="name" value="张三"></property>
     </bean>
</beans>

1.2. 根据Bean名称查找

根据 Bean 名称查找包括:

  • 实时查找。
  • 延时查找。

配置 Bean:

<!-- 配置 Bean -->
<bean id="user" class="thinking.in.spring.ioc.overview.domain.User">
    <property name="id" value="10"></property>
    <property name="name" value="张三"></property>
</bean>

<!-- ObjectFactoryCreatingFactoryBean 内部类实现了 ObjectFactory -->
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
    <property name="targetBeanName" value="user"></property>
</bean>

从 Ioc 容器中获取 Bean:

public class DependencyLookupDemo {
    public static void main(String[] args) {
        // 配置 XML 文件
        // 启动 Spring Application Context
        String configLocation = "classpath:/META-INF/dependency-look-context.xml";
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
        lookUpInRealTime(beanFactory);
        lookUpInLazy(beanFactory);
    }

    // 1: 延时查找
    private static void lookUpInLazy(BeanFactory beanFactory) {
        // 并没有实例化我们注入的 bean
        ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
        
        // 在这里实例化注入的 bean
        User user = objectFactory.getObject();
        System.out.println("延时查找: " + user);
    }

    // 2: 实时查找
    private static void lookUpInRealTime(BeanFactory beanFactory) {
        User user = (User) beanFactory.getBean("user");
        System.out.println("实时查找: " + user);
    }
}

延迟加载:ObjectFactory 对象并不是直接返回了实际的 Bean,而是一个 Bean 的查找代理。当得到 ObjectFactory 对象时,相当于 Bean 没有被创建,只有当 getObject() 方法时,才会触发 Bean 实例化等生命周期。

1.2. 根据Bean类型查找

public class DependencyLookupDemo {
    public static void main(String[] args) {
        // 配置 XML 文件
        // 启动 Spring Application Context
        String configLocation = "classpath:/META-INF/dependency-look-context.xml";
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
        lookupByType(beanFactory);
        lookupCollectionByType(beanFactory);
    }

    // 1: 根据类型查找 Bean, 以集合的方式返回
    private static void lookupCollectionByType(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
            Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
            System.out.println("查找到的所有 User 集合对象: " + users);
        }
    }

    // 2: 按照类型查找
    private static void lookupByType(BeanFactory beanFactory) {
        User user = beanFactory.getBean(User.class);
        System.out.println("按照类型查找: " + user);
    }
}

根据类型查找Bean,并以集合的方式返回,和 spring-boot 中注入类似:

@Autowired
Map<String, User> users;

1.3. 根据Java注解查找

自定义注解:

/**
 * 自定义注解
 *
 * @author Ringo
 * @date 2021/8/21 23:51
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Super {
}

创建父类:

public class User {

    private Long id;

    private String name;
    
    // ..get set
}

创建子类:

// 标注上 @Super 注解
@Super
public class SuperUser extends User{
    
    private String address;
    
    // ..get set
}

User、SuperUser 加入到 IoC 容器:

注:@Primary 有同样的效果。

<bean id="user" class="thinking.in.spring.ioc.overview.domain.User">
    <property name="id" value="10"></property>
    <property name="name" value="张三"></property>
</bean>

<bean id="superUser" class="thinking.in.spring.ioc.overview.domain.SuperUser" parent="user" primary="true">
    <!-- SuperUser 也是 User, 按照类型找就会报错, Spring 不知道应该找哪个Bean -->
    <!-- primary 可以指定 bean 类型, 当 bean 实例冲突时先找这个 bean -->
    <property name="address" value="杭州"/>
</bean>

在 IoC 容器中安装注解查找 Bean:

public class DependencyLookupDemo {
    public static void main(String[] args) {
        // 配置 XML 文件
        // 启动 Spring Application Context
        String configLocation = "classpath:/META-INF/dependency-look-context.xml";
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
        // 按注解类型查找bean
        lookupByAnnotationType(beanFactory);
    }

    // 通过注解方式查找
    private static void lookupByAnnotationType(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = 
                (ListableBeanFactory) beanFactory;
            Map<String, User> users = 
                (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
            System.out.println(users);
        }
    }
}

2. 依赖注入

2.1. 集合Bean对象注入

创建UserRepository类,属性为集合。

public class UserRepository {
    
    private Collection<User> users;
    
    // get set...
}

dependency-injection-context.xml 写一个新的配置文件。

注意:新的配置会导入之前的配置 dependency-look-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       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
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <!-- 注意:通过导入复用 dependency-look-context.xml -->
    <import resource="dependency-look-context.xml"/>

    <bean id="userRepository" class="thinking.in.spring.ioc.overview.repository.UserRepository"
          autowire="byType"> <!-- 2: 自动配置  -->

        <!-- 1: 手动配置 -->
        <!-- 使用 util:list 标签需要导入对应的 util 名称空间(需要添加以下三个) -->
        <!-- xmlns:util="http://www.springframework.org/schema/util" -->
        <!-- http://www.springframework.org/schema/util-->
        <!-- http://www.springframework.org/schema/util/spring-util-4.0.xsd-->

        <!--        <property name="users">-->
        <!--            <util:list>-->
        <!--                <ref bean="user"/>-->
        <!--                <ref bean="superUser"/>-->
        <!--            </util:list>-->
        <!--        </property>-->
    </bean>

</beans>

在IoC容器中查找 UserRepository 的bean。

public class DependencyInjectionDemo {

    public static void main(String[] args) {
        // 这里使用的是新创建的配置
        String configLocation = "classpath:/META-INF/dependency-injection-context.xml";
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
        lookupInType(beanFactory);
    }

    private static void lookupInType(BeanFactory beanFactory) {
        UserRepository userRepository = beanFactory.getBean(UserRepository.class);
        System.out.println(userRepository);
    }
}

2.2. 注入内建非Bean对象

UserRepository 中的属性为 BeanFactory

public class UserRepository {
    
    private BeanFactory beanFactory;   
    
    // get set...
    
}

UserRepository 直接加入到容器, 并自动注入:

<bean id="userRepository" class="thinking.in.spring.ioc.overview.repository.UserRepository"
      autowire="byType" /> 

依赖查找 和 依赖注入两种方式查看:

public class DependencyInjectionDemo {

    public static void main(String[] args) {
        String configLocation = "classpath:/META-INF/dependency-injection-context.xml";
        BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
        // 依赖注入
        System.out.println(beanFactory.getBean(UserRepository.class).getBeanFactory());

        // 依赖查找
        // 内建依赖不能通过依赖查找找到
        System.out.println(beanFactory.getBean(BeanFactory.class));
    }
}
  • 依赖注入输出结果:org.springframework.beans.factory.support.DefaultListableBeanFactory@7a5d012c: defining beans [user,superUser,objectFactory,userRepository]; root of factory hierarchy。
  • 依赖查找则报错:No qualifying bean of type 'org.springframework.beans.factory.BeanFactory' available。

3. Spring IoC依赖来源

Spring IoC依赖来源:

  1. 自定义 Bean。
  2. 容器内建 Bean 对象。
  3. 容器内建依赖(依赖注入,非Bean)。
// 依赖来源一:自定义Bean
UserRepository userRepository = beanFactory.getBean(UserRepository.class);

// 依赖来源二:依赖注入(容器内建依赖) BeanFactory 不是Bean, 不能用依赖查找
// 但是可以用【依赖注入】的方式来获取
System.out.println(userRepository.getBeanFactory());

// 依赖来源三:容器内创建的Bean
Environment environment = beanFactory.getBean(Environment.class);

注意:自定义的bean和内建的bean能够通过依赖查找查询到,但是内建依赖是不能通过依赖查找查询到,因为他们的来源不同。

BeanFactory 并不是 Bean,Java Bean 是一种功能组件,它是 Java对象的方式封装。这里重点是讨论 IoC 依赖的来源,所谓依赖就是被组合的对象。

4. Spring IoC配置元信息

Bean定义配置:

  • 基于 XML 文件。
  • 基于 Properties 文件。
  • 基于 Java 注解。
  • 基于 Java API。

IoC容器配置:

  • 基于 XML 文件。
  • 基于 Java 注解。
  • 基于 Java API。

外部化属性配置:

  • 基于 Java 注解(例如:Spring Boot中使用的 @Value)。

5. BeanFactory和ApplicationContext谁是IoC容器?

回顾之前创建的 UserRepository

public class UserRepository {
    
    private BeanFactory beanFactory;
    
    // get set...
}

UserRepository 加入到容器中,并且配置 autowired

<bean id="userRepository" class="thinking.in.spring.ioc.overview.repository.UserRepository"
      autowire="byType">

但是为什么等式不成立?

String configLocation = "classpath:/META-INF/dependency-injection-context.xml";
BeanFactory beanFactory = new ClassPathXmlApplicationContext(configLocation);
// 依赖注入
UserRepository userRepository = beanFactory.getBean(UserRepository.class);

// 这个等式为什么不成立?
System.out.println(userRepository.getBeanFactory() == beanFactory);
  • ApplicationContext是BeanFactory的子接口,说明ApplicationContext is BeanFactory。并且ApplicationContext 是BeanFactory的包装类,也就是内部组合了BeanFactory的实现-DefaultListableBeanFactory。
  • 为什么包装了DefaultListableBeanFactory,因为它需要简化且丰富功能来为企业开发提供更高的便捷性,也就是说ApplicationContext 是DefaultListableBeanFactory的超集。
  • 至于为什么UserRepository注入的BeanFactory 不等于ClassPathXmlApplicationContext得到的BeanFactory ,是因为AbstractApplicationContext#prepareBeanFactory中 指明了 beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory); 也就是说当byType是BeanFactory.class的时候,获得是的ApplicationContext中的DefaultListableBeanFactory对象。
  • 那真正的IOC的底层实现就是BeanFactory的实现类,因为ApplicationContext是委托DefaultListableBeanFactory来操作getBean等方法的。

ApplicationContext 就是 BeanFactory,只不过子类比父类功能更强大。

实际上,可以用 ApplicationContext 来 代替 BeanFactory

String configLocation = "classpath:/META-INF/dependency-injection-context.xml";
ApplicationContext applicationContext = 
    new ClassPathXmlApplicationContext(configLocation);
// 依赖注入
UserRepository userRepository = applicationContext.getBean(UserRepository.class);

System.out.println(applicationContext);
System.out.println(userRepository.getBeanFactory());
// 这个等式为什么不成立?
System.out.println(userRepository.getBeanFactory() == applicationContext);

// 这个等式成立
System.out.println(userRepository.getBeanFactory() == applicationContext.getAutowireCapableBeanFactory());

类继承图

6. Application Context特性

ApplicationContext 除了 IoC 容器角色, 还提供:

  • 面向切面(AOP)。
  • 配置元信息(Configuration Metadata)。
  • 资源管理(Resources)。
  • 事件(Events)。
  • 国际化(i18n)。
  • 注解(Annotations)。
  • Environment 抽象(Environment Abstract)。

7. Spring IoC 容器选BeanFactory还是ApplicationContext?

结论:

  • BeanFactory 是 Spring 底层 IoC 容器。
  • ApplicationContext 是具备应用特性的 BeanFactory 超集。

7.1. BeanFactory

显式声明 DefaultListableBeanFactory 充当 Spring IoC 容器:

// 单纯的使用 BeanFactory 获得 IoC 容器中的 Bean
// 1: 创建 BeanFactory 容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

// 2: 加载配置
// 构造器参数是 BeanDefinitionRegistry
// DefaultListableBeanFactory 恰好实现了 BeanDefinitionRegistry
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
String location = "classpath:META-INF/dependency-look-context.xml";
// 返回加载 Bean Definitions 的数量
int count = reader.loadBeanDefinitions(location);
System.out.println("加载 Bean Definition 的数量: " + co User.class);
System.out.println("查找到容器中所有 User Bean: " + users);

如果使用 BeanFactory 当做 IoC 容器,就没有事件发布、国际化等特性了!

7.2. ApplicationContext

显示声明 AnnotationConfigurationApplication 来使用 Spring IoC 容器:

/**
 * 注解的 {@link ApplicationContext} 作为 IoC 容器示例
 *
 * @author Ringo
 * @date 2021/8/22 21:23
 */
public class AnnotationApplicationContextAsIocContainerDemo {

    public static void main(String[] args) {
        // 1: 创建 ApplicationContext 作为 IoC 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 2: 将当前类注册为 Configuration 类
        applicationContext.register(AnnotationApplicationContextAsIocContainerDemo.class);
        // 3: 调用 refresh() 启动应用上下文(必须调用)
        applicationContext.refresh();
        
        // 4: IoC 容器中找 Bean
        Map<String, User> users = applicationContext.getBeansOfType(User.class);
        System.out.println("IoC 容器中查找到的User Bean: " + users);
    }

    /**
     * 通过 Java 注解的方式定义 Bean
     */
    @Bean
    public User user() {
        User user = new User();
        user.setId(10L);
        user.setName("zs");
        return user;
    }
}

8. 面试题

8.1. 什么是 Spring IoC 容器?

Spring 框架实现了控制反转原则(IoC)。IoC 包括【依赖查找】和【依赖注入】。

依赖注入(DI):可以通过属性、构造器、Set方法区注入依赖。IoC 容器会把注入的依赖放到创建的Bean中。

8.2. BeanFactory与FactoryBean?

BeanFactory 是 IoC 的底层容器。

FactoryBean 是创建 Bean 的一种方式,帮助实现复杂的初始化逻辑。

8.3. Spring IoC 容器启动时做了哪些准备?

IoC 配置元信息读取和解析、IoC 容器生命周期、Spring 事件发布、国际化等。

二、Spring Bean基础

1. BeanDefinition元信息

1.1. BeanDefinition 元信息?

属性(Property) 说明
Class Bean全类目,必须是具体类,不能用抽象类或者接口。
Name Bean的名称或者ID。
Scope Bean的作用域(singleton、prototype等)。
Constructor arguments Bean构造器参数(用于依赖注入)。
Properties Bean属性设置(用于依赖注入)。
Autowiring mode Bean自动绑定模式(如:通过名称 byName)。
Lazy initialization mode Bean延迟初始化模式(延迟和非延迟)。
Initialization method Bean初始化回调方法名称。
Destruction method Bean销毁回调方法名称。

1.2. BeanDefinition构建

  • 通过BeanDefinitionBuilder。
  • 通过 AbstractBeanDefinition 以及派生类。
// 1: 通过 BeanDefinitionBuilder 构建
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
// 通过属性设置
beanDefinitionBuilder
    .addPropertyValue("id", 10)
    .addPropertyValue("name", "张三");
// 获取 BeanDefinition 实例
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

// BeanDefinition 并非 Bean 最终状态, 可以自定义修改


// 2. 通过 AbstractBeanDefinition 以及派生类
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
// 设置 Bean 类型
genericBeanDefinition.setBeanClass(User.class);
// 通过 MutablePropertyValues 批量操作属性
MutablePropertyValues propertyValues = new MutablePropertyValues();
// propertyValues.addPropertyValue("id", "80");
// propertyValues.addPropertyValue("name", "李四");
// 和上面 addPropertyValue() 方法效果一样
propertyValues
    .add("id", "80")
    .add("name", "李四");

// 通过 set MutablePropertyValues 批量操作属性
genericBeanDefinition.setPropertyValues(propertyValues);

2. Bean别名简单使用

Bean 别名(Alias)的价值:

  • 复用现有的 BeanDefinition 。
  • 更具有场景化的命名方法。

配置 Bean 的信息和别名:

<!-- 创建Bean 加入到 IoC 容器 -->
<bean id="user" class="thinking.in.spring.ioc.overview.domain.User" scope="singleton">
    <property name="id" value="10"/>
    <property name="name" value="张三"/>
</bean>

<!-- 将 IoC 容器中 "User Bean" 关联/建立 别名 -->
<alias name="user" alias="ringo-user"/>

IoC 容器中根据 Alias 查找 Bean:

// 1: 创建 IoC 容器 加载配置元信息
String configLocation = "classpath:META-INFO/bean-definitions-context.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);

// 2: 依赖查找
// 通过 Bean Name 查找
User user = (User) applicationContext.getBean("user");
// 通过 Bean Alias 查找
User ringo = (User) applicationContext.getBean("ringo-user");
System.out.println("user: " + user);
System.out.println("ringo: " + ringo);
System.out.println("user == ringo ? " + (user == ringo));     // true

3. BeanDefinition注册到IoC容器

BeanDefinition注册到IoC容器:

  • XML配置元信息:
    • <bean name="" .. />
  • Java 注解配置元信息:
    • @Bean。
    • @Component。
    • @Import。
  • Java API配置元信息
    • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String, BeanDefinition)
    • 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition, BeanDefinitionRegistry)
    • 配置方式:AnnotatedBeanDefinitionReader#Register(Class...)

注意:Java API 的【配置方式】实现是 AnnotationConfigApplicationContextregistter(Class...)方法。

3.1. Java 注解注册Bean

/**
 * 注解 {@link org.springframework.beans.factory.config.BeanDefinition} 示例
 *
 * @author Ringo
 * @date 2021/8/23 23:19
 */
// 3: 通过 @Import 来进行导入
// @Import 配合 @Configuration 一起使用, 将标记的类加入到 IoC 容器
@Import(AnnotationBeanDefinitionDemo.Config.class)
@Configuration
public class AnnotationBeanDefinitionDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();

        // 注册 Configuration Class(配置类)
        // 等同于在 Config 类上添加 @Configuration 注解
        // applicationContext.register(AnnotationBeanDefinitionDemo.class);

        // 这里使用包扫描
        applicationContext.scan("com.ymy.thinking.in.spring.bean.definition.**");

        // 启动 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找
        Map<String, Config> configBeans = 
            applicationContext.getBeansOfType(Config.class);
        Map<String, User> userBeans = applicationContext.getBeansOfType(User.class);
        System.out.println("Config 类型所有的 Beans :" + configBeans);
        System.out.println("User 类型所有的 Beans :" + userBeans);

        // 显示关闭 Spring 应用上下文
        applicationContext.close();
    }


    // 2. 通过 @Component 方式定义 Bean
    // 使用了 @Import 方式, 就不使用 @Component 了(两种方式 任选其一)
    // @Component
    public static class Config {

        // 1: 通过 @Bean 的方式定义 Bean

        /**
         * 通过 Java 注解的方式, 定义了一个 Bean.
         * 配置 Bean 的别名.
         */
        @Bean(name = {"user", "ringo-user"})
        public User user() {
            User user = new User();
            user.setId(8848L);
            user.setName("李四");
            return user;
        }

    }
}

3.2. Java API注册Bean

/**
 * Java API 注册 {@link org.springframework.beans.factory.config.BeanDefinition} 到 IoC 容器
 *
 * @author Ringo
 * @date 2021/8/24 22:22
 */
public class JavaAPIRegistryBeanDefinitionDemo {
    
    public static void main(String[] args) {
        // 创建 IoC 容器
        // AnnotationConfigApplicationContext 实现了 BeanDefinitionRegistry接口
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 包扫描
        applicationContext.scan("com.ymy.thinking.in.spring.bean.definition.**");
        
        // 命名注册
        registerBeanDefinition(applicationContext, "zs-user");
        // 非命名注册
        registerBeanDefinition(applicationContext);

        // 开启 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找
        Map<String, User> users = applicationContext.getBeansOfType(User.class);
        System.out.println("IoC 容器中所有 User 类型的 Bean: ");
        users.forEach((k, v) -> System.out.println(k + "=" + v));

        // 关闭 Spring 应用上下文
        applicationContext.close();
    }

    // 命名方式 注册
    public static void registerBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(User.class)
                .addPropertyValue("id", 3500L)
                .addPropertyValue("name", "张三");
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        if (StringUtils.hasText(beanName)) {
            // 命名注册
            registry.registerBeanDefinition(beanName, beanDefinition);
        } else {
            // 非命名注册
            BeanDefinitionReaderUtils
                .registerWithGeneratedName(beanDefinition, registry);
        }
    }

    // 非命名方式 注册
    public static void registerBeanDefinition(BeanDefinitionRegistry registry) {
        registerBeanDefinition(registry, null);
    }

    // 配置方式 注册
    @Configuration
    public static class Config {
        @Bean(name = "lisi-user")
        public User user() {
            User user = new User();
            user.setName("里斯");
            user.setId(89L);
            return user;
        }
    }

}

输出结果:

Bean注册

4. 实例化 Spring Bean

Bean实例化(Instantiation):

  • 常规方式

    • 通过构造器(配置元信息:XML 、Java 注解 和 Java API)。
    • 通过静态工厂方法(配置元信息:XML 和 Java API)。
    • 通过 Bean 工厂方法(配置元信息:XML 和 Java API)。
    • 通过 FactoryBean(配置元信息:XML、Java 注解 和 Java API)。
  • 特殊方式:

    • 通过 ServiceLoaderFactoryBean (配置元信息、XML、Java 注解 和 Java API)。
    • 通过 AutowireCapableBeanFactory#createBean(Class, boolean)
    • 通过 BeanDefinitionRegistry#registerBeanDefinition(String, BeanDefinition)

4.1. 常规方式

4.1.1. 静态方法

创建 Person 类,声明一个静态方法,用于创建对象。

public class Person {

    private String name;

    private Integer age;

    public static Person create() {
        Person person = new Person();
        person.setName("Ringo");
        person.setAge(18);
        return person;
    }
    
    // get set...
}

在 XML 配置元信息中声明。

<!-- static method 实例化Bean -->
<bean id="person-by-static-method"
      class="com.ymy.thinking.in.spring.bean.entity.Person"
      factory-method="create"/>

4.1.2. 实例方法

创建 PersonFactory 和默认的实现类。

public interface PersonFactory {
    default Person create() {
        return Person.create();
    }
}

// 默认实现类
public class DefaultPersonFactory implements PersonFactory {
}

在 XML 配置元信息中声明。

<!-- 实例 method 实例化Bean -->
<bean id="personFactory"
      class="com.ymy.thinking.in.spring.bean.factory.DefaultPersonFactory"/>
<bean id="person-by-instance-method"
      class="com.ymy.thinking.in.spring.bean.entity.Person"
      factory-bean="personFactory" factory-method="create"/>

4.1.3. FactoryBean

创建 PersonFactoryBean 实现 FactoryBean 接口。

public class PersonFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        return Person.create();
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}

在 XML 配置元信息中声明。

<!-- FactoryBean 实例化 Bean -->
<bean id="person-by-factory-bean"
      class="com.ymy.thinking.in.spring.bean.entity.PersonFactoryBean"/>

4.1.4. 展示以上三种方式结果

// 1: 创建 IoC 容器
String configLocation = "classpath:META-INFO/bean-instance-context.xml";
ApplicationContext applicationContext = 
    new ClassPathXmlApplicationContext(configLocation);

// 2: 依赖查找

// 静态方法 实例化Bean
Person personByStaticMethod = 
    applicationContext.getBean("person-by-static-method", Person.class);

// 实例方法 实例化Bean
Person personByInstanceMethod = 
    applicationContext.getBean("person-by-instance-method", Person.class);

// FactoryBean 实例化Bean
// 注意:这里类型是写 Person.class
Person personByFactoryBean = 
    applicationContext.getBean("person-by-factory-bean", Person.class);

System.out.println(personByStaticMethod);
System.out.println(personByInstanceMethod);
System.out.println(personByFactoryBean);

System.out.println(personByStaticMethod == personByInstanceMethod);       // false
System.out.println(personByStaticMethod == personByFactoryBean);          // false

输出结果

4.2. 特殊方式

Java 提供了 ServiceLoader 来加载 接口 和 实现类。

ServiceLoader 源码中设置了路径:

private static final String PREFIX = "META-INF/services/";

所以在 classpath 下创建这个路径:

使用 ServiceLoader 来加载 实现类:

// 加载 接口 和 实现类
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
ServiceLoader<PersonFactory> serviceLoader = 
    ServiceLoader.load(PersonFactory.class, classLoader);

// 迭代输出实现类
// 定义多个相同的实现类会自动去重
// 有多个不同的实现类会逐一输出
Iterator<PersonFactory> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

注意:

  • 定义多个相同的实现类,最后只会加载一个!
  • 有多个不同的实现类会逐一输出。

输出结果,如下所示:

com.ymy.thinking.in.spring.bean.factory.DefaultPersonFactory@7ea987ac

4.2.1. ServiceLoaderFactoryBean

PersonFactory 有多个实现类。

ServiceLoaderFactoryBean 在 XML 配置元信息。

<bean id="personFactoryServiceLoader"
     class="org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean">
    <property name="serviceType" 
              value="com.ymy.thinking.in.spring.bean.factory.PersonFactory"/>
</bean>

ServiceLoaderFactoryBean 既然是 FactoryBean就有 FactoryBean 的特性。

根据源码可知,从 Ioc 容器获取到 ServiceLoaderFactoryBean ,拿到的结果是 ServiceLoader

public class ServiceLoaderFactoryBean 
    extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {

	@Override
	protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
		return serviceLoader;
	}

	@Override
	public Class<?> getObjectType() {
		return ServiceLoader.class;
	}

}

测试代码示例:

public class BeanSpecialInstanceDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        String configLocation = 
            "classpath:META-INF/special-bean-instance-context.xml";
        ApplicationContext applicationContext = 
            new ClassPathXmlApplicationContext(configLocation);

        // 依赖查找
        // 注意:这里 getBean 要写 ServiceLoader.class
        ServiceLoader<PersonFactory> serviceLoader = 
            applicationContext.getBean("personFactoryServiceLoader",
                                       ServiceLoader.class);

        System.out.println("== Java ServiceLoader ==");
        display(serviceLoader);

        System.out.println("== Spring ServiceLoaderFactoryBean ==");
        serviceLoaderDemo();
    }

    public static void serviceLoaderDemo() {
        // 加载 接口 和 实现类
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        ServiceLoader<PersonFactory> serviceLoader = 
            ServiceLoader.load(PersonFactory.class, classLoader);
        display(serviceLoader);
    }

    public static void display(ServiceLoader<PersonFactory> serviceLoader) {
        // 迭代输出实现类
        // 定义多个相同的实现类会自动去重
        Iterator<PersonFactory> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

输出结果:

5. 初始化 Spring Bean

Bean 初始化(Initialization)

  • @PostConstruct 标注方法(Java 提供)。
  • 实现 InitializationBean 接口。
  • 自定义初始化方法:
    • XML 配置:<bean init-method="init" .../>
    • Java 注解:@Bean(initMethod="init")
    • Java API:AbstractBeanDefinition#setInitMethodName(String)

5.1. 注解和接口初始化Bean

PersonFactory 的默认实现类 DefaultPersonFactory

以下是三种实现方法:

public class DefaultPersonFactory implements PersonFactory, InitializingBean {

    // 1: 基于 @PostConstructor 注解(Java提供)
    // 注意:该方法不能有参数
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct 初始化Bean: PersonFactory init...");
    }

    // 2: 自定义初始化方法
    // 这个方法不常用
    public void initPersonFactory() {
        System.out.println("自定义初始化方法: PersonFactory init...");
    }

    // 3: afterPropertiesSet 初始化
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet 初始化方法: PersonFactory init...");
    }
}

编写测试用例:

/**
 * Bean 初始化示例
 *
 * @author Ringo
 * @date 2021/8/26 22:41
 */
// IoC 容器会扫描到这个类
@Configuration
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 设置包扫描
        applicationContext.scan("com.ymy.thinking.in.spring.bean.initialization.**");
        // 启动 Spring 应用上下文
        applicationContext.refresh();
        // 依赖查找
        PersonFactory personFactory = applicationContext.getBean(PersonFactory.class);
        // 关闭 Spring 应用上下文
        applicationContext.close();
    }

    @Bean(initMethod = "initPersonFactory")
    public PersonFactory personFactory() {
        return new DefaultPersonFactory();
    }
}

运行结果:

5.2. Bean延迟初始化

Bean 延迟初始化(Lazy Initialization)

  • XML 配置:<bean lazy-init="true" />
  • Java 注解:@Lazy

思考:当某个 Bean 定义为延迟初始化,那么,Spring 容器返回的对象与非延迟的对象存在怎样的差异?

非延迟加载是在 Spring Application Context 初始化完成之前,就将 Bean 加载到 IoC 容器。

延迟加载是 Spring Application Context 初始化完成之后,由于依赖查找或者依赖注入,触发 Bean 的加载。

@Lazy 注解要配合 @Componet、@Bean 等注解使用,详情看 @Lazy 注解的说明即可。

6. Bean 销毁(Destroy)

Bean 销毁(Destroy)

  • @PreDestroy 标注方法(Java 提供)。
  • 实现 DisposableBean 接口的 destroy() 方法。
  • 自定义销毁方法:
    • XML 配置:<bean destroy="destroy".../>
    • Java 注解:@Bean(destroy="destroyMethod")
    • Java API:AbstractBeanDefinition#setDestroyMethodName(String)

写法参考 Bean 的初始化。

三种方法的顺序:

// 关闭 Spring 应用上下文才会触发 Bean 销毁的操作!
applicationContext.close();

7. GC Spring Bean

Bean 垃圾回收(GC)

  1. 关闭 Spring 容器(Application Context)。
  2. 执行 GC。
  3. Spring Bean 覆盖的 finalize() 方法被回调。

创建 PersonFactory 实现类。

public class RewriteFinalizePersonFactory implements PersonFactory, DisposableBean {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("当前对象 RewriteFinalizePersonFactory 正在被回收...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("当前对象 RewriteFinalizePersonFactory 正在被销毁...");
    }
}

测试程序:

/**
 * Bean 垃圾回收(GC)示例
 *
 * @author Ringo
 * @date 2021/8/27 22:52
 */
public class BeanGarbageCollectionDemo {

    public static void main(String[] args) throws IOException {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 注册 Bean. BeanGarbageCollectionDemo 相当于加了 @Configuration 注解
        applicationContext.register(BeanGarbageCollectionDemo.class);
        // 开启 Spring 应用上下文
        applicationContext.refresh();

        // 关闭 Spring 应用上下文
        applicationContext.close();

        // 强制触发 GC
        System.gc();

        // 阻塞程序, 防止 gc 还没完成程序就结束了!
        System.in.read();
     


    @Bean
    public PersonFactory personFactory() {
        return new RewriteFinalizePersonFactory();
    }
}

输出结果:

8. 面试题

8.1. 如何注册 Spring Bean?

通过 BeanDefinition 和 外部单体对象 来注册。

注册 外部单体对象 ?

// 创建 IoC 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 创建一个外部 PersonFactory 对象
PersonFactory personFactory = new DefaultPersonFactory();
SingletonBeanRegistry beanFactory = applicationContext.getBeanFactory();
// 注册外部单例对象
beanFactory.registerSingleton("personFactory", personFactory);
// 开启 Spring Application Context
applicationContext.refresh();
// 依赖查找
PersonFactory externalRegistryBean = applicationContext.getBean(PersonFactory.class);
PersonFactory lookUpBean = applicationContext.getBean(PersonFactory.class);
System.out.println(externalRegistryBean);
System.out.println(lookUpBean);

// 结果是 true
System.out.println("externalRegistryBean == lookUpBean? " + (externalRegistryBean == lookUpBean));

// 关闭 Spring Application Context
applicationContext.close();

输出结果:

8.2. 什么是 BeanDefinition?

回顾 "定义 Spring Bean" 和 "BeanDefinition 元信息" 。

BeanbDefition 接口表示的是关于 Bean 的定义的元信息,允许我们以 Get/Set 的方式来修改 Bean 定义元信息。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { 
    // 作用域:单例还是原型
    String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
    String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
    void setScope(@Nullable String scope);
    String getScope();
  
    // 设置当前 bean 定义的父级 bean 的名称(如果有)。
    void setParentName(@Nullable String parentName);
    String getParentName();
    
    // 是否是懒加载
    void setLazyInit(boolean lazyInit);
    boolean isLazyInit();
    
    // @Primary
    // 多个同类型Bean的时候, 注入优先级
    void setPrimary(boolean primary);
    boolean isPrimary();
    
    // ...
}

8.3. Spring 容器怎样管理注册 Bean?

三、Spring IoC 依赖查找

1. 单一类型依赖查找

单一类型依赖查找接口 — BeanFactory

  • 根据 Bean 名称查找
    • getBean(String)
    • Spring 2.5 覆盖默认参数:getBean(String, Object...)。(尽量不用!)
  • 根据 Bean 类型查找
    • Bean 实时查找
      • Spring 3.0 getBean(Class)
      • Spring 4.1 覆盖默认参数: getBean(Class, Object...)。(尽量不用!)
    • Spring 5.1 Bean 延迟查找
      • getBeanProvider(Class)
      • getBeanProvider(ResolvableType)
  • 根据 Bean 名称 + 类型查找:getBean(String, Class)
// BeanFactory 延迟查找
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

// ObjectProvider 继承自 ObjectFactory
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
    // ... 省略方法
}

getBeanProvider(Class) 的基本使用:

/**
 * {@link ObjectProvider} 延迟依赖查找示例
 *
 * @author Ringo
 * @date 2021/8/31 11:31
 */
public class ObjectProviderDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 包扫描
        applicationContext.scan("com.ymy.thinking.in.spring.dependency.lookup.**");

        // 开启 Spring 应用上下文
        applicationContext.refresh();

        // 通过 ObjectProvider 实现延迟依赖查找
        lookupByObjectProvider(applicationContext, String.class);

        // 关闭 Spring 应用上下文
        applicationContext.close();
    }

    private static <T> void lookupByObjectProvider(BeanFactory beanFactory, 
                                                   Class<T> requiredType) {
        ObjectProvider<T> objectProvider = beanFactory.getBeanProvider(requiredType);
        // 该方法来自于 ObjectFactory#getObject() 方法
        // ObjectFactory 接口可以实现 延迟依赖查找
        T object = objectProvider.getObject();
        System.out.println(object);
    }

    /**
    * 配置类, 配合包扫描就能解析这个注解
    */
    @Configuration
    public static class Config {
        @Bean
        public String hello() {
            return "Hello World";
        }
    }

}

2. 集合类型依赖查找

集合类型依赖查找接口 — ListableBeanFactory

  • 根据 Bean 类型查找
    • 获取同类型 Bean 名称列表。
      • getBeanNamesForType(Class)
      • Spring 4.2 getBeanNamesForType(ResolvableType)
    • 获取同类型 Bean 实例列表。
      • getBeansOfType(Class) 以及重载方法。
  • 通过注解类型查找
    • Spring 3.0 获取标注注解类型 Bean 名称列表。
      • getBeanNamesForAnnotation(Class<? extends Annotation>)
    • Spring 3.0 获取标注标注类型 Bean 实例列表。
      • getBeansWithAnnotation(Class<? extends Annotation>)
    • Spring 3.0 获取指定名称 + 标注类型 Bean 实例。
      • findAnnotationOnBean(String, Class<? extends Annotation>)。该方法会返回 Bean 上标注的注解。

创建自定义注解:

// RUNTIME 保证在运行时可以被反射到
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface Fine {
}

集合查找 Bean 示例:

public class DependencyLookupByCollectionTypeDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        // ApplicationContext 已经实现了 ListableBeanFactory
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        applicationContext.scan("com.ymy.thinking.in.spring.dependency.lookup.**");

        // 开启 Spring 应用上下文
        applicationContext.refresh();


        // 1: 获取同类型 Bean 名称列表
        String[] beanNamesForType =
            applicationContext.getBeanNamesForType(String.class);
        System.out.println("获取同类型Bean名称列表: " + 
                           Arrays.toString(beanNamesForType));

        // 2: 获取同类型 Bean 实例列表
        Map<String, String> beansOfTypeMap = 
            applicationContext.getBeansOfType(String.class);
        System.out.println("获取同类型 Bean 实例列表: " + beansOfTypeMap);

        // 3: 获得标注注解类型 Bean 名称列表
        String[] beanNamesForAnnotation = 
            applicationContext.getBeanNamesForAnnotation(Fine.class);
        System.out.println("获得标注注解类型 Bean 名称列表: " + 
                           Arrays.toString(beanNamesForAnnotation));

        // 4: 获得标注注解类型 Bean 实例列表
        Map<String, Object> beansWithAnnotation =
            applicationContext.getBeansWithAnnotation(Fine.class);
        System.out.println("获得标注注解类型 Bean 实例列表: ");
        beansWithAnnotation.forEach((k, v) -> System.out.println(k + "=" + v));

        // 关闭 Spring 应用上下文
        applicationContext.close();

    }

    @Fine
    @Configuration
    public static class Config {

        @Fine
        @Bean
        public String hello() {
            return "hello";
        }

        @Fine
        @Bean
        public String world() {
            return "world";
        }
    }

}

集合类型的 Bean 依赖查找结果:

3. 层次性依赖查找(IoC也能继承?)

层次性依赖查找接口 — HierarchicalBeanFactory

  • 获得父级 BeanFactoryBeanFactory getParentBeanFactory()
  • 层次性查找
    • 根据 Bean 名称查找:
      • 基于 containsLocalBean 方法实现。(该方法只会查找当前 IoC 容器中的 Bean,不会查询父级 BeanFactory 中的 Bean)。
    • 根据 Bean 类型查找实例列表:
      • 单一类型:BeanFactoryUtils#beanOfTypeIncludingAncestors
      • 集合类型:BeanFactoryUtils#beansOfTypeIncludingAncestors
    • 根据 Java 注解查找名称列表:
      • BeanFactoryUtils#beanNamesForTypeIncludingAncestors

创建 parent BeanFactory:

<!-- hierarchical-dependency-lookup-context.xml -->

<?xml version="1.0" encoding="UTF-8"?>
<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="hello" class="java.lang.String">
        <constructor-arg name="original" value="hello parent BeanFactory"/>
    </bean>
</beans>

测试案例:

/**
 * {@link HierarchicalBeanFactory} 层次性依赖查找 Bean 示例
 *
 * @author Ringo
 * @date 2021/8/31 16:50
 */
public class HierarchicalDependencyLookupDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 注册为 Bean
        applicationContext.register(HierarchicalDependencyLookupDemo.class);

        // 1: 获取 HierarchicalBeanFactory
        // ConfigurableListableBeanFactory 继承了 HierarchicalBeanFactory
        ConfigurableListableBeanFactory beanFactory = 
            applicationContext.getBeanFactory();

        // 这里返回 null
        System.out.println("当前 BeanFactory 的 Parent BeanFactory:" +
                beanFactory.getParentBeanFactory());

        // 2. 设置 Parent BeanFactory
        HierarchicalBeanFactory parentBeanFactory = createParentBeanFactory();
        beanFactory.setParentBeanFactory(parentBeanFactory);

        System.out.println("当前 BeanFactory 的 Parent BeanFactory:" +
                beanFactory.getParentBeanFactory());

        // 开启 Spring 应用上下文
        applicationContext.refresh();

        // 查看当前 BeanFactory 是否包含 hello 这个 Bean
        displayContainsLocalBean(beanFactory, "hello");            // false
        displayContainsLocalBean(parentBeanFactory, "hello");      // true

        // 先查子容器 再查父容器
        displayContainsBean(beanFactory, "hello");                 //  true
        displayContainsBean(parentBeanFactory, "hello");           // true

        // 关闭 Spring 应用上下文
        applicationContext.close();
    }


    // 既查当前 BeanFactory 又查父级 BeanFactory
    private static void displayContainsBean(HierarchicalBeanFactory beanFactory,
                                            String beanName) {
        System.out.println(containsBean(beanFactory, beanName));
    }

    // 递归查询(我们自己写的 containsBean的实现)
    public static boolean containsBean(HierarchicalBeanFactory beanFactory, 
                                       String beanName) {
        // 拿到父级的 IoC 容器
        BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
        // 父级 IoC 容器是 HierarchicalBeanFactory
        if (parentBeanFactory instanceof HierarchicalBeanFactory) {
            HierarchicalBeanFactory parentHierarchicalBeanFactory = 
                (HierarchicalBeanFactory) parentBeanFactory;
            if (containsBean(parentHierarchicalBeanFactory, beanName)) {
                return true;
            }
        }
        // 父级 IoC 容器不是 HierarchicalBeanFactory, 就只查询当前容器
        // containsLocalBean 只返回当前 IoC 容器中的 Bean
        // 不会去查询父级的 IoC 容器
        return beanFactory.containsLocalBean(beanName);
    }

    // 只查本地 BeanFactory
    private static void displayContainsLocalBean(HierarchicalBeanFactory beanFactory, 
                                                 String beanName) {
        System.out.println(beanFactory.containsLocalBean(beanName));
    }

    /**
     * 创建父级 {@link BeanFactory}
     */
    private static HierarchicalBeanFactory createParentBeanFactory() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        String location = 
            "classpath:META-INF/hierarchical-dependency-lookup-context.xml";
        reader.loadBeanDefinitions(location);
        return beanFactory;
    }

}

4. 延迟依赖查找(有用?)

Bean 延迟依赖查找接口:

  • org.springframework.beans.factory.ObjectFactory
  • org.springframework.beans.factory.ObjectProvider
    • Spring 5 对 Java 8 特性的扩展:
      • 函数式接口:
        • getIfAvailable(Supplier)
        • ifAvailable(Consumer)
      • Stream 扩展 - stream()

ObjectProvider接口API:

// ObjectProvider 接口

// 1: 容器中有 Bean 就获取, 没有就返回 null。
@Nullable
T getIfAvailable() throws BeansException;

// 2: 容器中有 Bean 就获取, 没有就返回指定对象。
default T getIfAvailable(Supplier<T> defaultSupplier) throws BeansException {
    T dependency = getIfAvailable();
    return (dependency != null ? dependency : defaultSupplier.get());
}

ObjectProvider接口的使用:

public class ObjectProviderIfAvailableDemo {

    public static void main(String[] args) {
        // 创建 IoC 容器
        AnnotationConfigApplicationContext applicationContext = 
            new AnnotationConfigApplicationContext();
        // 注册当前类到 IoC 容器
        applicationContext.register(ObjectProviderIfAvailableDemo.class);
        // 开启 Spring 应用上下文
        applicationContext.refresh();

        // 依赖查找
        lookupIfAvailable(applicationContext);
        System.out.println("=======================");
        lookupByStream(applicationContext);

        // 关闭 Spring 应用上lose();
    }

    // Stream 流式查找
    private static void lookupByStream(ApplicationContext applicationContext) {
        ObjectProvider<String> objectProvider = 
            applicationContext.getBeanProvider(String.class);
        // Stream 方式
        objectProvider.stream().forEach(System.out::println);
        // 迭代器方式
        objectProvider.iterator().forEachRemaining(System.out::println);
    }

    // IfAvailable 的方式查找
    private static void lookupIfAvailable(ApplicationContext applicationContext) {
        ObjectProvider<String> objectProvider =
            applicationContext.getBeanProvider(String.class);
        // 如果容器中有 Bean 就获取, 没有就返回 null
        String beanIfAvailable = objectProvider.getIfAvailable();
        // 如果容器中有 Bean 就获取, 没有就返回指定的值
        String ifAvailable = objectProvider.getIfAvailable(() -> "aabb");
        System.out.println(beanIfAvailable);
        System.out.println(ifAvailable);
    }

    @Bean
    @Primary
    public String fine() {
        return "fine";
    }

    @Bean
    public String message() {
        return "message";
    }
}

输出结果:

5. 安全依赖查找

依赖查找安全性对比:

依赖查找类型 代表实现 是否安全
单一类型查找 BeanFactory#getBean
ObjectFactory#getObject
ObjectProvider#getIfAvailable
集合类型查找 ListableBeanFactory#getBeansOfType
ObjectProvider#stream

注意:层次性依赖查找的安全性取决于其扩展的单一或集合类型的 BeanFactory 接口。

6. IoC内建可查找依赖

AbstractApplicationContext 内建可查找的依赖。

Bean名称 Bean实例 使用场景
environment Environment 对象 外部化配置以及 Profiles
systemProperties java.util.Properties 对象 Java 系统属性
systemEnvironment java.util.map 对象 操作系统环境变量
messageSource MessageSource 对象 国际化文案
lifecycleProcessor LifecycleProcessor 对象 Lifecycle Bean 处理器
applicationEventMulticaster ApplicationEventMulticaster 对象 Spring 事件广播器

7. 依赖查找中的经典异常

异常类型 触发条件(举例) 场景举例
NoSuchBeanDefinitionException 当查找的 Bean 不存在于 IoC 容器时 BeanFactory#getBean() ObjectFactory#getObject()
NoUniqueBeanDefinitionException 按类型依赖查找时,IoC 容器中存在多个 Bean 的实例 BeanFactory#getBean(Class)
BeanInstantiationException 当 Bean 所对应的类型非具体类时 BeanFactory#getBean()
BeanCreationException 当 Bean 初始化过程中 Bean 初始化方法执行异常
BeanDefinitionStoreException 当 BeanDefition 配置元信息非法时 XML 配置资源无法打开时

8. 面试题

8.1. ObjectFactory和BeanFactory区别?

ObjectFactory、BeanFactory 都提供依赖查找的能力。

不过 ObjectFactory 仅仅关注一个或者一种类型的 Bean 依赖查找,并且自身不具备依赖查找的能力,能力则由 BeanFactory 输出。

BeanFactory 和其字类则提供了单一类型、集合类型以及层次性等多种依赖查找方式。

8.2. BeanFactory.getBean 是否线程安全?

线程安全(原因不清楚...)

四、Spring IoC依赖注入

1. 依赖注入的模式和类型

手动模式 — 配置或者编程的方式,提前安排注入规则

  • XML 资源配置元信息。
  • Java 注解配置元信息。
  • API 配置元信息。

自动模式 — 实现方提供依赖自动关联的方式,按照内建的注入规则

  • Autowiring(自动绑定)。

依赖注入类型

1.1. Setter 方法注入

手动模式:

  • XML 资源配置元信息。
  • Java 注解配置元信息。
  • API 配置元信息。

自动模式:

  • byName。
  • byType。

下面举一个 Java 注解注入的例子:

/**
 * 基于 Java 注解的依赖注入
 *
 * @author Ringo
 * @date 2021/9/29 0:29
 */
public class AnnotationDependencySetterInjectionDemo {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AnnotationDependencySetterInjectionDemo.class);
        
        System.out.println(applicationContext.getBean(UserHolder.class));
    }

    @Bean
    public User user() {
        User user = new User();
        user.setName("李白");
        user.setAge(19);
        return user;
    }

    @Bean
    public UserHolder userHolder(User user) {
        return new UserHolder(user);
    }

}

1.2. 构造器注入:官方推荐?

手动模式:

  • XML 资源配置元信息。
  • Java 注解配置元信息。
  • API 配置元信息。

自动模式:

  • constructor

构造器注入:

@Controller
public class FooController {
   
  private final FooService fooService;
   
  @Autowired
  public FooController(FooService fooService) {
      this.fooService = fooService;
  }
   
  //使用方式上同,略
}

如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错

使用构造器注入的好处:

  1. 保证依赖不可变(final关键字)
  2. 保证依赖不为空(省去了我们对其检查)
  3. 保证返回客户端(调用)的代码的时候是完全初始化的状态
  4. 避免了循环依赖
  5. 提升了代码的可复用性

1.3. 字段注入

手动模式:Java注解配置元信息

  • @Autowired。
  • @Resource。
  • @Inject(可选)。

注意:@Autowired 会忽略静态字段。

@Component
public class Test {
    
    // @Autowired 主要处理实例字段
    // @Autowired 会忽略静态字段,无法自动注入。
    @Autowired
    private static User user;
    
}

1.4. 方法注入:@Autowired用在方法上?

/**
 * {@link Autowired} {@link Resource} 基于方法的依赖注入
 *
 * @author Ringo
 * @date 2021/9/30 12:03
 */
public class AnnotationDependencyMethodInjectionDemo {

    private UserHolder userHolder;

    private UserHolder userHolder2;

    // =============================================================
    // @Autowired @Resource @Bean 都可以实现方法注入
    @Autowired
    public void init1(UserHolder userHolder) {
        this.userHolder = userHolder;
    }

    @Resource
    public void init2(UserHolder userHolder) {
        this.userHolder2 = userHolder;
    }


    @Bean
    public UserHolder userHolder(User user) {
        return new UserHolder(user);
    }

    // =============================================================

    @Bean
    public User user() {
        return new User("zsf", 18);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext(AnnotationDependencyMethodInjectionDemo.class);
        
        AnnotationDependencyMethodInjectionDemo demo =
                applicationContext.getBean(AnnotationDependencyMethodInjectionDemo.class);
        
        System.out.println(demo.userHolder);
        System.out.println(demo.userHolder2);
        System.out.println(demo.userHolder == demo.userHolder2);   // true
    }
}

1.5. 接口回调注入

/**
 * 基于 {@link Aware} 接口回调的依赖注入示例
 *
 * @author Ringo
 * @date 2021/9/30 13:06
 */
public class AwareInterfaceDependencyInjectionDemo implements BeanFactoryAware, ApplicationContextAware {

    private static BeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(AwareInterfaceDependencyInjectionDemo.class);

        System.out.println(beanFactory == context.getBeanFactory());    // true
        System.out.println(applicationContext == context);              // true
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        AwareInterfaceDependencyInjectionDemo.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) 
        throws BeansException {
        AwareInterfaceDependencyInjectionDemo.applicationContext = applicationContext;
    }
}

1.6. 集合类型注入

集合类型注入

1.7. 限定注入@Qualifier:支持分组?

限定注入

@Qualifier 不仅可以用在 Bean 的依赖注入上,还可以用在 Bean 的声明上。

@Qualifier 也可以用在注解上,派生新的注解,是对 @Qualifier 注解的扩展。

有没有 @Qualifier 可以对 Bean 进行分组。

/**
 * 用户组注解, 扩展 {@link Qualifier}
 *
 * @author Ringo
 * @date 2021/9/30 14:37
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier 	// 可以标注在 Annotation 上
public @interface UserGroup {
}
/**
 * {@link Qualifier} 限定注入案例
 *
 * @author Ringo
 * @date 2021/9/30 14:13
 * @see Qualifier
 * @see UserGroup
 */
public class QualifierAnnotationDependencyInjectionDemo {

    @Autowired
    private User user;   // user2 -> @Primary

    @Autowired
    @Qualifier("user1")  // 指定 Bean name
    private User namedUser;

    // 整体应用上下文存在 4 个 User 类型的 Bean
    // user1
    // user2
    // user3 -> @Qualifier
    // user4 -> @Qualifier

    @Autowired
    private List<User> allUsers;    // 注入所有的 User 类型的 Bean

    @Autowired
    @Qualifier
    private List<User> qualifiedUsers; // 4 Beans = user1 + user2 + user3 + user4

    @Autowired
    @UserGroup
    private List<User> groupedUsers; // 2 Bean = user3 + user4

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext =
                new AnnotationConfigApplicationContext();
        applicationContext.register(QualifierAnnotationDependencyInjectionDemo.class);
        applicationContext.refresh();

        QualifierAnnotationDependencyInjectionDemo demo =
                applicationContext.getBean(QualifierAnnotationDependencyInjectionDemo.class);

        System.out.println("demo.user = " + demo.user);
        System.out.println("demo.namedUser = " + demo.namedUser);
        System.out.println("demo.allUsers = " + demo.allUsers);
        System.out.println("demo.qualifiedUsers = " + demo.qualifiedUsers);
        System.out.println("demo.groupedUsers = " + demo.groupedUsers);
    }

    @Bean
    @Qualifier
    public User user1() {
        return new User("zs", 19);
    }

    @Bean
    @Primary
    @Qualifier
    public User user2() {
        return new User("ls", 20);
    }

    @Bean
    @UserGroup  // 进行逻辑分组
    public User user3() {
        return createUser("007");
    }

    @Bean
    @UserGroup // 进行逻辑分组
    public User user4() {
        return createUser("008");
    }

    // 工厂方法
    public static User createUser(String name) {
        User user = new User();
        user.setName(name);
        return user;
    }

}

1.8. 延迟注入

延迟依赖注入

@Autowired
private User user;  // 实时注入

@Autowired
private ObjectProvider<User> userObjectProvider; // 延迟注入

@Autowired
private ObjectFactory<User> userObjectFactory;  // ObjectFactory 延迟注入

1.9. 依赖处理过程

1.10. @Autowired注入规则和原理

AutowiredAnnotationBeanPostProcessor

@Autowired注入过程:

  • 元信息解析。
  • 依赖查找。
  • 依赖注入(字段、方法)。

1.11. Java 通用注解

CommonAnnotationBeanPostProcessor

注入注解:

  • javax.xml.ws.WebServiceRef。
  • javax.ejb.EJB。
  • javax.annotation.Resource。

生命周期注解:

  • javax.annotation.PostConstruct。
  • javax.annotation.PreDestroy。

1.12. 自定义DI注解

/**
 * 扩展DI的最优方式: 这样实现新旧注解都可以使用
 * <p>
 * static 静态注入会对 Bean 注入优先级做提升
 */
@Bean
@Order(Ordered.LOWEST_PRECEDENCE - 3)
public static AutowiredAnnotationBeanPostProcessor beanPostProcessor() {
    AutowiredAnnotationBeanPostProcessor beanPostProcessor =
        new AutowiredAnnotationBeanPostProcessor();
    // 替换原有的注解处理, 使用新注解 @Injected
    beanPostProcessor.setAutowiredAnnotationType(Injected.class);
    return beanPostProcessor;
}

1.13. 面试题

13.1. 多少种依赖注入方式

构造器注入、Setter注入、字段注入、方法注入、接口回调注入。

13.2. 你偏好构造器注入还是 Setter 注入

两种依赖注入的方式都可以用,如果必须依赖的话,那么推荐构造器注入,Setter 注入用于可选依赖。

13.3. Spring依赖注入来源

五、Spring IoC 依赖来源

1. 依赖查找的来源

Spring BeanDefinition:配置元数据

  • <bean id="user" class="">
  • @Bean public class User user() {}
  • BeanDefinitionBuilder

单例对象:API 实现。

2. 依赖注入来源

九、Spring Bean生命周期

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

spring、IoC的学习 展开 收起
Java
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/RingoTangs/spring-lessons.git
git@gitee.com:RingoTangs/spring-lessons.git
RingoTangs
spring-lessons
spring-lessons
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891