Spring依赖查找和依赖注入

801 阅读9分钟

Spring依赖查找和依赖注入

演示简单的Bean工厂环境,使用Xml进行相关Bean的配置

  1. 定义两个简单的POJO,一个学生对象,一个体育生对象继承了学生对象并且扩展了其擅长的体育项目字段
@Data
public class Student {
    private String name;
    private Integer age;
}

@Data
@EqualsAndHashCode(callSuper = true)
public class SportsStudent extends Student{

    private Sports goodAtSports;

    public enum Sports {
        BASKETBALL,FOOTBALL,SWIM
    }
}
  1. 创建Xml上下文 META-INF/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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--声明一个Student Bean-->
    <bean id="student" class="blog.spring.introdction.domain.Student">
        <property name="age" value="18"/>
        <property name="name" value="lazylittle"/>
    </bean>

    <!--声明一个体育生-->
    <bean id="sportsStudent" class="blog.spring.introdction.domain.SportsStudent" parent="student" primary="true"> <!--需要声明为primary不然根据类型查找会报NoUniqueBeanDefinitionException -->
        <property name="goodAtSports" value="SWIM"/>
    </bean>
</beans>

1. 依赖查找

1.1 单一类型查找(通过名称/类型)

public class DependencyLookupDemo {

    public static void main(String[] args) {
        //1. 创建容器对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //2. 加载xml配置
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        //3. 加载BeanDefinition (加载学生Bean)
        reader.loadBeanDefinitions("META-INF/lookup-context.xml");
        //演示单一类型依赖查找
        displaySimpleObjectLookup(beanFactory);
    }
    /**
     * 演示简单类型的依赖查找
     */
    private static void displaySimpleObjectLookup(DefaultListableBeanFactory beanFactory) {
        //1. 根据名称查找,没指定类型需要强制类型转换
        Student student = (Student) beanFactory.getBean("student");
        //2. 根据类型查找, 若此时有多个Student对象,则会报错NoUniqueBeanDefinitionException
        Student studentByType = beanFactory.getBean(Student.class);
        //3. 根据类型+名称进行查找
        Student studentByNameAndType = beanFactory.getBean("student", Student.class);
        System.out.println(student);
        System.out.println("studentByType == student is " + (studentByType == student));
        System.out.println("student == studentByNameAndType is " + (student == studentByNameAndType));
    }
}

// output 
Student(name=lazylittle, age=18)
studentByType == student is false //student 和 sportsStudent比较 (primary)
student == studentByNameAndType is true // student 和 Student

1.2 复杂对象查找

    private static void displayComplexObjectLookup(DefaultListableBeanFactory beanFactory) {
        //1. 通过类型查询复杂类型
        Map<String, Student> students = beanFactory.getBeansOfType(Student.class);
        System.out.println("lookup complex object by type => " + students);
        //2. 通过注解查询所有匹配的类型
        Map<String, Object> studentsByAnnotation = beanFactory.getBeansWithAnnotation(Sport.class);
        System.out.println("lookup complex object by annotation => " + studentsByAnnotation);
    }

// output 
// 1. 通过类型查询出来两个Student
lookup complex object by type => {student=Student(name=lazylittle, age=18), sportsStudent=SportsStudent(goodAtSports=SWIM)}
// 2. 通过Sport注解查找,只查找到了SportStudent
lookup complex object by annotation => {sportsStudent=SportsStudent(goodAtSports=SWIM)}

1.3 层次性查找/安全查找

  • 修改加载的容器配置,添加上ParentBeanFactory

        //1. 创建容器对象
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        //2. 加载xml配置
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        //3. 加载BeanDefinition (加载学生Bean)
        reader.loadBeanDefinitions("META-INF/lookup-context.xml");
        //4. 设置父上下文,用来演示层次性查找
        ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("META-INF/parent-context.xml");
        beanFactory.setParentBeanFactory(parent);
        //演示层次性依赖查找
        displayHierarchicalLookup(beanFactory,parent.getBeanFactory());
  • 在父上下文中加载一个额外的Student来测试层次性的查找
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--ParentBeanFactory 声明一个Student Bean-->
    <bean id="studentFromParent" class="blog.spring.introdction.domain.Student">
        <property name="age" value="50"/>
        <property name="name" value="z3"/>
    </bean>
