服务端计算——Spring依赖注入
依赖注入
传统的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标记的方法。
1@Component
2public class CDPlayer implements MediaPlayer {
3 private CompactDisc cd;
4 private Power power;
5
6 // 调用顺序 2 验证了多个参数也可以注入
7 @Autowired
8 public void setCd(CompactDisc cd,Power power) {
9 this.cd = cd;
10 this.power=power;
11 }
12
13 // 调用顺序 3 重复注入,可以覆盖之前的注入
14 @Autowired
15 public void lalala(CompactDisc cd){
16 this.cd=new CompactDisc() {
17 @Override
18 public void play() {
19 System.out.println("hacked");
20 }
21 };
22 }
23
24 // 调用顺序:1 构造函数首先被调用 cd 为容器内CompactDisc的一个bean对象
25 @Autowired
26 public CDPlayer(CompactDisc cd) {
27 this.cd = cd;
28 }
29
30 public void play() {
31 power.on();
32 cd.play();
33 }
34}
35// 输出:
36//Power on
37//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方法,实现注入。例如:
1@Configuration
2public class CDPlayerConfig {
3
4 @Bean
5 public CompactDisc compactDisc() {
6// return new SgtPeppers();
7 SgtPeppers sgtPeppers = new SgtPeppers();
8 sgtPeppers.play();
9 return sgtPeppers;
10 }
11
12 // 传入参数
13// @Bean
14// public CDPlayer cdPlayer(CompactDisc cd) {
15// return new CDPlayer(cd);
16// }
17
18 // 直接调用@Bean方法
19 @Bean
20 public CDPlayer cdPlayer() {
21 //这里的方法调用被Spring拦截,并传入已经存在于容器内的对象
22 return new CDPlayer(compactDisc());
23 }
24
25 // 就算多次调用函数,容器内也只有一个CD实例
26 @Bean
27 public CDPlayer cdPlayer2() {
28 //这里的方法调用被Spring拦截,并传入已经存在于容器内的对象
29 return new CDPlayer(compactDisc());
30 }
31
32}
在需要使用容器中的对象的位置,还用@Autowired注解进行声明,Spring便会传入一个容器内的对象。
Bean对象的名字
需要创建成Bean对象的类可以实现一个BeanNameAware接口中的方法 setBeanName。Spring在容器中实例化这个对象后,会回调这个方法,将当前对象的名字传入。
在配置类中可以通过注解参数指定Bean对象的名字,如@Bean(name=“newPlayer”) 。默认是取方法的名字。
顺便一提,上一种方法(自动化配置)中,容器内Bean对象的默认名字是类名(第一个字母改为小写)。如果需要指定名字,使用诸如@Component(“newPlayer”)的注解就可以起名。
业务代码和配置代码分离
通过配置类配置Bean的好处是,业务代码中不需要加入什么Spring的内容,也就是Spring对业务代码的侵入性大大降低了。为了减小耦合、建立清晰的项目结构,通常把配置类放在单独的地方,和业务代码隔开。
XML文件配置
是Spring最先出现的一种容器对象配置方法,可以在XML中用一定格式指定配置,就可以在其他地方用xml文件构建上下文(Spring容器)。
虽然是Spring的起家方案,但是因为陈旧、不利于编译器帮忙进行错误检查,已经很少有人使用。现在使用最多的是第一种方案,然后就配置类的第二种方案。
xml文件头需要使用特定的头部,例如:
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7 <!-- 中间是bean定义 -->
8
9</beans>
Tips: 测试类上使用 @ContextCofiguration 声明上下文时,如果不加参数,默认会找同名的xml配置文件配置上下文。
构造方法配置
constructor-arg 指定构造方法参数,如:
1<bean id="compactDisc" class="soundsystem.SgtPeppers" />
2
3<bean id="cdPlayer" class="soundsystem.CDPlayer">
4 <constructor-arg ref="compactDisc" />
5</bean>
传入参数的类型不仅有引用,也可以之间传入值:
1<bean id="compactDisc" class="soundsystem.BlankDisc">
2 <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
3 <constructor-arg value="The Beatles" />
4</bean>
甚至可以直接传入list,在xml用list元素声明即可:
1<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
2 <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
3 <constructor-arg value="The Beatles" />
4 <constructor-arg>
5 <list>
6 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
7 <value>With a Little Help from My Friends</value>
8 <value>Lucy in the Sky with Diamonds</value>
9 <!-- 省略内容 -->
10 </list>
11 </constructor-arg>
12</bean>
另外还有set元素,可以保证元素不重复。
c命名空间
在头部增加命名空间的定义,就可以在bean的定义中使用c命名空间进行属性声明。命名空间定义例如下面第4行所示:
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:c="http://www.springframework.org/schema/c"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6</beans>
在使用时,引用参数传递可以这样使用:
1<bean id="cdPlayer" class="soundsystem.CDPlayer"
2 c:_0-ref="compactDisc" />
_0-ref 指的是传入的第一个参数,_1-ref 指的是第二个参数,以此类推。如果只有一个参数甚至可以不加数字。
如果是值传递:
1<bean id="compactDisc" class="soundsystem.BlankDisc"
2 c:_0="Sgt. Pepper's Lonely Hearts Club Band"
3 c:_1="The Beatles" />
属性配置
可以通过set方法进行配置。Spring寻找set方法就是将属性首字母大写然后前面加set,然后将后面的参数传入。例如:
1<bean id="cdPlayer" class="soundsystem.properties.CDPlayer">
2 <property name="compactDisc" ref="compactDisc" />
3</bean>
在这个配置中,Spring自动找到类的 setCompactDisc 方法,然后传入名为 compactDisc 的Bean引用,将该对象的类进行配置。
Tips: xml配置方法对代码没有侵入性,减少对框架的依赖
同样也可以进行属性值的注入,以及list和set的注入:
1 <bean id="compactDisc"
2 class="soundsystem.properties.BlankDisc">
3 <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
4 <property name="artist" value="The Beatles" />
5 <property name="tracks">
6 <list>
7 <value>Sgt. Pepper's Lonely Hearts Club Band</value>
8 <value>With a Little Help from My Friends</value>
9 <value>Lucy in the Sky with Diamonds</value>
10 <!-- 省略 -->
11 </list>
12 </property>
13 </bean>
p命名空间
在头部增加p命名空间的声明(第4行):
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:p="http://www.springframework.org/schema/p"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
6
7</beans>
但是p命名空间不能解决list赋值的问题,所有如果想直接赋值列表,还是要使用property标签。
实在想用可以用util先声明列表后使用。
如果想对第三方库进行装配,由于不能改变库代码,所以只能使用配置类或配置xml的方式配置
混合配置
三种方法可以混合使用。
配置类可以import其他配置类和xml配置文件。只需使用最上层的跟配置就可以用到所有import到的配置。例如:
1@Configuration
2@Import(CDPlayerConfig.class)
3@ImportResource("classpath:cd-config.xml")
4public class SoundSystemConfig {
5
6}
也可以使用xml文件做为根,在xml中可以创建配置类的Bean。看到Bean是配置类,Spring就会自动调用配置类中的@Bean方法创建Bean对象。
1<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默认会去创建。