装配Bean
在Spring中,对象无需自己查找或创建与其所关联的其他对象,统一由容器负责把需要相互协作的对象引用赋予各个对象。
创建应用对象之间协作关系的行为通常称为装配(wiring),也是依赖注入的本质(DI)。
Spring的三种装配机制
- 在XML中显示配置
- 在java中进行显示配置
- 隐式的bean发现机制和自动装配
自动化装配Bean
Spring 从两个角度实现自动化装配
- 组件扫描:Spring会自动发现应用上下文中所创建的bena。
- 自动装配:Spring会自动满足bean之间的依赖。
创建可被发现(扫描)的bean:@Component
package soundsystem;
/**
* soundsystem : 声响系统
* CompactDisc :compact disc 唱片,光盘
* 这是一个唱片接口
*/
public interface CompactDisc {
void play();
}
package soundsystem;
import org.springframework.stereotype.Component;
/**
* @Component 该注解表名,要将该类作为组件类,并告知Spring为这个类创建bean
* 注意:
* 组件扫描默认是不启用的,需要显式配置Spring,让其寻找带有@Component注解的类
*/
@Component
public class SgtPeppers implements CompactDisc {
// 名称
private String title = "Hello kk";
// artist 唱作人
private String artist = "XiaoZhang";
public void play() {
System.out.println("播放:"+title+"by"+artist);
}
}
创建配置类:@Configuration,启用注解扫描:@ComponentScan
package soundsystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration
* @ComponentScan 启用Spring组件扫描,默认扫描与配置类相同的包,以及下面的子包
* 通过XML启用组件扫描:<context:component-scan>元素
* <context:component-scan base-package="soundsystem">
*/
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
对组件扫描进行测试:@RunWith、@ContextConfiguration
package soundsystem;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/*
指定在单元测试启动的时候自动创建Spring的上下文对象
手动获取Spring上下文对象
ApplicationContext context = new AnnotationConfigApplicationContext(CDPlayerConfig.class);
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
*/
@ContextConfiguration(classes = CDPlayerConfig.class)
/*
RunWith的value属性指定以spring test的SpringJUnit4ClassRunner作为启动类
如果不指定启动类,默认启用的junit中的默认启动类
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
public class CDPlayerTest {
ApplicationContext context = new AnnotationConfigApplicationContext(CDPlayerConfig.class);
/*
自动装配
手动装配:
CompactDisc compactDisc = (CompactDisc) context.getBean("sgtPeppers");
*/
@Autowired
private CompactDisc compactDisc;
@Test
public void compactDiscClassTest(){
System.out.println(compactDisc);
}
}
为组件扫描的bean命名
spring应用上下文中,所用的bean都会给定一个ID(在XML中,装配bean需要指定ID)。
@Component:默认将类名的首字母小写,设置为bean的ID
@Component源码:
public @interface Component {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";
}
自定义bean ID:
@Component("mySgtPeppers")
public class SgtPeppers implements CompactDisc {...}
设置组件扫描的基础包
@ComponentScan部分源码:
public @interface ComponentScan {
/**
* 指定要扫描的包:默认属性
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 要扫描的多个包
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定包中所包含的类或接口,这些类中所包含的包将作为组件扫描的基础包。
* 可以在包中,通过标记接口的方式, 保持对重构友好的接口引用,同事避免引用任何实际的应用程序代码
*/
Class<?>[] basePackageClasses() default {};
通过为bean添加注解实现自动装配:@Autowired
自动装配就是让Spring自动满足bean依赖的一种方法。
@Autowired可以使用在属性、构造器、setter和其他方法上,Spring会尝试满足方法参数上声明的依赖。
@Autowired(required = false):当required = false,Spring依然会进行装配,但如果没有匹配的bean,Spring将会让这个bean处于未装配状态(可能会面临空指针异常)。
package soundsystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* CD播放器类
*/
@Component
public class CDPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd){
this.cd = cd;
}
public void play(){
System.out.println("CD播放器播放音乐...");
cd.play();
}
}
通过Java代码装配bean
在有的时候,自动化装配不能进行时,我们就需要用到显式装配的方式。
相比于XML配置,Java配置更加方便、强大、安全,且对重构友好。
创建配置类
package soundsystem;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration 指定该类为配置类
* 该类应该包含在Spring应用上下文中如何创建bean的细节
*/
@Configuration
public class CDPlayerConfig {
}
声明简单的bean
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Configuration 指定该类为配置类
* 该类应该包含在Spring应用上下文中如何创建bean的细节
* 在JavaConfig中声明bean,需要编写一个方法,这个方法需要创建所需类型的实例,然后给该方法加上@Bean注解
*/
@Configuration
public class CDPlayerConfig {
/**
* @Bean 注解会告诉Spring这个方法会返回一个对象,该对象要注册为Spring应用上下文的bean。
* 默认情况下bean的ID与方法名一致,可以通过@Bean(name="mySgtPeppers")进行自定义。
*/
@Bean()
public CompactDisc sgtPeppers(){
return new SgtPeppers();
}
}
借助JavaConfig实现注入
@Bean
public CDPlayer cdPlayer(){
return new CDPlayer(sgtPeppers());
}
或者
/**
* 这种方式相比上中方式事实上更好,降低了依赖。这里甚至没有强求CompactDisc必须在JavaConfig中声明。
* 实际上可以通过组价扫描功能自动发现或通过XML来进行配置。
*/
@Bean
public CDPlayer cdPlayer(CompactDisc sgtPeppers){
return new CDPlayer(sgtPeppers);
}
看起来CompactDisc的实例是通过sgtPeppers()方法获取,但并非如此。
因为在sgtPeppers()方法上添加@Bean注解,Spring会拦截所有对它的调用,并确保直接返回该方法所创建的bean,而不是每次都对其进行实际的调用。
注: 在spring容器中,默认情况所有bean都是单例的。
带有@Bean注解的方法可以采用任何有必要的Java功能来产生实例。
通过XML装配Bean
创建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">
</beans>
声明一个简单的bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<context:component-scan base-package="soundsystem"/>
<!--
类似于JavaConfig中的@Bean注解
在没有明确指明id的情况下,将会根据全限定类名来进行命名
soundsystem.SgtPeppers#0,其中#0是用来计数的
通常会使用id属性进行明确的指定
XML 配置的缺点:不能从编译期的类型检查中受益(在IDEA、STS这种比较强大的编译期中,可以检查出来)
-->
<bean class="soundsystem.SgtPeppers" id="compactDisc" />
</beans>
借助构造器注入初始化bean
在XML中只有一种声明bean的方式:使用元素,并制定class属性。但在依赖注入的时候,有多种方案和风格。
构造器注入bean引用
<constructor-arg/>
方式
<bean class="soundsystem.CDPlayer" id="cdPlayer">
<constructor-arg ref="compactDisc"/>
</bean>
- c-命名空间方式
<!--
使用c-命名空间来声明构造函数参数
c:cd-ref
c: 命名空间的前缀
cd:要装配的构造器参数名,public CDPlayer(CompactDisc cd){...}
-ref:命名的约定,告诉spring装配的是一个bean引用,这个bean的名字是compactDisc而不是字面量
c:_0-ref="compactDisc"
_0:在这里表示参数索引,即参数位置
_:如果只有一个参数,可以写作 c:_-ref="compactDisc"
-->
<bean class="soundsystem.CDPlayer" id="cdPlayer" c:cd-ref="compactDisc"/>
将字面量注入到构造器中
- constructor-arg元素注入
<bean class="soundsystem.SgtPeppers" id="compactDisc">
<!--
value 表名给定的值要以字面量形式注入到构造器中
index 参数索引,可选
-->
<constructor-arg value="老街" index="0"/>
<constructor-arg value="lrh" index="1"/>
</bean>
- c-命名空间
<bean class="soundsystem.SgtPeppers" id="compactDisc"
c:_0="老街"
c:_1="李荣浩"
/>
XML装配集合
注意:c-命名空间对于集合无法进行装配
<bean class="soundsystem.BlankDisc" id="blankDisc">
<constructor-arg index="0" value="耳朵" />
<constructor-arg index="1" value="李荣浩" />
<constructor-arg>
<list>
<value>耳朵</value>
<value>麻雀</value>
<value>贝贝</value>
<value>老街</value>
</list>
</constructor-arg>
</bean>
<list/>, <set/>, <map/>, <props/>
对于list、set、array相互之间可以通用(可以无视报错)
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
设置属性:property
对强依赖使用构造器注入,对可选性依赖使用属性注入。
<property>
元素为属性的setter方法所提供的功能与<constructor-arg>
为构造器提供的功能是一样的。
c-命名空间替代<constructor-arg>
p-命名空间替代<property>
:添加xmlns:p="http://www.springframework.org/schema/p"
使用<property>
元素
<bean class="soundsystem.CDPlayer" id="cdPlayer">
<property name="cd" ref="blankDisc" />
</bean>
使用p-命名空间
<!--
p:cd-ref="blankDisc"
p-:p命名空间前缀
cd:属性名
-ref:注入bean引用,不带-ref就装配字面量
"blankDisc":所注入的bean的id
-->
<bean class="soundsystem.CDPlayer" id="cdPlayer" p:cd-ref="blankDisc"/>
package soundsystem;
/**
* CD播放器类
*/
public class CDPlayer {
private CompactDisc cd;
public CDPlayer(){}
public void play(){
System.out.println("CD播放器播放音乐...");
cd.play();
}
// 提供setter方法,用来实现属性注入
public void setCd(CompactDisc cd) {
this.cd = cd;
}
}
package soundsystem;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/*
指定在单元测试启动的时候自动创建Spring的上下文对象
手动获取Spring上下文对象
ApplicationContext context = new AnnotationConfigApplicationContext(CDPlayerConfig.class);
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
*/
@ContextConfiguration(locations = "classpath:applicationContext.xml")
/*
RunWith的value属性指定以spring test的SpringJUnit4ClassRunner作为启动类
如果不指定启动类,默认启用的junit中的默认启动类
*/
@RunWith(value = SpringJUnit4ClassRunner.class)
public class CDPlayerTest {
@Autowired
private CDPlayer cdPlayer;
@Test
public void compactDiscClassTest(){
cdPlayer.play();
}
}
util-命名空间
使用util-命名空间需要添加
命名空间:xmlns:util="http://www.springframework.org/schema/util"
约束:http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
用它可以完成集合的定义,并用-ref的方式应用
<bean class="soundsystem.BlankDisc" id="blankDisc"
c:_0="《耳朵》"
c:_1="李荣浩"
c:_2-ref="trackList" />
<util:list id="trackList">
<value>耳朵</value>
<value>麻雀</value>
<value>贝贝</value>
<value>老街</value>
</util:list>
下方引用形式也正确
<bean class="soundsystem.BlankDisc" id="blankDisc">
<constructor-arg index="0" value="耳朵"/>
<constructor-arg index="1" value="李荣浩"/>
<constructor-arg index="2" ref="trackList" />
</bean>
导入和混合配置
自动化装配、JavaConfig和XML配置在Spring中相互之间是不排斥的。
自动装配会考虑Spring容器中所有的bean,不管他在JavaConfig或者XMl中声明,通过组件扫描的方式都能获取到。
JavaConfig的相互引用
使用
@Import(CDConfig.class)
可以进行JavaConfig之间的互相引用
CDPlayerConfig
@Configuration
@Import(CDConfig.class)
@ComponentScan
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
CDConfig
@Configuration
public class CDConfig {
@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}
}
在JavaConfig中引用XML配置
通过
@ImportResource({"class:applicationContext.xml"})
注解引入一个或多个XML配置文件
自动装配的时候,优先使用明确指定id的类
@Configuration
@ImportResource({"classpath:applicationContext.xml"})
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc){
return new CDPlayer(compactDisc);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
https://www.springframework.org/schema/util/spring-util.xsd
">
<bean class="soundsystem.BlankDisc" id="blankDisc"
c:_0="《耳朵》"
c:_1="李荣浩"
c:_2-ref="trackList"
/>
<util:list id="trackList">
<value>耳朵</value>
<value>麻雀</value>
<value>贝贝</value>
<value>老街</value>
</util:list>
</beans>
在XML配置中引用XML配置
<import resource="applicationContext01.xml" />
在XML配置中引用JavaConfig
<bean class="soundsystem.CDPlayerConfig" />
根配置
不管使用JavaConfig还是使用XML进行装配,我们通常都会创建一个根配置(rootconfiguration),这个配置会将两个或两个以上的装配类或XML文件组合起来。同时也会在根配置中启用组件扫描。