依赖查找是什么
使用过Spring框架的同学应该都清楚,Spring会将我们所需要使用到的Bean按照一定规则存储到一个应用上下文中(ApplicationContext)。所谓的依赖查找就是根据规则从Spring容器中获取对应的Bean。
流程有点类似于下图:
客户端程序希望获取一个BeanA,那么就向Spring中发起一个请求,从容器中获取对应的BeanA。
其实在Spring发明之前,jdk内部也有类似依赖查找的这种功能。下边我们来看看这几个类的属性。
java.beans.beancontext.BeanContext
java.beans.beancontext.BeanContextServices
在这两个类里面存有着和Spring设计类似的一些管理bean的基本接口和功能,这一点上可以看出,其实Spring的依赖查找设计是有对Jdk内部实现做了一定借鉴的。
依赖查找归纳
按照自己的经验和一些资料帮助,下边大体将依赖查找划分为了三种类型:
- 单一类型查找
- 集合类型查找
- 层次类型查找
接下来便是相关代码的实战案例,通过代码实践来深入理解这些依赖查找。
Spring内部的单一类型查找
关于spring内部的单一查找这里我将常见的几种类型给大家罗列出来。
Object getBean(String name) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansExceptio
在高版本的Spring中还提供了对于Spring内部Bean的延迟查找功能
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
ResolvableType解释下:因为jdk内部的类不仅仅只有Class类型,还有例如说Type类型,FIeld类型,所以Spring又增加了一种类型参数作为暴露的Api服务。
实践案例:
配置Bean:Pig
package org.idea.spring.look.up.factory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author linhao
* @Date created in 9:26 下午 2021/4/10
*/
@Configuration
public class Config {
public class Pig{
int id;
public Pig() {
System.out.println("this is pig");
}
@Override
public String toString() {
return "Pig{" +
"id=" + id +
'}';
}
}
@Bean(name = "pig")
public Pig getPig(){
return new Pig();
}
}
测试入口:
package org.idea.spring.look.up;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
/**
* @Author linhao
* @Date created in 9:02 下午 2021/4/10
*/
public class SpringSingleLookUpDemo {
@Bean
public String HelloWorld(){ //bean 注解里面的name或者value如果没有定义的话,这里默认就是 "helloWorld"
System.out.println("THIS IS INIT");
return "Hello.World";
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =new AnnotationConfigApplicationContext();
//此时相当于声明当前这个类是一个配置类,所以不需要进行额外注解的声明
applicationContext.register(SpringSingleLookUpDemo.class);
applicationContext.refresh();
//单一查找的一种实现方式
String bean = (String) applicationContext.getBean("Pig");
System.out.println(bean);
//spring 4.1 高版本中的一种实现方式 这里只是支持单一查找类型
ObjectProvider<Config.Pig> objectProvider = applicationContext.getBeanProvider(Config.Pig.class);
System.out.println(objectProvider.getObject());
applicationContext.close();
}
}
Spring内部的集合类型查找
根据 Bean 类型查找
获取同类型 Bean 名称列表
String[] getBeanNamesForType(@Nullable Class<?> type);
String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);
获取同类型 Bean 实例列表
getBeansOfType(Class) 以及重载方法 //不建议使用
通过注解类型查找
//Spring 3.0 - 获取标注类型 Bean 名称列表
String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);
//Spring 3.0 - 获取标注类型 Bean 实例列表
Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException;
//Spring 3.0 - 获取指定名称+标注类型 Bean 实例
<A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType)
throws NoSuchBeanDefinitionException;
实验案例:
package org.idea.spring.look.up;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author linhao
* @Date created in 10:03 下午 2021/4/10
*/
class Text {
int id;
public Text(int id) {
System.out.println("init");
this.id = id;
}
public Text(){
System.out.println("no arg init");
}
@Override
public String toString() {
return "Text{" +
"id=" + id +
'}';
}
}
class A {
void doJob(){
System.out.println("do Job A");
}
}
class B extends A {
@Override
void doJob() {
System.out.println("do Job B");
}
}
public class SpringListableLookUpDemo {
@Bean(name = "text")
public Text getText() {
return new Text(1);
}
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(SpringListableLookUpDemo.class);
annotationConfigApplicationContext.register(B.class);
annotationConfigApplicationContext.register(A.class);
annotationConfigApplicationContext.refresh();
ListableBeanFactory listableBeanFactory = annotationConfigApplicationContext;
listableBeanFactory.getBeansWithAnnotation(Component.class);
Text text = (Text) listableBeanFactory.getBean("text");
Map<String, Text> map = listableBeanFactory.getBeansOfType(Text.class);
System.out.println(map);
//如果查找的是一个父类,会顺便将其子类也查询出来
String[] beanNameArr = listableBeanFactory.getBeanNamesForType(A.class);
for (String beanName : beanNameArr) {
System.out.println(beanName);
}
}
}
在这段代码程序中,有几个点需要注意一下:bean的依赖查找一般是需要结合BeanDefinition来使用,这一步的操作是发生在了Spring容器的上下文启动之前进行的。
对于判断某个bean是否存在,建议可以使用(一般是借助BeanDefinition这种关于类的元信息辅助类判断,此时可以避免提早实现bean的初始化)
例如:
listableBeanFactory.getBeanNamesForType(Text.class);
而不是以下方法(可能会涉及到bean的提前初始化操作)
listableBeanFactory.getBeansOfType(Text.class);
getBeanNamesForType其中的底层逻辑是从BeanDefinitionNames 集合中去遍历查询,并不会触发bean的初始化步骤;
getBeansOfType的底层内部可能会涉及到bean的初始化操作。
不过如果在使用这两个方法之前,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具有这么两个特点:
从他的基本接口设计就可以看出两点:
- 支持获取当前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");
}
}
}