</beans>

1.3.1 层次性依赖查找
private static void displayHierarchicalLookup(ConfigurableListableBeanFactory child, ConfigurableListableBeanFactory parent) {
    //1. 查找子容器中的所有Student对象
    Map<String, Student> beansFromChild = child.getBeansOfType(Student.class);
    //2. 查找父容器中的所有Student对象
    Map<String, Student> beansFromParent = parent.getBeansOfType(Student.class);
    //3. 层次性递归查找
    Map<String, Student> beansFromHierarchical = BeanFactoryUtils.beansOfTypeIncludingAncestors(child, Student.class);
    System.out.println("子容器中 student : " + beansFromChild);
    System.out.println("父容器中 student : " + beansFromParent);
    System.out.println("递归所有层级容器 student : " + beansFromHierarchical);
}
//output
子容器中 student : {student=Student(name=lazylittle, age=18), sportsStudent=SportsStudent(goodAtSports=SWIM)}
父容器中 student : {studentFromParent=Student(name=z3, age=50)}
//发现使用BeanFactoryUtils提供的递归查询工具类能查询到所有层级的Bean
递归所有层级容器 student : {student=Student(name=lazylittle, age=18), sportsStudent=SportsStudent(goodAtSports=SWIM), studentFromParent=Student(name=z3, age=50)}
BeanFactoryUtils递归依赖查找实现解析

层次性查找,若beanName有重复的,则子容器会覆盖父容器

	public static <T> Map<String, T> beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type)
			throws BeansException {
		Assert.notNull(lbf, "ListableBeanFactory must not be null");
		Map<String, T> result = new LinkedHashMap<>(4);
		result.putAll(lbf.getBeansOfType(type)); //1. 将当前上下文的所有对应类型bean加入到cache中
		if (lbf instanceof HierarchicalBeanFactory) { //2. 若是层次性上下文,则去父容器中获取
			HierarchicalBeanFactory hbf = (HierarchicalBeanFactory) lbf;
			if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
				Map<String, T> parentResult = beansOfTypeIncludingAncestors( //3. 递归获取
						(ListableBeanFactory) hbf.getParentBeanFactory(), type); 
				parentResult.forEach((beanName, beanInstance) -> { //4. 合并,若没有beanName则进行设置,有则跳过
					if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) {
						result.put(beanName, beanInstance);
					}
				});
			}
		}
		return result;
	}
// output
子容器中 student : {student=Student(name=lazylittle, age=18), sportsStudent=SportsStudent(goodAtSports=SWIM)}
父容器中 student : {studentFromParent=Student(name=z3, age=50)}
递归所有层级容器 student : {student=Student(name=lazylittle, age=18), sportsStudent=SportsStudent(goodAtSports=SWIM), studentFromParent=Student(name=z3, age=50)}
1.3.2 安全性/延迟查找

通过ObjectFactory可以提供延迟查找功能,其中使用getObject()方法获取真实包含的Bean

Spring4.3提供了ObjectProvider类,添加了一系列的安全性延迟依赖查找,通过一些java8函数式技巧来进行安全查找,从而避免例如之前的NoUniqueBeanDefinitionException / NoSuchBeanDefinitionException等。。

在Xml添加一个Bean来演示ObjectFactory

    <!--
        声明一个ObjectFactory 的FactoryBean
        FactoryBean是Spring提供的一种用来创建复杂Bean对象的类,可以通过他的getObject()方法进行返回Bean对象
        这里的ObjectFactoryCreatingFactoryBean是用来创建一个 ObjectFactory<Student>
    -->
    <bean id="studentObjectFactoryBean" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
        <property name="targetBeanName" value="student"/>
    </bean>

