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具有这么两个特点:
从他的基本接口设计就可以看出两点:
- 支持获取当前Spring容器的父容器
- 支持判断当前容器所处的层级是否包含某个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"); } } }