Spring依赖查找和依赖注入
演示简单的Bean工厂环境,使用Xml进行相关Bean的配置
- 定义两个简单的
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
}
}
- 创建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
- 声明Bean的时候使用@Qualifier限定
- 注入的时候使用@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