演示相关代码如下

    private static void displaySafeOrLazyLookup(DefaultListableBeanFactory beanFactory) {
        ObjectProvider<Student> studentProvider = beanFactory.getBeanProvider(Student.class);
        //1. 如果存在则消费
        studentProvider.ifAvailable(System.out::println);
        //2. Unique安全查找
        System.out.println(studentProvider.getIfUnique());
        //3. ObjectFactory延迟查找
        beanFactory.getBeanProvider(ResolvableType.forClassWithGenerics(ObjectFactory.class, Student.class)).ifAvailable(System.out::println);
    }
//output 
SportsStudent(goodAtSports=SWIM)
SportsStudent(goodAtSports=SWIM)
  • 可以通过getBeanProvidedr()来延迟安全查找
  • 可以通过指定ResolableType来指定复杂泛型查找

2. 依赖注入

实验环境如下

  • 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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--声明一个Student Bean-->
    <bean id="student" class="blog.spring.introdction.domain.Student">
        <!--1. 属性注入-->
        <property name="age" value="18"/>
        <property name="name" value="lazylittle"/>
        <property name="address" ref="address"/>
    </bean>
    <!--Address Bean-->
    <bean id="address" class="blog.spring.introdction.domain.Address">
        <property name="addressName" value="HZ"/>
    </bean>
</beans>
  • 启动环境
public class DependencyInjectionDemo {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
        //1. 为了处理我们的@PostConstruct注解,添加一个Spring内置的CommonAnnotationBeanPostProcessor
        applicationContext.addBeanFactoryPostProcessor(beanFactory ->
                beanFactory.addBeanPostProcessor(new CommonAnnotationBeanPostProcessor()));
        applicationContext.setConfigLocation("META-INF/inject-context.xml");
        //2. 启动上下文,会初始化所有的单实例Bean
        applicationContext.refresh();
        //3. 关闭上下文
        applicationContext.close();
    }
}

2.1 xml注入

2.1.1 setter注入

  • 在Xml使用<property>完成setter注入
  • Student类加入初始化回调@PostConstruct展示注入的属性
@Data
public class Student {

    private String name;
    private Integer age;
    private Address address;

    @PostConstruct
    public void showInjectedValue() {
        System.out.println(this);
    }
}
//output , 成功注入了引用bean和基本属性,并且spring能自动完成xml的string -> 需要的类型 (后序类型转化详细探讨)
Student(name=lazylittle, age=18, address=Address(addressName=HZ))

