Spring 依赖查找知识点总结

前言

源码在我github的guide-spring仓库中,可以克隆下来 直接执行。

我们本文主要来介绍依赖查找的使用示例

依赖查找

什么是依赖查找

依赖查找并不是 Spring 框架特有的概念,它是一种在软件开发中获取依赖对象的方式。它通常用于获取运行时需要的服务、组件或其他对象的引用。在面向对象编程中,依赖通常体现为一个对象需要另一个对象的服务或功能。

在不同的编程框架和容器中,依赖查找的方式可能会有所不同。我们简单罗列一些常见的依赖查找的例子:

Java中的依赖查找

  • 在纯 Java 环境中,依赖查找通常通过构造函数、方法参数或其他手段来获得依赖对象的引用。
  • 例如,通过在一个对象的构造函数中传递另一个对象的引用:
java 复制代码
public class MyClass {
    private DependencyClass dependency;

    public MyClass(DependencyClass dependency) {
        this.dependency = dependency;
    }

    // ...
}

Spring框架中的依赖查找

  • 在Spring框架中,依赖查找通常通过 Spring 容器来实现。你可以使用ApplicationContextor BeanFactory 来获取所需的 Bean 。

    java 复制代码
    public class UseDependencyLookupDemo {
    
        public static void main(String[] args) throws Exception {
            BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup.xml");
            // 1. 实时查找
            realtimeLookup(beanFactory);
        }
        private static void realtimeLookup(BeanFactory beanFactory) {
            // 名称+类型
            User user = beanFactory.getBean("user", User.class);
            System.out.println("实时查找: " + user);
        }
    }
  • 或者,通过在类中使用@Autowired注解来自动注入依赖:

    kotlin 复制代码
    @Service
    public class MyService {
        @Autowired
        private DependencyClass dependency;
    
        // ...
    }

Java EE中的依赖查找:

  • 在Java EE环境中,你可以使用JNDI(Java Naming and Directory Interface)进行依赖查找。通过JNDI,你可以在运行时查找和获取命名对象。

    java 复制代码
    public class JNDIDependencyLookupDemo {
        public static void main(String[] args) throws NamingException {
            // 设置JNDI环境属性
            Properties properties = new Properties();
            properties.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
            properties.put(Context.PROVIDER_URL, "file:/META-INF/jndi");
    
            // 初始化InitialContext
            Context initialContext = new InitialContext(properties);
    
            // 在文件系统上查找一个名为 "user" 的对象
            User user = (User) initialContext.lookup("user");
    
            System.out.println("JNDI Lookup Result: " + user);
        }
    }

依赖查找的方式

依赖查找的方式有很多,我们先看下 BeanFactory 的 接口定义:

java 复制代码
public interface BeanFactory {

	Object getBean(String name) throws BeansException;

	<T> T getBean(String name, Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
  
	<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

}

可以看出上述定义,我们可以通过 Bean 名称、Bean 名称 + 类型、类型等方式进行依赖查找 Bean。下面我们分别从单一类型、集合类型、层次类型、延迟等方式依次展示依赖查找的示例。

单一类型的依赖查找

单一类型的查找,需要要求容器中同一类型的Bean只能有一个为 primary (BeanDefinition中的概念),我们可以看下 xml 配置示例

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="com.markus.spring.ioc.overview.domain.User">
        <property name="id" value="1"/>
        <property name="username" value="markus zhang"/>
    </bean>
		<!-- 当有多个 User 时,需要指出 其中一个 Bean 的 primary属性为 true 否则会出现 NoUniqueBeanDefinitionException -->
    <bean id="user2" class="com.markus.spring.ioc.overview.domain.User" lazy-init="true" primary="true">
        <property name="id" value="2"/>
        <property name="username" value="markus zhang"/>
    </bean>

</beans>

我们来看下使用示例

java 复制代码
public class UseDependencyLookupDemo {

    public static void main(String[] args) throws Exception {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup.xml");

        typeLookup(beanFactory);
    }

    /**
     * ========================按照 Bean 类型查找========================
     */

    /**
     * 单个Bean类型查找
     *
     * @param beanFactory
     */
    private static void typeLookup(BeanFactory beanFactory) {
        User user = beanFactory.getBean(User.class);
        System.out.println(user);
    }
}

集合类型的依赖查找

与单一类型查找的区别在于,它不需要指定 primary 并且 返回一个 Map<String,T> 对象,key 为 Bean 的名称,value 为 Bean 实例。

java 复制代码
public class UseDependencyLookupDemo {

