深入挖掘Spring系列 -- 依赖查找背后的细节(下)

简介: 深入挖掘Spring系列 -- 依赖查找背后的细节(下)

Spring内部的层次性查找


Spring内部的上下文其实是分有等级的,来看看下边这张图:


网络异常,图片无法展示
|


在Spring容器内部,A容器内部含有Bean-A,B容器继承了A容器,那么按道理来说,B容器也应该有权利获取到A容器内部的Bean。这样的好处在于减少了额外存储Bean的开销。


接下来我们通过实战案例来深入理解下层次性Spring容器是个怎么样的存在。

通过这段代码来建立一个父亲级别Spring容器:


package org.idea.spring.look.up.factory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * 父类ioc容器 这里面的ioc容器只包含有ParentBean这个类
 *
 * @Author linhao
 * @Date created in 8:46 上午 2021/4/11
 */
public class ParentIocContainer {
    public static AnnotationConfigApplicationContext applicationContext = null;
    class ParentBean {
        int id;
        public ParentBean(){
            System.out.println("this is no arg init");
        }
        @Override
        public String toString() {
            return "ParentBean{" +
                    "id=" + id +
                    '}';
        }
    }
    public ApplicationContext getAndStartApplicationContext(){
       applicationContext = new AnnotationConfigApplicationContext();
       applicationContext.register(ParentIocContainer.class);
       //需要支持无参构造函数
       applicationContext.registerBean("parentBean",ParentBean.class);
       applicationContext.refresh();
       return applicationContext;
    }
    public static void main(String[] args) {
        ParentIocContainer parentIocContainer = new ParentIocContainer();
        ApplicationContext applicationContext = parentIocContainer.getAndStartApplicationContext();
        String[] str = applicationContext.getBeanNamesForType(ParentBean.class);
        for (String beanName : str) {
            System.out.println(beanName);
        }
    }
}
复制代码


然后通过一个案例代码分析层次级别的bean查找案例:


package org.idea.spring.look.up.factory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * 层次性的依赖查找 {@link org.springframework.beans.factory.HierarchicalBeanFactory}
 *
 * @Author linhao
 * @Date created in 10:55 下午 2021/4/10
 */
public class SpringHierarchicalLookUpDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(SpringHierarchicalLookUpDemo.class);
        applicationContext.refresh();
        ParentIocContainer parentIocContainer = new ParentIocContainer();
        ApplicationContext parentApplicationContext = parentIocContainer.getAndStartApplicationContext();
        // ConfigurableListableBeanFactory -> ConfigurableBeanFactory -> HierarchicalBeanFactory
        ConfigurableListableBeanFactory configurableListableBeanFactory = applicationContext.getBeanFactory();
        System.out.println("此时的父类BeanFactory为:" + configurableListableBeanFactory.getParentBeanFactory());
        configurableListableBeanFactory.setParentBeanFactory(parentApplicationContext);
        System.out.println("此时的父类BeanFactory为:" + configurableListableBeanFactory.getParentBeanFactory());
        ParentIocContainer.ParentBean parentBean = (ParentIocContainer.ParentBean) configurableListableBeanFactory.getBean("parentBean");
        System.out.println(parentBean);
        isContainedBean(configurableListableBeanFactory, "parentBean");
        displayContainsBean(configurableListableBeanFactory, "parentBean");
    }
  /**
     * 这里是子类可以获取自己和父类层次内部的bean,如果是使用containsLocalBean方法的话就只能判断当前所在层次的容器上下文
     *
     * @param beanFactory
     * @param beanName
     */
    public static void isContainedBean(HierarchicalBeanFactory beanFactory, String beanName) {
        System.out.println("getBean is " + beanFactory.getBean(beanName));
        System.out.printf("contained is [%s] ,beanFactory is [%s],beanName is [%s]\n", beanFactory.containsLocalBean(beanName), beanFactory, beanName);
    }
    /**
     * 查找关于父类容器内部的bean
     *
     * @param beanFactory
     * @param beanName
     */
    private static void displayContainsBean(HierarchicalBeanFactory beanFactory, String beanName) {
        System.out.printf("contained is [%s] ,beanFactory is [%s],beanName is [%s]\n", isContainedBeanInHoldApplication(beanFactory, beanName), beanFactory, beanName);
    }
    /**
     * 使用递归判断 -- 自上到下判断父类容器是否含有bean
     *
     * @param hierarchicalBeanFactory
     * @param beanName
     * @return
     */
    public static boolean isContainedBeanInHoldApplication(HierarchicalBeanFactory hierarchicalBeanFactory, String beanName) {
        BeanFactory parentBeanFactory = hierarchicalBeanFactory.getParentBeanFactory();
        if (parentBeanFactory instanceof HierarchicalBeanFactory) {
            HierarchicalBeanFactory parentHierarchicalBeanFactory = HierarchicalBeanFactory.class.cast(parentBeanFactory);
            if (isContainedBeanInHoldApplication(parentHierarchicalBeanFactory, beanName)) {
                return true;
            }
        }
        return hierarchicalBeanFactory.containsBean(beanName);
    }
}
复制代码


