装配Bean
装配Bean(wiring Bean)
装配bean有3种方式:
- xml显示配置
- java显示配置
- 自动配置(注解方式)
自动配置(注解方式)
要使用自动配置,需要用到组件扫描和自动装配。
组件扫描(component scanning):扫描spring应用上下文(application context),发现要创建的bean。
自动装配(autowiring):自动满足bean的依赖关系,把一个bean所需要依赖的bean都装配到这个bean中。
组件扫描
组件扫描默认是不启动的。
启动组件扫描有2种方式:
- 基于java的配置
- 基于xml的配置
基于java的配置
使用注解@ComponentScan。
@ComponentScan:在类的声明处使用,会扫描这类所在的包及其子包的所有类。
基于xml的配置
在spring的xml文件中加入标签<context:component-scan>。
<context:component-scan base-package=“要扫描的包的全路径path”>:就会扫描path包及其子包的所有类
要让spring能自动创建我们想要创建的bean,除了要使用组件扫描,还要告诉spring哪些bean是要spring容器来创建的。
声明要创建的bean
使用注解@Component声明某个类是由spring来创建。
@Component:在需要spring创建的类的声明外使用这个注解。
自动装配
自动装配使用注解@Autowired。
@Autowired:在类的内部使用,表明这个类要依赖其他的某个类。
例子
创建组件扫描类
package com.test.soundsSystem;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class CDPlayerConfig {
}
创建bean
- 接口
package com.test.soundsSystem;
public interface CompactDisc {
void play();
}
- 实现类
package com.test.soundsSystem;
import org.springframework.stereotype.Component;
@Component
public class SgtPeppers implements CompactDisc {
public void play() {
System.out.println("SgtPeppers play");
}
}
测试
package com.test.soundsSystem;
import static org.junit.Assert.assertNotNull;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private CompactDisc cd;
@Test
public void test1() {
cd.play();
assertNotNull(cd); //运行成功,Junit窗口绿条
}
}
运行结果:
Junit窗口绿条
console控制台输出 SgtPeppers play
为组件扫描的bean命名(id)
spring会为应用上下文(application context)管理的bean赋予一个id,id默认是首字母小写的类名。我们可以为bean重新赋予新的id,有2种方式:
- @Component(“id”)
- @Named(“id”)
两个注解都是在类上使用。
关于@ComponentScan设置组件扫描的基础包
@ComponentScan是扫描这个注解定义的类所在的包及其子包,但是如果有2个或以上的基础包,或者这样说,基础包里包含了不想扫描的包,那么就需要使用@ComponentScan来设置基础包。有2个属性能完成这个功能:
- basePackages
- basePackageClasses
basePackages属性
basePackages属性有3种使用方式:
- @ComponentScan(“basePackagePath”)
- @ComponentScan(basePackages=“basePackagePath”)
- @ComponentScan(basePackages={“basePackagePath1”, “basePackagePath2”, …}):这里定义多个基础包
这里使用的是字符串类型,这种方法是类型不安全的。如果项目需要重构时,包的名称可能会改变。所以可以使用下面的basePackageClasses方式。
basePackageClasses
- @ComponentScan(basePackageClass={AClass.class, BClass.class, …})
会把AClass和BClass类所在的包作为基础包。
自动装配
可以使用注解@Autowired进行自动装配,@Autowired可以用在4种地方:
- 构造器
- setter方法
- 普通方法
- 属性
构造器
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
}
spring启动后,在创建CDPlayer bean时,会把相应的CompactDisc bean注入。
setter方法
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public void setCompactDisc(CompactDisc cd) {
this.cd = cd;
}
}
普通方法
其实普通方法和setter方法是一样的,就是方法的名称不重要,这里方法名改为insertCompactDisc。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public void insertCompactDisc(CompactDisc cd) {
this.cd = cd;
}
}
测试
- 接口
package com.test.soundsSystem;
public interface MediaPlayer {
void play();
}
- 实现类
package com.test.soundsSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
- 测试
package com.test.soundsSystem;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest {
@Autowired
private MediaPlayer mp;
@Test
public void test2() {
mp.play();
}
}
- 运行结果:
Junit窗口绿条
console控制台输出 SgtPeppers play
注意
CompactDisc的实现类有3种情况:
- 有1个
- 0个
- 有多个
1个
如果存在一个CompactDisc的实现类bean可以注入,那么注入成功。
0个
如果没有CompactDisc的实现类bean,那么在创建application context时会抛出异常。
可以在@Autowired中使用required=false,解决抛出异常的问题,但是此时这个属性变量就没有装配(注入),是null,要注意对null的判断。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
@Autowired(required=false)
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
}
多个
也是抛出异常
@inject
除了使用@Autowired进行装配,还可以使用注解@inject,使用方法和@Autowired一样。可以在构造器、setter方法和普通方法上使用。(能不能在属性上用不知道)
基于java装配bean
通常,我们使用上面的自动装配的方式,但是,不是任何情况都可以使用自动装配的方式,例如使用第三方库时,就不能在它的类上使用@Component和@Autowired。
需要完成创建bean实例并放入application context中,以及装配bean这2个任务。
基于java的显示配置JavaConfig是一种很好的显示配置方式。
现在,基于java的装配,不能使用@ComponentScan、@Component和@Autowired这3个注解。
使用注解@Configuration和注解@Bean。
- @Configuration:在JavaConfig类上使用。
- @Bean:在JavaConfig类的方法上使用。
@Bean的使用
在JavaConfig类的方法上使用
使用@Bean完成创建bean实例并放入application context中,以及装配bean这2个任务。
创建bean实例并放入application context中
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean的作用:
- 表示这个方法返回一个CompactDisc bean实例。
- 让spring执行这个sgtPeppers()方法,把创建的bean实例放到application context中。
注意:
- 方法体内要有创建bean实例的逻辑代码。
- 创建的bean实例的id是方法名,也就是sgtPeppers。
- 如果想要修改bean的id,有2种方式:
- 第一种,修改方法名。
- 第二种,@Bean注解那么属性可以设置id。
@Bean("newId")
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
装配bean
装配bean有2种方式
- 方法1
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}
}
对于cdPlayer()方法体中的sgtPeppers()方法,不是创建了新的SgtPeppers实例,而是application context中的SgtPeppers实例,因为加上了@Bean,spring会起到了拦截作用。
首先,spring调用sgtPeppers()得到SgtPeppers实例,把SgtPeppers实例放入application context中,接着spring调用cdPlayer(),把application context中的SgtPeppers实例作为参数,执行sgtPeppers()方法,并没有创建新的SgtPeppers实例。
- 方法2(推荐使用)
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
@Bean
public MediaPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
demo
- 定义接口和实现类bean
接口和上面的一样,这里不再重复写,实现类去掉@Component和@Autowired
package com.test.wiringWithJava;
public class SgtPeppers implements CompactDisc {
public void play() {
System.out.println("SgtPeppers play");
}
}
package com.test.wiringWithJava;
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
- 定义JavaConfig类
去掉@ComponentScan
package com.test.wiringWithJava;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
// @Bean
// public CDPlayer cdPlayer() {
// return new CDPlayer(sgtPeppers());
// }
@Bean
public MediaPlayer cdPlayer(CompactDisc cd) {
return new CDPlayer(cd);
}
}
- 测试
和上面的一样
- 运行结果:
Junit窗口绿条
console控制台输出 SgtPeppers play
基于xml装配bean
可以在xml文件中完成bean的创建和装配bean。
建议使用自动化装配和基于java的装配,xml的方式作为可以维护已有的xml文件即可。
xml方式的缺点:
- xml文件复杂。相对于基于java的显式装配,完成相同功能时,xml要更加复杂。下面是没有任何功能下的xml,可以看到,很复杂。
- xml容易出错。在定义时,class是字符串类型的类的全限定类名,容易出错。
- xml没有编译时的检查。
以下是没有任何功能的基本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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--spring xml code-->
</beans>
使用xml的方式就是不能使用@Component和@Autowired,@ComponentScan。就是不能使用注解。
下面详细说明关于xml完成创建bean和装配bean的方式
创建bean
使用标签,class属性,class值是字符串类型的bean的全限定类名。
<bean class="com.test.soundsSystem.SgtPeppers" />
这时bean的id是由全限定类名决定,id是com.test.wiringWithJava.SgtPeppers#0。如果定义下一个SgtPeppers的,那么id是com.test.wiringWithJava.SgtPeppers#1。
定义id
<bean id="compactDisc" class="com.test.soundsSystem.SgtPeppers" />
装配bean
在装配bean时,要用到bean的id,所以建议:在定义时,如果是作为被依赖的bean,那么定义id,否则不用定义id。
装配bean的方式:
- 基于构造器的方式
- 基于setter的方式
其中基于构造器的方式也有2种方式:
- <constructor-arg />
- c-命名空间
基于setter的方式也有2种方式:
- <property />
- p-命名空间
Spring为提供了c-命名空间作为替代方案,为提供了p-命名空间作为替代方案。
构造器方式
接口和实现类和之前的一样,只不过把注解都去掉。
构造器方式可以注入:
- 引用
- 字面量
- 集合(只有支持,c-命名空间不支持)
注入引用
- <constructor-arg />
使用属性ref,ref的值是要被依赖的bean的id。
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
- c-命名空间
使用c-命名空间需要添加新的声明。
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--code-->
</beans>
以下是使用c-命名空间的装配
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
@Override
public void play() {
cd.play();
}
}
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer" c:cd-ref="compactDisc">
</bean>
- c: :c:是c-命名空间要用的。
- cd:cd是构造器中的参数名称。
- -ref:-ref是注入引用使用的。
如果在重构代码时,把参数名修改了,那么这个xml会无效。下面是使用索引的方式。
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer" c:_0-ref="compactDisc">
</bean>
- 0:0表示第一个参数,0的前面有下划线_是因为数字不能作为属性的开头。
下一个参数就是c:_1-ref
如果只有一个参数,那么可以省略为以下代码,去掉索引。
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer" c:_-ref="compactDisc">
</bean>
注入字面量
先定义一个新的类。
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
public BlankDisc(String title, String artist) {
super();
this.title = title;
this.artist = artist;
}
@Override
public void play() {
System.out.println("play " + title + " by " + artist);
}
}
- <constructor-arg />
使用value属性注入。
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<constructor-arg value="Field of hope" />
<constructor-arg value="Seed" />
</bean>
- c-命名空间
c-命名空间注入字面量和注入引用不同之处就是去掉了-ref。
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc" c:_title="Field of hope" c:_artist="Seed">
</bean>
使用索引的方式:
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc" c:_0="Field of hope" c:_1="Seed">
</bean>
假设这个构造器只有一个参数,可以省略为以下代码。
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc" c:_="Field of hope">
</bean>
装配集合
如果构造器的参数是List类型。在里面使用或标签
-
<value>:集合元素是字面量
-
<ref bean=“bean id” />:集合元素是引用
-
装配String类型的List
实现类
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.println("play " + title + " by " + artist);
for(String track : tracks) {
System.out.println("-track " + track)
}
}
}
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<constructor-arg value="Field of hope" />
<constructor-arg value="Seed" />
<constructor-arg>
<list>
<value>Numb</value>
<value>Burn</value>
<!--...-->
</list>
</constructor-arg>
</bean>
- 装配引用类型的List
实现类
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<CompactDisc> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.println("play " + title + " by " + artist);
for(CompactDisc track : tracks) {
System.out.println("-track " + track)
}
}
}
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<constructor-arg value="Field of hope" />
<constructor-arg value="Seed" />
<constructor-arg>
<list>
<ref bean="bean_id_1" />
<ref bean="bean_id_2" />
<!--...-->
</list>
</constructor-arg>
</bean>
构造器的参数也可以是Set类型,如果是Set类型,使用标签替换上面的标签,其他的和标签的用法一样。
或是都可以装配List、Set和数组。
setter方式
我们可以使用setter方法的方式注入属性。
有2种方式:
- <property >
- p-命名空间
属性注入可以注入引用和字面量。
注入引用
- 实现类
public class CDPlayer implements MediaPlayer {
private CompactDisc compactDisc;
public void setCompactDisc(CompactDisc compactDisc) {
this.compactDisc = compactDisc;
}
@Override
public void paly() {
compactDisc.play();
}
}
- <property >
使用ref属性
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<!-- ... -->
</bean>
<bean class="soundsystem.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
对property标签的属性进行说明:
- name:类的属性名。
- ref:定义的bean的id。
- p-命名空间
使用p-命名空间,需要引入p-命名空间的约束。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--code-->
</beans>
<bean class="soundsystem.CDPlayer" p:compactDisc-ref="compactDisc">
</bean>
p-命名空间属性的语法规则如下:
- p::p:是p-命名空间需要的。
- compactDisc:类的属性名。
- -ref:表明后面的字符串是应用,是bean的id。
- =“compactDisc”:compactDisc是bean的id。
注入常量
- 实现类
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
@Override
public void play() {
System.out.println("play " + title + " by " + artist);
for(String track : tracks) {
System.out.println("-track " + track)
}
}
}
- <property >
<bean class="soundsystem.BlankDisc">
<property name="title" value="Field of hope" />
<property name="artist" value="Seed" />
<property name="tracks">
<value>Numb</value>
<value>Burning</value>
<!-- ... -->
</property>
</bean>
对property标签的属性进行说明:
- name:类的属性名。
- value:后面是字符串值,是字面量,不是引用。
- p-命名空间
<bean class="soundsystem.BlankDisc" p:title="Field of hope" p:artist="Seed">
<property name="tracks">
<value>Numb</value>
<value>Burning</value>
<!-- ... -->
</property>
</bean>
和注入引用的区别是没有了-ref。
p-命名空间不能装配集合。
如果想简化标签装配集合,可以使用util-命名空间。
- util-命名空间
要使用util-命名空间,需要引入util-命名空间的约束。
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--code-->
</beans>
<util:list id="trackList">
<value>Numb</value>
<value>Burning</value>
<!-- ... -->
</util:list>
<bean class="soundsystem.BlankDisc" p:title="Field of hope" p:artist="Seed" p:tracks-ref="trackList">
</bean>
混合配置
我们可以使用自动化配置,或者基于Java或xml的显示配置来装配bean。因为这三种方式并不互斥,所以,可以混合使用。
建议使用自动化配置,如果需要显示配置,也建议使用JavaConfig,在需要的时候才使用xml。
在使用自动化配置时,需要使用显示配置启动组建扫描(@ComponentScan或<context:component-scan >)
现在有一个问题,如果配置的bean太多,那么在一个文件中完成所有的bean的配置的话,维护起来很麻烦。所以,可以在一个配置的文件中引入其他的配置文件,这样,就可以把多个bean的配置分开到多个配置文件中了。
有2种情况:
- 在JavaConfig中引入JavaConfig和xml
- 在xml中引入JavaConfig和xml
通常,使用一个根的JavaConfig或根的xml,来引入所有的bean的配置,然后,在这个根文件中开启组建扫描。
在JavaConfig引入JavaConfig和xml
引入JavaConfig
使用注解@Import
语法:@Import({xxxConfig.class, yyyConfig.class, …})
如果只引入一个文件,那么@Import中的{}可以省略
@Configuration
public class CDPlayerConfig {
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
return new CDPlayer(compactDisc);
}
}
@Configuration
public class CDConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
@Configuration
@Import({CDPlayerConfig.class, CDConfig.class})
public class SoundSystemConfig {
}
引入xml
使用注解@ImportResource
语法:@ImportResource(“classpath:xml文件相对于JavaConig文件的路径”)
CDPlayerConfig存在,CompactDisc在xml配置。xml文件叫cd-config.xml。
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<constructor-arg value="Field of hope" />
<constructor-arg value="Seed" />
<constructor-arg>
<list>
<value>Numb</value>
<value>Burn</value>
<!--...-->
</list>
</constructor-arg>
</bean>
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}
在xml中引入JavaConfig和xml
引入xml
使用标签<import>
语法:<import resource=“相对于根xml的需要引入的xml配置文件的路径” />
cd-config.xml
<bean id="compactDisc" class="com.test.soundsSystem.BlankDisc">
<constructor-arg value="Field of hope" />
<constructor-arg value="Seed" />
<constructor-arg>
<list>
<value>Numb</value>
<value>Burn</value>
<!--...-->
</list>
</constructor-arg>
</bean>
cdPlayer-config.xml
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer" c:_-ref="compactDisc">
</bean>
soundSystem.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="cd-config.xml" />
<import resource="cdPlayer-config.xml" />
</beans>
引入JavaConfig
引入JavaConfig的方式是把JavaConfig作为bean在xml中配置。
@Configuration
public class CDConfig {
@Bean
public CompactDisc sgtPeppers() {
return new SgtPeppers();
}
}
cdPlayer-config.xml
<bean id="cdPlayer" class="com.test.soundsSystem.CDPlayer" c:_-ref="compactDisc">
</bean>
soundSystem.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="soundSystem.CDConfig" />
<import resource="cdPlayer-config.xml" />
</beans>