    public static void main(String[] args) throws Exception {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup.xml");
        // 4. 按照类型查找多个Bean
        collectionLookup(beanFactory);
    }

    /**
     * 根据集合类型查找
     */
    private static void collectionLookup(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
            Map<String, User> userMap = listableBeanFactory.getBeansOfType(User.class);
            userMap.forEach((beanName, user) -> System.out.println("Bean name: " + beanName + ", User: " + user));
        }
    }
}

层次类型的依赖查找

层次性依赖查找,体现在父子容器中,我们一般可能体会不到,实际上 Spring MVC 的底层就涉及父子容器的概念,即 Root ApplicationContext 和 Dispatcher-Servlet ApplicationContext。这里不展开了。我们通过一个简单的示例来展示层次性依赖查找 Bean

java 复制代码
package com.markus.spring.dependency.lookup;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @author: markus
 * @date: 2023/12/17 10:23 PM
 * @Description: {@link HierarchicalBeanFactory}
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class HierarchicalBeanFactoryDependencyDemo {
    public static void main(String[] args) {
        ConfigurableListableBeanFactory subBeanFactory = new DefaultListableBeanFactory();
        // 设置父容器
        subBeanFactory.setParentBeanFactory(createParent());

        // 展示 仅在当前 Bean 容器中是否 存在
        System.out.println(displayContainBean(subBeanFactory, "user", true));
        // 展示 父子 Bean 容器中是否 存在(体现出 可继承 BeanFactory 的示例 即 HierarchicalBeanFactory)
        System.out.println(displayContainBean(subBeanFactory, "user", false));

    }

    private static boolean displayContainBean(ConfigurableListableBeanFactory beanFactory, String beanName, boolean onlyLocal) {
        boolean result = beanFactory.containsLocalBean(beanName);
        if (!onlyLocal) {
            if (!result) {
                BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
                if (parentBeanFactory != null) {
                    result = parentBeanFactory.containsBean(beanName);
                }
            }
        }
        return result;
    }

    private static ConfigurableListableBeanFactory createParent() {
        ConfigurableListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
        BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) parentBeanFactory);

        String location = "classpath:/META-INF/dependency-lookup.xml";
        // 加载 父容器 的 Bean 实例
        beanDefinitionReader.loadBeanDefinitions(location);
        return parentBeanFactory;
    }
}

延迟依赖查找

延迟依赖查找通常体现在懒加载 Bean 的场景,比如一些大资源的Bean希望在使用到的时候才会触发初始化以达到降低服务启动时间的目的,这个时候就可以使用懒加载模式,而在我们依赖查找的时候,使用延迟依赖查找的时候,也不会触发 Bean 的初始化,只有在真正使用到对象的时候才会触发初始化。(ps 比较绕,我们直接看例子)

因为 Bean 元信息配置比较特殊,我把 xml 配置也展示出来

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="user2" class="com.markus.spring.ioc.overview.domain.User" lazy-init="true" primary="true">
        <property name="id" value="2"/>
        <property name="username" value="markus zhang"/>
    </bean>

    <bean id="factoryBean" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName" value="user2"/>
    </bean>

</beans>

使用示例

java 复制代码
public class UseDependencyLookupDemo {

    public static void main(String[] args) throws Exception {
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup.xml");

        lazyLookup(beanFactory);
    }

    /**
     * 延迟查找
     */
    private static void lazyLookup(BeanFactory beanFactory) throws Exception {
        @SuppressWarnings("unchecked")
        ObjectFactory<User> factoryBean = (ObjectFactory<User>) beanFactory.getBean("factoryBean");
        System.out.println("延迟生效中....");
        System.out.println("延迟查找: " + factoryBean.getObject());
    }
 }

Spring内建可查找的依赖

除了我们自己配置的Bean,我们还可以查找 Spring 框架内 注册的单例 Bean。具体如下:

  • environment Environment 外部化配置以及Profiles
  • systemProperties Properties Java系统属性
  • systemEnvironment Map<String,String> 操作系统环境变量
  • messageSource MessageSource 国家化文案
  • lifecycleProcessor LifecycleProcessor Lifecycle Bean 处理器
  • applicationEventMulticaster ApplicationEventMulticaster Spring 事件广播器
  • internalConfigurationAnnotationProcessor ConfigurationClassPostProcessor 处理 Spring 的配置类
  • internalAutowiredAnnotationProcessor AutowiredAnnotationBeanPostProcessor 处理 @Autowired 以及 @Value、@Inject注解
  • internalCommonAnnotationProcessor CommonAnnotationBeanPostProcessor 处理 JSR-250 注解,如 @Resource、@PostConstruct等
  • internalEventListenerProcessor EventListenerMethodProcessor 处理标注 @EventListener 的 Spring 事件监听方法
  • internalEventListenerFactory DefaultEventListenerFactory 处理@EventListener 事件监听方法适配为 ApplicationListener