代码里面有几个点我这里做些细节剖析:


ConfigurableListableBeanFactory的类结构图设计如下:


网络异常,图片无法展示
|


从名字可以看出,这是一个BeanFactory的子类,而且支持扩展(Configurable)和集合查找(Listable),同时还继承了层次性(HierarchicalBeanFactory)的特点。


网络异常,图片无法展示
|


在ConfigurableBeanFactory中支持这么一个功能:


void setParentBeanFactory(BeanFactory parentBeanFactory) throws IllegalStateException;
复制代码


可以手动设置当前的BeanFactory的父类BeanFactory。


层次性的BeanFactory具有这么两个特点:


网络异常,图片无法展示
|


从他的基本接口设计就可以看出两点:


  1. 支持获取当前Spring容器的父容器
  2. 支持判断当前容器所处的层级是否包含某个Bean


在案例代码中的对于父类工厂的递归遍历设计思路实际上参考了Spring5中的org.springframework.beans.factory.BeanFactoryUtils#beanNamesForTypeIncludingAncestors(org.springframework.beans.factory.ListableBeanFactory, java.lang.Class<?>) 的设计思路,设计目的也是为了对父类进行递归判断某些Bean是否存在。


什么场景下会使用到层次性的HierarchicalBeanFactory?


比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。


Spring内部的延迟查找


Bean 延迟依赖查找接口


org.springframework.beans.factory.ObjectFactory
org.springframework.beans.factory.ObjectProvider
复制代码


Spring 5 对 Java 8 特性扩展–函数式接口


getIfAvailable(Supplier)

ifAvailable(Consumer)

Stream 扩展 - stream()

getIfAvailable


我们先在代码中通过注解定义一个 User


@Bean
    public User user() {
        return User.createUser("ifAvailable-user");
    }
复制代码


下面方法中User::createUser 只是提供兜底实现,当获取的对象为空时不会出现异常抛出。


private static void lookupGetIfAvailable(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
//        User ifAvailable = beanProvider.getIfAvailable(()->User.createUser());
        User ifAvailable = beanProvider.getIfAvailable(User::createUser);
        System.out.println(ifAvailable);
    }
复制代码


ifAvailable


通过 Consumer 的方式消费掉,我们这里直接打印


private static void lookupIfAvailable(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<User> beanProvider = applicationContext.getBeanProvider(User.class);
        beanProvider.ifAvailable(System.out::println);
    }
复制代码


Stream 扩展


private static void lookupByStreamOps(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<String> beanProvider = applicationContext.getBeanProvider(String.class);
//        Iterable<String> stringIterable = beanProvider;
//        for (String str : stringIterable) {
//            System.out.println(str);
//        }
        beanProvider.stream().forEach(System.out::println);
    }
复制代码


完整的代码案例:


package org.idea.spring.look.up.lazy;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import java.util.Iterator;
/**
 * @Author linhao
 * @Date created in 3:58 下午 2021/4/11
 */
