本篇博客主要讲述Spring中Bean的三种主要装配:1、在XML中进行显示配置;2、自动装配方式;3、在Java中进行显示配置。下面分别来研究上述三种装配方式。
在Spring IOC容器中注入依赖资源主要有以下两种基本实现方式:(1)构造器注入:容器实例化Bean时注入一些依赖,通过在Bean定义中指定构造器参数进行注入依赖,包括实例工厂方法参数注入依赖,但静态工厂方法参数不允许注入依赖;(2)setter注入:通过setter方法进行注入依赖。
1、在XML中进行显示配置
定义接口:Animal
package com.wygu.spring.animal;
public interface Animal {
public void walk();
public void call();
}
实现类Cat和Dog
package com.wygu.spring.animal;
public class Cat implements Animal{
private String catName;
public Cat(String catName) {
this.catName = catName;
}
@Override
public void walk() {
System.out.println("The Cat name: "+catName+" is walking");
}
@Override
public void call() {
System.out.println("The Cat name: "+catName+" is called");
}
}
package com.wygu.spring.animal;
public class Dog implements Animal{
private String dogName;
public Dog(String dogName) {
this.dogName = dogName;
}
@Override
public void walk() {
System.out.println("The Dog name: "+dogName+" is walking");
}
@Override
public void call() {
System.out.println("The Dog name: "+dogName+" is walking");
}
}
定义一个工厂AnimalFactory生产动物
package com.wygu.spring.animal;
public class AnimalFactory {
private Animal animal;
public AnimalFactory(){
}
public AnimalFactory(Animal animal){
this.animal = animal;
}
public Animal getAnimal() {
return animal;
}
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
编写spring-application.xml
<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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 通过构造器参数名依赖注入 -->
<bean id="cat" class="com.wygu.spring.animal.Cat">
<constructor-arg name="catName" value="加菲猫"/>
</bean>
<!-- 通过构造器参数索引进行注入 -->
<bean id="dog" class="com.wygu.spring.animal.Dog">
<constructor-arg index="0" value="欧巴"/>
</bean>
<!-- 通过构造器参数索引依赖注入 -->
<bean id="factory1" class="com.wygu.spring.animal.AnimalFactory">
<constructor-arg name="animal" ref="cat"/>
</bean>
<!-- 通过setter方式依赖注入 -->
<bean id="factory2" class="com.wygu.spring.animal.AnimalFactory">
<property name="animal" ref="dog" />
</bean>
</beans>
通过上述示例,我们知道,利用构造器注入时,常见的有两种注入方式:(1)根据参数索引注入,使用标签“”来指定注入的依赖,其中“index”表示索引,从0开始,即第一个参数索引为0,“value”来指定注入的常量值;(2)根据参数名进行注入,使用标签“”来指定注入的依赖,其中“name”表示需要匹配的参数名字,“value”来指定注入的常量值。
对于setter方法注入方式,只有一种依赖注入方式: 根据参数名进行注入,语法同上。
但是,当系统非常庞大时,如果Bean之间的依赖关系全部通过XML方式实现,会导致配置文件的可读性与可维护性变得很低。此外,在开发中,开发人员需要在.java文件和.xml文件之间来回的切换,从而降低开发效率。
为了解决上述问题,Spring引入了注解,通过”@XXX”的方式,让注解与Java Bean紧密结合,不仅精简了大量的配置文件,而且还增加了Java Bean的可读性与内聚性。下面给出通过注解方式实现Java Bean的装配。
2、通过注解技术实现Bean的自动装配
(1)@Autowired
1)在AnimalFactory的成员域上标注注解 @Autowired
package com.wygu.spring.animal;
import org.springframework.beans.factory.annotation.Autowired;
public class AnimalFactory {
@Autowired
private Animal animal;
....
}
spring-application.xml
<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
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 该 BeanPostProcessor 将自动起作用,对标注 @Autowired 的 Bean 进行自动注入 -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<!-- 通过构造器参数索引进行注入 -->
<bean id="dog" class="com.wygu.spring.animal.Dog">
<constructor-arg index="0" value="欧巴"/>
</bean>
<bean id="factory1" class="com.wygu.spring.animal.AnimalFactory">
</bean>
</beans>
Spring 通过一个 BeanPostProcessor 对 @utowired 进行解析,所以要让 @Autowired 起作用必须事先在 Spring 容器中如spring-application.xml中声明AutowiredAnnotationBeanPostProcessor Bean。当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor 将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有 @Autowired 注释时就找到和其匹配(默认byType)的 Bean,并注入到对应的地方中去。
2) 此外,还可以在AnimalFactory的构造器上标注注解 @Autowired:
package com.wygu.spring.animal;
import org.springframework.beans.factory.annotation.Autowired;
public class AnimalFactory {
private Animal animal;
@Autowired
public AnimalFactory(Animal animal){
this.animal = animal;
}
.....
}
3)或者在AnimalFactory的setter方法上标注注解 @Autowired:
package com.wygu.spring.animal;
import org.springframework.beans.factory.annotation.Autowired;
public class AnimalFactory {
private Animal animal;
public Animal getAnimal() {
return animal;
}
@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}
......
}
但是,当我们在 Spring 容器中配置了两个类型为Animal类型的 Bean时,在对AnimalFactory中成员域animal自动注入时,Spring 容器将无法确定注入哪一个,就会抛出如下的异常:
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'factory1': Unsatisfied dependency expressed through method 'setAnimal' parameter 0: No qualifying bean of type [com.wygu.spring.animal.Animal] is defined: expected single matching bean but found 2: cat,dog; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.wygu.spring.animal.Animal] is defined: expected single matching bean but found 2: cat,dog
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:651)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:350)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:775)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.wygu.spring.main.Main.main(Main.java:12)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.wygu.spring.animal.Animal] is defined: expected single matching bean but found 2: cat,dog
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:172)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1059)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1018)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:643)
... 15 more
通过上述异常,很容易看出是由于同时存在两个满足注入条件的Bean(同一种类型Animal),从而使得IOC容器抛出异常。
Spring中 允许我们通过 @Qualifier 注解指定注入 Bean 的名称,这样歧义就消除了。
(2)@Qualifier
@Autowired
@Qualifier("cat")
public void setAnimal(Animal animal) {
this.animal = animal;
}
仍然报上述错误,不知道为什么,网上各种方法都试了,仍然没有消除二义性?????
然后在spring-application.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:annotation-config/>
<bean id="factory1" class="com.wygu.spring.animal.AnimalFactory"/>
<!-- 通过构造器参数索引进行注入 -->
<bean id="cat" class="com.wygu.spring.animal.Cat">
<constructor-arg index="0" value="加菲猫"/>
</bean>
<!-- 通过构造器参数索引进行注入 -->
<bean id="dog" class="com.wygu.spring.animal.Dog">
<constructor-arg index="0" value="欧巴"/>
</bean>
</beans>
@Autowired 和@Qualifier 结合使用时,自动注入的策略就从 byType 转变成 byName 了。@Autowired 和@Qualifier可以结合对构造器和成员域进行标注。
(3)如果不存在Bean匹配,可以在自动注入的地方加上 @Autowired(required = false)
@Autowired(required = false)
public void setAnimal(Animal animal) {
this.animal = animal;
}
但是上述方式不建议使用,因为很有可能出现空指针异常。
3、在Java中进行显示配置
尽管在很多场景下通过自动装配实现实现Spring的自动化配置是更为推荐的,但有时候自动化配置的方案是行不通的,因此我们需要明确配置Spring。比如想将第三方第三方库中的组件装配到应用中,此时是没有办法自动装配的,因而我们需要采用显示装配方式,比如XML,或者JavaConfig方式。由于JavaConfig是类型安全的而且重构友好,我们更推荐使用JavaConfig方式。
package com.wygu.spring.hello;
public interface HelloWorld {
public void printHelloWorld(String str);
}
package com.wygu.spring.hello;
public class HelloWorldImp implements HelloWorld{
@Override
public void printHelloWorld(String str){
System.out.println("Hello:"+str);
}
}
创建HelloConfig
package com.wygu.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.wygu.spring.hello.HelloWorld;
import com.wygu.spring.hello.HelloWorldImp;
@Configuration
public class HelloConfig {
@Bean(name="helloBean")
public HelloWorld getHelloWorld(){
return new HelloWorldImp();
}
}
package com.wygu.spring.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.wygu.spring.config.HelloConfig;
import com.wygu.spring.hello.HelloWorld;
public class Main {
public static void main(String[] args) {
@SuppressWarnings("resource")
ApplicationContext context = new AnnotationConfigApplicationContext(HelloConfig.class);
HelloWorld helloWorld = (HelloWorld) context.getBean("helloBean");
helloWorld.printHelloWorld("Spring Java Config");
}
}
执行结果为:Hello:Spring Java Config
显然,上述代码示例中,没有使用自动装配和XMl实现了装配Bean的功能。