我们来看下示例:

java 复制代码
package com.markus.spring.dependency.lookup;

import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.LifecycleProcessor;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.DefaultEventListenerFactory;
import org.springframework.context.event.EventListenerMethodProcessor;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;

import java.util.Properties;

/**
 * @author: markus
 * @date: 2023/12/17 10:54 PM
 * @Description: Spring 内建依赖的 依赖查找示例
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SpringInternalBeanDependencyLookDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/META-INF/dependency-lookup.xml");

        displaySpringInternalBean(context, Environment.class);
        displaySpringInternalBean(context, Properties.class);
        displaySpringInternalBeanByName(context, "systemEnvironment");
        displaySpringInternalBean(context, MessageSource.class);
        displaySpringInternalBean(context, LifecycleProcessor.class);
        displaySpringInternalBean(context, ApplicationEventMulticaster.class);

        // 关闭 Spring 容器上下文
        context.close();

        // 基于 注解驱动 的应用上下文
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(SpringInternalBeanDependencyLookDemo.class);
        annotationConfigApplicationContext.refresh();

        displaySpringInternalBean(annotationConfigApplicationContext, ConfigurationClassPostProcessor.class);
        displaySpringInternalBean(annotationConfigApplicationContext, AutowiredAnnotationBeanPostProcessor.class);
        displaySpringInternalBean(annotationConfigApplicationContext, CommonAnnotationBeanPostProcessor.class);
        displaySpringInternalBean(annotationConfigApplicationContext, EventListenerMethodProcessor.class);
        displaySpringInternalBean(annotationConfigApplicationContext, DefaultEventListenerFactory.class);

        annotationConfigApplicationContext.close();
    }

    private static void displaySpringInternalBean(ApplicationContext context, Class<?> type) {
        Object bean = context.getBean(type);
        System.out.println(bean);
    }

    private static void displaySpringInternalBeanByName(ApplicationContext context, String beanName) {
        Object bean = context.getBean(beanName);
        System.out.println(bean);
    }
}

可以看到上面我们引入了基于 Xml 驱动的Spring应用上下文以及基于 注解 驱动的Spring应用上下文来实现 Spring 内建 Bean 的依赖查找。

ps: 如果一个默认的 ClassPathXmlAppplicationContext 不会包含ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor等这些注解的依赖,需要我们在 xml 配置文件中开启 注解启动,才会注册进 Spring IoC容器中。

大家可能会有疑问,这些 Spring 内建的 Bean 是什么时候被注册进去呢?这里给下源码位置,感兴趣的可以自行查看:

  • org.springframework.context.support.AbstractApplicationContext#prepareBeanFactory
  • org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

依赖查找的常见异常

下面这些就是场景的在使用依赖查找的时候可能会触发的异常,都是 BeansException 的子类型。场景比较清晰,这里就不写具体的示例了。

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

本文总结

好了,到这就基本上把 Spring 依赖查找相关的知识点就总结完了,本文我们主要总结了依赖查找的几种方式,包括单一类型、集合类型、层次性、延迟性以及 Spring 内建 Bean 的依赖查找,并给出了 Spring 内建 Bean 注册的源码位置,最后提到了依赖查找的几个常见的异常,并给出了常见场景触发的条件。

关于 Spring 依赖 还有依赖注入、依赖来源等知识 后面会跟进梳理。

以上也是我在学习 Spring 时参考的资料并加上了自己的理解,如有错误,欢迎交流。

相关推荐
橙序员小站3 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德3 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆5 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
开心就好20256 小时前
UniApp开发应用多平台上架全流程:H5小程序iOS和Android
后端·ios
悟空码字6 小时前
告别“屎山代码”:AI 代码整洁器让老项目重获新生
后端·aigc·ai编程
小码哥_常6 小时前
大厂不宠@Transactional,背后藏着啥秘密?
后端
奋斗小强6 小时前
内存危机突围战:从原理辨析到线上实战,彻底搞懂 OOM 与内存泄漏
后端
小码哥_常7 小时前
Spring Boot接口防抖秘籍:告别“手抖”,守护数据一致性
后端
心之语歌7 小时前
基于注解+拦截器的API动态路由实现方案
java·后端
None3217 小时前
【NestJs】基于Redlock装饰器分布式锁设计与实现
后端·node.js