public class LazyLoadBean {
    static class Bus {
        int id;
        @Override
        public String toString() {
            return "Bus{" +
                    "id=" + id +
                    '}';
        }
        public Bus(int id) {
            this.id = id;
        }
        public static Bus buildBus(){
            return new Bus(1);
        }
    }
    @Bean
    @Primary
    public String helloWorld(){
        return "hello world";
    }
    @Bean
    public String message(){
        return "message";
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(LazyLoadBean.class);
//        annotationConfigApplicationContext.register(Bus.class);
        annotationConfigApplicationContext.refresh();
        // ObjectProvider 可以不需要通过initLazy这种配置方式处理懒加载问题,借助程序实现
        lookupByObjectProvider(annotationConfigApplicationContext);
        lookUpByPrimary(annotationConfigApplicationContext);
        lookUpIter(annotationConfigApplicationContext);
        lookUpStream(annotationConfigApplicationContext);
    }
    public static void lookupByObjectProvider(AnnotationConfigApplicationContext annotationConfigApplicationContext){
        ObjectProvider<Bus> objectProvider = annotationConfigApplicationContext.getBeanProvider(Bus.class);
        //类型安全的策略
        Bus bus = objectProvider.getIfAvailable(Bus::buildBus);
        System.out.println(bus.toString());
    }
    public static void lookUpByPrimary(AnnotationConfigApplicationContext annotationConfigApplicationContext){
        ObjectProvider<String> objectProvider = annotationConfigApplicationContext.getBeanProvider(String.class);
        System.out.println(objectProvider.getObject());
    }
    public static void lookUpIter(AnnotationConfigApplicationContext annotationConfigApplicationContext) {
        ObjectProvider<String> objectProvider = annotationConfigApplicationContext.getBeanProvider(String.class);
        Iterator<String> iterator = objectProvider.iterator();
        while(iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
        }
    }
    public static void lookUpStream(AnnotationConfigApplicationContext annotationConfigApplicationContext) {
        ObjectProvider<String> objectProvider = annotationConfigApplicationContext.getBeanProvider(String.class);
        objectProvider.stream().forEach(System.out::println);
    }
}
复制代码


依赖查找安全性问题



关于Spring内部的bean依赖查找我们上边基本上已经梳理完毕了,主要分为了Bean的单一查找,集合查找,层次性查找三种类型。那么在使用Spring内部的Api进行bean查找的时候,如果没有对应的bean,内部框架是否会有异常抛出的情况发生,这一点就需要另外实践分析。


以下是一个实践的代码案例供大家参考:


package org.idea.spring.look.up.safe;
import org.apache.catalina.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * bean的依赖查找中安全性对比案例
 *
 * @Author linhao
 * @Date created in 8:58 上午 2021/4/12
 */
public class BeanLookUpSafe {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(BeanLookUpSafe.class);
        applicationContext.refresh();
        displayBeanFactoryGetBean(applicationContext);
        displayObjectFactoryGetBean(applicationContext);
        displayObjectProviderGetIfAvailable(applicationContext);
        displayListableBeanFactoryGetBean(applicationContext);
        displayObjectFactoryGetBeanStream(applicationContext);
        applicationContext.close();
    }
    private static void displayObjectFactoryGetBeanStream(ApplicationContext applicationContext) {
        printBeansException("displayObjectFactoryGetBeanStream",() ->{
            ObjectProvider<User> userObjectProvider = applicationContext.getBeanProvider(User.class);
            userObjectProvider.stream().forEach(System.out::println);
        });
    }
    private static void displayListableBeanFactoryGetBean(ListableBeanFactory listableBeanFactory) {
        printBeansException("displayListableBeanFactoryGetBean",() ->{listableBeanFactory.getBeansOfType(User.class);});
    }
    private static void displayObjectProviderGetIfAvailable(AnnotationConfigApplicationContext applicationContext) {
        ObjectProvider<User> objectProvider = applicationContext.getBeanProvider(User.class);
        printBeansException("displayObjectProviderGetIfAvailable",() ->{objectProvider.getIfAvailable();});
    }
    private static void displayObjectFactoryGetBean(BeanFactory beanFactory) {
        ObjectFactory<User> objectFactory = beanFactory.getBeanProvider(User.class);
        printBeansException("displayObjectFactoryGetBean",()->{ objectFactory.getObject();});
    }
    private static void displayBeanFactoryGetBean(BeanFactory beanFactory) {
        printBeansException("displayBeanFactoryGetBean",()->{ beanFactory.getBean(User.class);});
    }
    private static void printBeansException(String source,Runnable runnable){
        try {
            System.err.println("==========================");
            System.err.println("Source is from :" + source);
            runnable.run();
        }catch (BeansException exception){
            exception.printStackTrace();
        }
    }
}
复制代码


