何为自动装配
在使用Spring
框架配置bean
时,我们通常需要为bean的属性设置值。如果不手动设置这些值,它们通常会被初始化为默认值(对于对象类型通常是null,对于基本类型如int则是0,boolean是false等)。自动装配的好处在于,我们不需要手动为每个依赖的bean设置值,Spring容器会根据配置在上下文中自动寻找并装配合适的值。
在Spring中,有几种不同的装配方式:
-
XML配置:
通过Spring的XML配置文件,我们可以显式地定义bean以及它们之间的依赖关系。这通常涉及使用<bean>
标签来定义bean,并通过<property>
或<constructor-arg>
标签来设置bean的属性或构造器参数。 -
Java配置(或称为注解配置):
从Spring 3.0开始,我们可以使用Java类来定义bean和它们之间的依赖关系,而不是使用XML。这通常涉及使用@Configuration
注解来标记一个类作为配置类,并使用@Bean
注解来标记一个方法作为bean的工厂方法。 -
自动装配:
Spring提供了自动装配功能,可以自动解析和注入bean之间的依赖关系。这可以通过在bean上使用@Autowired
、@Resource
或@Inject
等注解来实现。这些注解告诉Spring容器在创建bean
时自动查找并注入所需的依赖项。-
@Autowired
:Spring特有的注解,用于自动装配bean。默认情况下,它按类型(byType)
自动装配。当存在多个相同类型的bean时,可以使用@Qualifier
注解来指定要注入的bean的名称。 -
@Resource
:Java EE的注解,也支持自动装配。与@Autowired
不同,@Resource
默认按名称(byName)
查找bean,但也可以按类型查找(如果同时指定了名称和类型)。 -
@Inject
:这是JSR-330规范的一部分,与@Autowired
类似,但它是Java标准的一部分,不仅限于Spring。
-
环境准备
先来定义好三个 POJO 类,Teacher、Student、Study。并且有一个关系就是 Teacher拥有学生和学科。
package com.JavaClass.pojo;public class student{public void shout(){System.out.println("我是学生");}
}
package com.JavaClass.pojo;public class study {public void shout(){System.out.println("I like study");}
}
package com.JavaClass.pojo;public class teacter{private Student student;private Study study;private String name;/* getter/setter 和 toString */}
Spring 配置文件部分:
在该项目的resources
目录下,创建一个spring-config.xml
用来配置文件spring-config.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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="student" class="com.JavaClass.pojo.Student"/>
<bean id="study" class="com.JavaClass.pojo.Study"/>
<bean id="teacher" class="com.JavaClass.pojo.Teacher"><property name="name" value="JAVA学习课堂/><property name="cat" ref="cat"/><property name="dog" ref="dog"/></bean>
</beans>
在该项目的src/test/java
目录下,创建一个MyTest
测试类,用来测试代码。
import com.JavaClass.pojo.Teacher;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest {@Testpublic void test(){//解析beans.xml文件,IOC容器生成并管理相应的Bean对象ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");//通过Spring的上下文对象,从IOC容器中获取Bean对象Teacher person = context.getBean("teacher", Teacher.class);//调用Bean对象的方法person.getStudent().shout();person.getStudy().shout();}
}
测试默认装配的结果如图:
配置实现自动装配
自动装配在Spring中有几种不同的方式,主要包括:
-
byName:
根据属性名自动装配。Spring容器会查找与bean的属性名相同的另一个bean的定义,并将其自动装配到当前bean中。 -
byType:
根据属性类型自动装配。Spring容器会查找与bean的属性的类型相匹配的bean定义,如果有多个相同类型的bean,则会抛出异常,除非指定了其他方式(如使用@Qualifier
注解)来消除歧义。 -
constructor:
基于构造函数的自动装配。与基于类型的自动装配类似,但它是应用于构造函数的参数。Spring容器会查找与构造函数的参数类型相匹配的bean定义,并使用这些bean来实例化当前的bean。
byName
在使用Spring框架的自动装配特性时,若启用了byName模式,则容器会按照特定规则查找与bean属性匹配的bean定义。具体而言,它会根据类中定义的setter方法名(去掉set
前缀,并将剩余部分的首字母转换为小写)来寻找一个具有相同ID的bean定义。例如,如果 theacher
类中包含一个名为setStudent
的setter方法,并且Spring的配置文件中存在一个ID为student
的bean定义,那么Spring容器在启用byName自动装配后,会自动将ID为student
的bean注入到Teacher
类的student
属性中。
操作:修改 spring-config.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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="student" class="com.JavaClass.pojo.Student"/><bean id="study" class="com.JavaClass.pojo.Study"/><!--加上了 person 的 bean 的 autowired 属性并去掉了手动装配的 cat 和 dog --><bean id="teacher" class="com.JavaClass.pojo.Teacher" autowire="byName"><property name="name" value="Java学习课堂"/></bean><!-- <bean id="person" class="com.JavaClass.pojo.Person">--><!-- <property name="name" value="Java学习课堂"/>--><!-- <property name="cat" ref="cat"/>--><!-- <property name="dog" ref="dog"/>--><!-- </bean>-->
</beans>
结果依旧相同,证明这样根据 byName
的代码是没问题的。
byType
byType
模式会根据bean中所需依赖的类型,在容器上下文中自动寻找匹配的bean定义并进行装配。如果容器中存在一个或多个与所需依赖类型相匹配的bean,Spring将尝试将它们自动注入到当前bean中。
改动下 spring-config.xml 配置文件中 id 为 teacher 的 bean,配置成byType 模式
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="student" class="com.JavaClass.pojo.Student"/><bean id="study" class="com.JavaClass.pojo.Study"/><!--加上了 person 的 bean 的 autowired 属性并去掉了手动装配的 cat 和 dog --><bean id="teacher" class="com.JavaClass.pojo.Teacher" autowire="byType"><property name="name" value="Java学习课堂"/></bean>
</beans>
注:byType自动装配时,存在一个潜在的问题,即当容器中存在多个相同类型的bean时,由于不是基于唯一的id来匹配,而是根据类型进行匹配,Spring将无法确定应该注入哪一个bean,这会导致一个异常。
使用注解实现自动装配
使用注解的准备
如果使用注解我们还需要对原本的配置文件做一些修改,增加约束,来支持通过注解的形式实现自动装配。
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启注解支持 --><context:annotation-config/><bean id="student" class="com.JavaClass.pojo.Student"/><bean id="study" class="com.JavaClass.pojo.Study"/><bean id="teacher" class="com.JavaClass.pojo.Teacher"/></beans>
@AutoWired注解的使用
在 POJO 类需要自动装配的属性上加 @AutoWired 注解实现自动装配。
package com.JavaClass.pojo;import org.springframework.beans.factory.annotation.Autowired;public class Teacher {@Autowiredprivate Student student;@Autowiredprivate Study study;private String name;/* getter/setter 和 toString */}
执行测试代码,结果如下:
@Qualifier
当在Spring配置中定义了同一个类的多个bean,并使用@Autowired注解进行自动装配时,由于@Autowired默认采用按类型(byType)的装配方式,Spring框架将无法直接确定应该注入哪一个bean实例,因为存在多个相同类型的候选者。
可以使用@Qualifier注解:通过在@Autowired注解旁边添加@Qualifier注解,可以指定要注入的bean的名称。例如,如果有一个名为myBeanA和myBeanB的相同类型的bean,你可以在@Autowired
注解旁边使用@Qualifier("myBeanA")来指定注入myBeanA。
测试:在配置文件中增加一个 Student 的 bean。
<?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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启注解支持 --><context:annotation-config/><bean id="student" class="com.JavaClass.pojo.Student"/><bean id="student2" class="com.JavaClass.pojo.Student"/><bean id="study" class="com.JavaClass.pojo.Study"/><bean id="teacher" class="com.JavaClass.pojo.Teacher"/></beans>
Teacher 类中配合 @Qualifier 注解
package com.JavaClass.pojo;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;public class Teacher{@Autowired@Qualifier("student")private Student student;@Autowiredprivate Study study;private String name;/* getter/setter 和 toString */
}
测试代码结果如下“
@Resource
在Spring框架中,@Resource注解用于自动装配bean,它结合了@Autowired(按类型装配)和@Qualifier(按名称装配)的功能。在bean的自动装配过程中,可以使用@Resource注解的name属性来指定要注入的bean的名称,这个name属性的作用类似于@Qualifier注解的value属性,用于消除类型相同的bean之间的歧义。同时,@Resource还提供了额外的功能,如根据JNDI名称查找资源。
@Resource 与 @AutoWired 的不同在于:
-
@Resource 默认是按照 名称 来装配注入的,当找不到与名称匹配的 bean 才会按照类型来装配注入。
-
@Autowired 默认是按照 类型 装配注入的,如果想按照名称来装配注入,则需要结合 @Qualifier 一起使用;
通过 @Resource 注解来进行自动装配 bean
import javax.annotation.Resource;public class Teacher{@Resourceprivate Student student;@Resourceprivate Study study;private String name;/* getter/setter 和 toString */
<bean id="student1" class="com.JavaClass.pojo.Student"/>
<bean id="student2" class="com.JavaClass.pojo.Student"/>
<bean id="study" class="com.JavaClass.pojo.Study"/>
<bean id="teacher" class="com.JavaClass.pojo.Teacher"/>
运行结果如下:
@Resource 默认是通过 id 来查找 bean,而配置的两个 cat 的 bean 没有一个名称为默认查找的 cat,故而创建失败。
修改 @Resource,加入参数
@Resource(name = "student1")
private Student student;
@Resource
private Study study;
private String name;
再次执行程序:
程序没问题了
@Resource总结:
@Resource 注解在 Spring 中默认先尝试通过 bean 的名称(byName)进行自动装配。如果容器中不存在与指定名称相匹配的 bean,则会退回到通过 bean 的类型(byType)进行匹配。但是,如果在类型匹配时找到多个相同类型的 bean,并且没有通过 name 属性指定具体的 bean 名称来消除歧义,或者根本找不到任何该类型的 bean,那么自动装配将失败,并会抛出一个异常。