依赖注入
传统的Java开发中,程序员负责在代码中用new创建对象以及维护对象之间的关系。使用Spring框架后,Spring可以自动进行对象创建和对象关系的维护,也就是所谓的依赖注入。
从Spring的总体模块组成来看,依赖注入是Spring核心容器模块所做的事情。
要想让Spring自动完成依赖注入,有三种配置方法:
- 自动化配置 在需要使用的类中用@Autowired进行配置
- JavaConfig 在专门的Config类中配置
- XML配置 在专门的XML文件中进行配置。也是Spring最开始的配置方法
方案一 自动化配置
例子类图:
CDPlayer可以播放CD的一个例子。
如果要实现自动化配置,需要两步:
- 告诉Spring需要创建哪些对象。通过组件扫描的方式告诉Spring。
- 自动装配:构建对象和对象之间的关系。
组件扫描
在需要创建对象的类上方加上@Component
注解。
还需要告诉Spring,去哪些类或哪些包下去扫描这样的类。这一步通过配置类(通过@Configuration
标明配置类)来实现。在配置类上通过@ComponentScan
告诉Spring要在当前配置类所在的包和子包下扫描有@Component
的类。
需要注意的是,凡是涉及到自动注入的类,都需要@Component
进行声明,交给Spring容器进行管理,否则Spring还是无法进行对象关系的管理
自动装配
三种自动装配的方式。
构造方法注入。
- @Autowired指定一个构造方法时,这个组件被Spring实例化时就会调用该构造方法。如果构造方法的参数中需要传入其他对象,Spring便会从容器中找一个符合声明类型的对象传入。
- Autowired指定的构造方法至少有一个参数,并且这个参数指定的类型需要在容器中能够找到,否则会报错。
- 如果不用@Autowired指定,则Spring会使用默认构造函数进行对象构造;这种情况下假如不存在默认构造函数,Spring也会报错。
- 不能同时给多个构造函数声明Autowired,否则Spring不知道调用哪个构造方法。
属性注入。
- @Autowired指定一个Set方法,Spring可以自动调用这个方法,传入参数中指定类型的Bean
- 本质上,对于任何一个非构造函数的方法都可以加上@Autowired,只不过如果参数中有不存在于容器中的对象,就会报错。
- Spring依照代码编写前后顺序,依次调用带有Autowired标记的方法。
@Component
public class CDPlayer implements MediaPlayer {
private CompactDisc cd;
private Power power;
// 调用顺序 2 验证了多个参数也可以注入
@Autowired
public void setCd(CompactDisc cd,Power power) {
this.cd = cd;
this.power=power;
}
// 调用顺序 3 重复注入,可以覆盖之前的注入
@Autowired
public void lalala(CompactDisc cd){
this.cd=new CompactDisc() {
@Override
public void play() {
System.out.println("hacked");
}
};
}
// 调用顺序:1 构造函数首先被调用 cd 为容器内CompactDisc的一个bean对象
@Autowired
public CDPlayer(CompactDisc cd) {
this.cd = cd;
}
public void play() {
power.on();
cd.play();
}
}
// 输出:
//Power on
//hacked
其实构造函数和Set方法(属性注入)的注入方法,Spring只负责传入参数,至于方法内部怎么做Spring是管不着的。对于方法上的Autowired,Spring只会自动调用这个方法,传入相应的参数而已。
使用习惯上,如果两个对象关系密切就使用构造函数注入,否则使用属性注入。
直接在成员变量上加@Autowired
这种情况,Spring直接给这个成员变量初始化指向容器内对象。
其他注意点
ComponentScan注解可以带参数,指定要搜的包名、类名。如@ComponentScan(basePackages={“aaa”,”bbb”}) 或者 @ComponentScan({CDPlayer.class})。也可以通过@ComponentScan(basePackageClasses = MyInter.class)来指定检查哪个类所在的包。使用一个空的接口Maker Interface可以避免后期修改。
在测试类中,通过@ContextConfiguration(class= CDPlayerConfig.class)可以指定当前类使用的Spring上下文,然后就可以注入和使用Spring容器中的对象了。
@Autowired(required=false)声明时,即使容器内并没有要装配的对象也不会报错(但是引用可能为null)
方案二 JavaConfig配置类
配置类
通过配置类(配置类需要用@Configuration注解进行声明),用@Bean来声明需要在容器中创建的对象。声明的需要是一个方法,返回值就是一个需要交给Spring容器管理的对象。Spring容器会自动调用这些方法,并得到返回值做为Bean对象在容器中进行管理。
在调用这些方法的时候,如果需要传入参数,Spring便会自动试图从容器中找一个合适的对象引用传入。但也可以不用参数,而是直接在方法体中调用其他@Bean方法,实现注入。例如:
@Configuration
public class CDPlayerConfig {
@Bean
public CompactDisc compactDisc() {
// return new SgtPeppers();
SgtPeppers sgtPeppers = new SgtPeppers();
sgtPeppers.play();
return sgtPeppers;
}
// 传入参数
// @Bean
// public CDPlayer cdPlayer(CompactDisc cd) {
// return new CDPlayer(cd);
// }
// 直接调用@Bean方法
@Bean
public CDPlayer cdPlayer() {
//这里的方法调用被Spring拦截,并传入已经存在于容器内的对象
return new CDPlayer(compactDisc());
}
// 就算多次调用函数,容器内也只有一个CD实例
@Bean
public CDPlayer cdPlayer2() {
//这里的方法调用被Spring拦截,并传入已经存在于容器内的对象
return new CDPlayer(compactDisc());
}
}
在需要使用容器中的对象的位置,还用@Autowired注解进行声明,Spring便会传入一个容器内的对象。
Bean对象的名字
需要创建成Bean对象的类可以实现一个BeanNameAware接口中的方法 setBeanName。Spring在容器中实例化这个对象后,会回调这个方法,将当前对象的名字传入。
在配置类中可以通过注解参数指定Bean对象的名字,如@Bean(name=”newPlayer”) 。默认是取方法的名字。
顺便一提,上一种方法(自动化配置)中,容器内Bean对象的默认名字是类名(第一个字母改为小写)。如果需要指定名字,使用诸如@Component(“newPlayer”)的注解就可以起名。
业务代码和配置代码分离
通过配置类配置Bean的好处是,业务代码中不需要加入什么Spring的内容,也就是Spring对业务代码的侵入性大大降低了。为了减小耦合、建立清晰的项目结构,通常把配置类放在单独的地方,和业务代码隔开。
XML文件配置
是Spring最先出现的一种容器对象配置方法,可以在XML中用一定格式指定配置,就可以在其他地方用xml文件构建上下文(Spring容器)。
虽然是Spring的起家方案,但是因为陈旧、不利于编译器帮忙进行错误检查,已经很少有人使用。现在使用最多的是第一种方案,然后就配置类的第二种方案。
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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 中间是bean定义 -->
</beans>
Tips: 测试类上使用 @ContextCofiguration 声明上下文时,如果不加参数,默认会找同名的xml配置文件配置上下文。
构造方法配置
constructor-arg 指定构造方法参数,如:
<bean id="compactDisc" class="soundsystem.SgtPeppers" />
<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
传入参数的类型不仅有引用,也可以之间传入值:
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>
甚至可以直接传入list,在xml用list元素声明即可:
<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<!-- 省略内容 -->
</list>
</constructor-arg>
</bean>
另外还有set元素,可以保证元素不重复。
c命名空间
在头部增加命名空间的定义,就可以在bean的定义中使用c命名空间进行属性声明。命名空间定义例如下面第4行所示:
<?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">
</beans>
在使用时,引用参数传递可以这样使用:
<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_0-ref="compactDisc" />
_0-ref 指的是传入的第一个参数,_1-ref 指的是第二个参数,以此类推。如果只有一个参数甚至可以不加数字。
如果是值传递:
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles" />
属性配置
可以通过set方法进行配置。Spring寻找set方法就是将属性首字母大写然后前面加set,然后将后面的参数传入。例如:
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer">
<property name="compactDisc" ref="compactDisc" />
</bean>
在这个配置中,Spring自动找到类的 setCompactDisc 方法,然后传入名为 compactDisc 的Bean引用,将该对象的类进行配置。
Tips: xml配置方法对代码没有侵入性,减少对框架的依赖
同样也可以进行属性值的注入,以及list和set的注入:
<bean id="compactDisc"
class="soundsystem.properties.BlankDisc">
<property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
<property name="artist" value="The Beatles" />
<property name="tracks">
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<!-- 省略 -->
</list>
</property>
</bean>
p命名空间
在头部增加p命名空间的声明(第4行):
<?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">
</beans>
但是p命名空间不能解决list赋值的问题,所有如果想直接赋值列表,还是要使用property标签。
实在想用可以用util先声明列表后使用。
如果想对第三方库进行装配,由于不能改变库代码,所以只能使用配置类或配置xml的方式配置
混合配置
三种方法可以混合使用。
配置类可以import其他配置类和xml配置文件。只需使用最上层的跟配置就可以用到所有import到的配置。例如:
@Configuration
@Import(CDPlayerConfig.class)
@ImportResource("classpath:cd-config.xml")
public class SoundSystemConfig {
}
也可以使用xml文件做为根,在xml中可以创建配置类的Bean。看到Bean是配置类,Spring就会自动调用配置类中的@Bean方法创建Bean对象。
<bean class="soundsystem.CDConfig" />
其他功能
Profile
可以指定某些Bean只在特定的环境下才会创建。
@Profile(“dev”)
@Profile(“prod”)
决定用哪个Bean要靠环境指定。可以通过激活的方式指定环境:
- 默认spring.profiles.default=”dev”,所以默认会使用dev的Bean。
- 在java运行参数中可以通过 -D的方式修改spring.profiles.active=”sys”,就可以使用prod的Bean。-D参数还可以起到不同环境读不同配置文件的作用,例如 java -jar -Dspring.profiles.active=sys 就可以让Spring运行时读取application-sys.properties而不是application.properties
- @ActiveProfiles(“dev”) 通过注解激活。
Conditional
有条件地创建Bean。通过@Conditional(**.class)指定一个实现了Condition接口的类。类实现boolean matches接口后就可以进行判断,根据条件决定要不要创建Bean。
例如类中可以去读取系统环境变量,实现有某个环境变量时创建,但没有时不创建的效果。
Scope
可以指定Bean的四种作用域:
- Singleton 单例 默认
- Prototype 多例
- Session 一次会话包含多次请求响应。一次会话创建一个
- Request 一次请求。每次请求创建一个。
Spring将会话/请求作用域的Bean注入到单例Bean的时候,会通过代理对象实现。实际运行时,Spring可以根据上下文区分后面具体的会话/请求作用域Bean是哪个。
自动装配的歧义性
实现某个接口的Bean可能有多个,导致Spring自动注入的时候不知道该选哪个,产生报错。
@Primary可以指定创建某个Bean
@Qualifier(“mycd”)指定限定符。在注入时也用带有相同限定符的@Qualifier(“mycd”)去指定,Spring就能对应起来了。
还可以自己定义注解,但是自定义的注解定义中也是通过Qualifier注解实现的。
不加任何修饰的注解的Bean,Spring默认会去创建。