2.1.2 构造器注入

  • Student类中添加构造器
    public Student(String name, Integer age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
  • 在xml中使用<constructor-arg>进行构造器注入
    <!--声明一个Student Bean-->
    <bean id="student" class="blog.spring.introdction.domain.Student">
        <!--2. 构造器注入-->
        <constructor-arg name="age" value="18"/>
        <constructor-arg name="name" value="lazylittle"/>
        <constructor-arg name="address" ref="address"/>
    </bean>
  • 执行验证注入结果
//output,也是成功注入进来
Student(name=lazylittle, age=18, address=Address(addressName=HZ))

2.1.3 接口注入,通过Aware方式回调

这种方式一般都是在Spring Bean 刷新过程中 , 通过容器手动回调相关setXXX方法传入内建的xxxAware接口引用给自定义的Bean完成注入

  • Student实现相关Aware接口 , 以BeanNameAware做示例,会通过setBeanName回调传入当前Student的bean名称
@Data
public class Student implements BeanNameAware {
    private String beanName;
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}
  • 检验结果
//output , beanName已经注入进来
Student(name=lazylittle, age=18, address=Address(addressName=HZ), beanName=student)
  • 相关回调源码位置和实现 , 相关容器生命周期可以查看AbstractAutowireCapableBeanFactory#invokeAwareMethods()方法以及ApplicationContextAwareProcessor
  //AbstractAutowireCapableBeanFactory
	private void invokeAwareMethods(String beanName, Object bean) {
		if (bean instanceof Aware) {
			if (bean instanceof BeanNameAware) {
				((BeanNameAware) bean).setBeanName(beanName); //回调BeanNameAware
			}
			if (bean instanceof BeanClassLoaderAware) {
				ClassLoader bcl = getBeanClassLoader();
				if (bcl != null) {
					((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);//回调BeanClassLoader
				}
			}
			if (bean instanceof BeanFactoryAware) {//回调BeanFactory
				((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
			}
		}
	}
//ApplicationContextAwareProcessor
private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof EnvironmentAware) {
			((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
		}
		if (bean instanceof EmbeddedValueResolverAware) {
			((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
		}
		if (bean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
		}
		if (bean instanceof ApplicationEventPublisherAware) {
			((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
		}
		if (bean instanceof MessageSourceAware) {
			((MessageSourceAware) bean).setMessageSource(this.applicationContext);
		}
		if (bean instanceof ApplicationStartupAware) {
			((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
		}
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}

2.2 annotation注入

启动上下文代码

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

2.2.1 注解方式属性注入

相比于Xml的方式,Spring注解驱动可以通过使用@Autowired/@Resource/@Inject等注解完成属性注入,

而这些注入方式的 实现,是由AutowiredAnnotationBeanPostProcessor/CommonAnnotationBeanPostProcessor进行扫描对应注解并且使用反射注入。(后序源码分析的时候会重点分析)

  • 属性注入一个Address对象,属性注入是注解驱动里面提供的。核心在AutowiredAnnotationBeanPostProcessor中(源码解析中讲解)
public class Student implements BeanNameAware {
    @Autowired // 可以替换成 @Resource @Inject , 甚至可以使用@Value注解注入外部化配置
    private Address address;
}
  • 校验结果
//output
Student{name='null', age=null, address=Address(addressName=HZ), beanName='student'}

2.2.2 注解方式setter注入

    @Bean
    public Address address() {
        Address address = new Address();
        address.setAddressName("HZ"); //使用setter方法注入addressName属性
        return address;
    }

2.2.3 注解方式构造器注入

  • 可以不用@Autowired标注,但此时只能有一个带参构造 , (若有默认构造参数则会选择默认构造参数)
@Component
public class Student implements BeanNameAware {
    private Address address;
    public Student(Address address) {
        this.address = address;
    }
}
  • 仅有一个@Autowired标注的构造方法,那么会受AutowiredAnnotationBeanPostProcessor#determineCandidateConstructors方法回调,仅使用该构造器进行构造器注入
@Component
@NoArgsConstructor
public class Student implements BeanNameAware {
  
    private Address address;
  	//会优先采用Autowired标记过的构造器
    @Autowired(required = false)
    public Student(Address address,String name) {
        this.address = address;
        this.name = name;
    }

    public Student(Address address) {
        this.address = address;
    }
  • 若有多个@Autowired标记的构造器,那么只能同时为required=false,否则会报BeanCreationException异常 (至于为什么会出异常,后面源码解析会解释)
@Component
@NoArgsConstructor
public class Student implements BeanNameAware {
    private Address address;
		//会优先选择可用(容器中已经有的Bean)参数较多的构造器进行构造器注入
  	// 必须都标注为required=false才行。不然会报错
    @Autowired(required = false)
    public Student(String name,Address address) {
       this.name = name;
       this.address = address;
    }
    @Autowired(required = false)
    public Student(Address address) {
        this.address = address;
        this.name = name;
    }

2.2.4 注解方式接口注入 : 同Xml没有任何变化

2.2.5 复杂类型注入和分组注入

optional可选注入
@Configuration
public class DependencyInjectionWithComplexTypeDemo {
		//注入可选Bean
    @Autowired
    private Optional<String> helloOptional;

    @PostConstruct
    public void showResult() {
        System.out.println(this.helloOptional);
    }
延迟注入(ObjectFactory/ObjectProvider/@Lazy)
 //ObjectFactory同ObjectProvider,但是ObjectFactory没有安全性查找
    @Autowired
    private ObjectProvider<String> helloBeanProvider;

    @PostConstruct
    public void showResult() {
        System.out.println(this.helloOptional);
        System.out.println(this.helloBeanProvider);
        System.out.println(this.helloBeanProvider.getIfAvailable(()->"fallback")); //安全性查找
        System.out.println(this.helloBeanProvider.getObject()); //NoSuchBeanDefinitionException,No qualifying bean of type 'java.lang.String' available
    }

@Lazy方式


    @Autowired
    @Lazy
    private GenericHolder lazyBean; //注意这里不能使用String类型,@Lazy需要使用CGLIB生成代理对象(子类),final Class不能继承

    @PostConstruct
    public void showResult() {
				//调用时会回调代理对象,此时会报错找不到Bean,因为上下文中压根没有GenericHolder Bean。但是不使用的话就不会报错
        System.out.println(this.lazyBean); 
    }
集合类型加载(Collection,Array,Map)
    @Autowired
    private Collection<String> hellos;

    @Autowired
    private String[] helloArray;
		//会注入 k: beanName v: bean value
    @Autowired
    private Map<String,String> helloMap;

    @PostConstruct
    public void showResult() {
        System.out.println("Collection Way  => "+this.hellos);
        System.out.println("Array Way => "+ Arrays.toString(this.helloArray));
        System.out.println("Map Way => "+this.helloMap);
    }

//output
Collection Way  => [hello, hello1]
Array Way => [hello, hello1]
Map Way => {hello=hello, hello1=hello1}
java规范的Provider加载 , 其实底层通过适配方式适配到了spring的DefaultListtableBeanFactory#doResolveDependency()
    @Autowired
    private Provider<List<String>> listProvider;
分组加载 : Qualifier

可以通过@Qualifirer注解进行分组注入,具体实现可以看QualifierAnnotationAutowireCandidateResolver#isQualifier

  1. 声明Bean的时候使用@Qualifier限定
  2. 注入的时候使用@Qualifier获取限定的分组Bean
public class DisplayQualifierAnnotationDemo implements InitializingBean {

    @Autowired
    private Collection<String> helloCollection;
		//加载所有分组名称为hello  或者名称为 hello的		
    @Autowired
    @Qualifier("hello")
    private Collection<String> helloCollectionWithQualifier;
		//加载所有分组名称为hello1 或者名称为 hello1的
    @Autowired
    @Qualifier("hello1")
    private Collection<String> hello1CollectionWithQualifier;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(this.helloCollection);
        System.out.println(this.helloCollectionWithQualifier);
        System.out.println(this.hello1CollectionWithQualifier);
    }
    @Bean
    public String hello() {
        return "hello";
    }
  	//申明该Bean属性hello分组
    @Bean
    @Qualifier("hello")
    public String hello1() {
        return "hello1";
    }
}
//output
[hello, hello1]
[hello, hello1]
[hello1]

3. 依赖来源

依赖查找和依赖注入的元数据来源(BeanDefinition),基本一致,依赖注入多了一个 ResolvableDependencies,如下,我们可以通过ConfigurableListableBeanFactory#registerResolvableDependency注册这种类型的Bean提供给依赖注入时使用,但是此时依赖查找是获取不到的

public class DisplayResolvableDependency
{
    @Autowired
    private String hello;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.register(DisplayResolvableDependency.class);
        applicationContext.getDefaultListableBeanFactory().registerResolvableDependency(String.class,"byResolvableDependency");
        applicationContext.refresh();
        //print hello prop
        System.out.println(applicationContext.getBean(DisplayResolvableDependency.class).hello);
        // lookup sting
        System.out.println(applicationContext.getBean(String.class));
        applicationContext.close();
    }
}
//output
// 依赖注入正常
byResolvableDependency
//依赖查找找不到Bean
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available