依赖查找可能抛出的异常


这里我整理了以下集中常见的Bean异常:


public static void main(String[] args) {
    //bean不在ioc容器中 继承了BeansException
    NoSuchBeanDefinitionException noSuchBeanDefinitionException;
    // 继承了NoSuchBeanDefinitionException的运行时候异常 通常只需要对bean标注@Primary注解即可
    // 但是这种设计会有语义性设计的不足,例如无法精确判断是没有该bean还是因为bean过多导致的
    NoUniqueBeanDefinitionException noUniqueBeanDefinitionException;
    //初始化的时候会抛出异常
    BeanInstantiationException beanInstantiationException;
    //初始化进行方法回调的时候会有异常抛出
    BeanCreationException beanCreationException;
    //一些常见的xml异常,例如说某些xml解析异常
    BeanDefinitionStoreException beanDefinitionStoreException;
}
复制代码


为了更好地进行实践,这里贴了几个实践出来的代码供大家进行分析思考:


BeanInstantiationException


bean在进行初始化过程中出现的异常,案例代码如下:


package org.idea.spring.look.up.exception;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * @Author linhao
 * @Date created in 9:59 下午 2021/4/12
 */
public class BeanInstantiationExceptionDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        //注册一个 BeanDefinitionBuilder
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(CharSequence.class);
        //Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.lang.CharSequence]: Specified class is an interface
        annotationConfigApplicationContext.registerBeanDefinition("error bean",beanDefinitionBuilder.getBeanDefinition());
        annotationConfigApplicationContext.refresh();
        annotationConfigApplicationContext.close();
    }
}
复制代码


BeanDefinitionStoreException


bean已经初始化结束了,在初始化回调接口环节出现的异常,案例代码如下:


package org.idea.spring.look.up.exception;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import javax.annotation.PostConstruct;
/**
 * @Author linhao
 * @Date created in 10:01 下午 2021/4/12
 */
public class BeanCreationExceptionDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext  = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(POJO.class);
        annotationConfigApplicationContext.refresh();
        annotationConfigApplicationContext.close();
    }
    static class POJO implements InitializingBean {
        //这里的基于注解的回调是通过 CommonAnnotationBeanPostProcessor 来实现的
        @PostConstruct
        public void init(){
            throw  new RuntimeException(" init has error");
        }
        @Override
        public void afterPropertiesSet() throws Exception {
            throw  new RuntimeException(" afterPropertiesSet : has error");
        }
    }
}


目录
相关文章
|
6月前
|
Java Maven 微服务
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的 maven 依赖
在项目中使用Swagger2工具时,需导入Maven依赖。尽管官方最高版本为2.8.0,但其展示效果不够理想且稳定性欠佳。实际开发中常用2.2.2版本,因其稳定且界面友好。以下是围绕2.2.2版本的Maven依赖配置,包括`springfox-swagger2`和`springfox-swagger-ui`两个模块。
208 0
|
6月前
|
缓存 Java 应用服务中间件
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——依赖导入和Thymeleaf相关配置
在Spring Boot中使用Thymeleaf模板,需引入依赖`spring-boot-starter-thymeleaf`,并在HTML页面标签中声明`xmlns:th=&quot;http://www.thymeleaf.org&quot;`。此外,Thymeleaf默认开启页面缓存,开发时建议关闭缓存以实时查看更新效果,配置方式为`spring.thymeleaf.cache: false`。这可避免因缓存导致页面未及时刷新的问题。
240 0
|
9月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
203 2
|
12月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
107 4
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
130 0
|
Java Spring 容器
深入挖掘Spring系列 -- 依赖的来源(下)
深入挖掘Spring系列 -- 依赖的来源(下)
164 0
|
XML 存储 Java
深入挖掘Spring系列 -- 依赖的来源(上)
深入挖掘Spring系列 -- 依赖的来源(上)
198 0
|
2月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
725 0