Spring framework 之依赖注入
Contents
Spring framework 系列之依赖注入,将对象之间的依赖关系减弱,进而其实例化也交给 Spring,Spring 维护一个上下文,并以声明式的方式对依赖进行满足(注入),进一步的强化面向接口编程的思想。
测试代码搭建
面向接口编程,接口的实现就可以通过依赖注入进来,这种模式又叫做依赖注入或者控制反转:
- IOC (inversion of control) 控制反转
- DI (dependency injection) 依赖注入
为了阐述 Spring 的依赖注入,先规划下这样的场景,在 Service 里为了减小耦合,使用 Dao 的接口变量来操作数据库,这时候这个 Service 是 has Dao 的结构。
新建一个工程名为 demo 的 maven 空项目,以在 Web 开发中常用的 Dao->Service 的关系来做讲解,部分示例代码如下:
实体类(POJO)(getter/setter 最好使用 IDEA 自动生成):
|
|
接口和接口的实现如下:
|
|
数据库访问层:
|
|
spring 的依赖利用 maven 下面的两种方式,推荐使用新方式,老方式在 jdk 1.8 时候会有 …… are only available on JDK 1.5 and higher
的错误,参见,而新的方式是把 spring 拆解成为 Spring core、Spring beans、Spring contexts 等。
|
|
常规的方式是在 service 代码里初始化时候 new 一个接口实现,然后传递给接口变量。这样代码入侵程度较高。spring 的 IOC/DI 提供了较好的解决方案,以上面代码为例下面进行讲解。
配置方式
创建 src/main/resources/beans.xml
文件,内容如下:
|
|
bean
的可以理解为最终会由 spring 根据指定的 class 参数 new 出来的对象,而子节点 property
对应着该对象中由 name 指定名称的某个属性,ref 参数指定了其依赖(即 UserDaoImpl)。
spring 会在创建 userService 这个 bean 时候将根据反射调用 userDao 属性的 set 方法(需要注入的属性 get/set 方法),将提前创建完成的 UserDaoImpl 的 bean 赋值给其属性。这了就实现了 bean 的装配、依赖注入。
最后在测试代码里可以编写下面的测试用例用于后续的测试。
|
|
测试用例使用 junit,所以需要添加 maven 依赖:
|
|
spring 有三种注入方法:
setter 注入,也就是上面的使用,使用自动生成的 get/set 即可,这种方式是最主要的
构造方法注入,需要提前写好构造方法,bean 会将以参数的形式传递传入,所以需要自己完成(ide也可以自动生成属性在构造里的业务)属性赋值的业务。配置需要更改如下:
1 2 3 4 5 6 7 8
<bean id="userService" class="demo.service.UserService"> <constructor-arg> <!-- 参考已经初始化的 bean --> <ref bean="u"/> <!-- 重新初始化一个 bean --> <!-- <bean class="demo.dao.impl.UserDaoImpl" /> --> </constructor-arg> </bean>
除了 bean 的注入也可以直接使用简单属性值注入,构造函数参数不只一个的时候,可以使用 index 属性指定参数顺序,或者根据 type
1 2
<constructor-arg index="0" value="12" /> <constructor-arg index="1" value="34" />
spring 3.0 之后可以使用命名空间 c 的方式将构造参数简化,但是这种方式不支持集合。
1 2 3 4 5 6 7 8 9 10 11
<!-- 需要加入命名空间 --> ... xmlns:c="http://www.springframework.org/schema/c" ... <!-- 为构造方法中名为 ver 的参数注入名为 BeanName 的 bean --> <bean id="u" c:ver-ref="BeanName" class="demo.dao.impl.UserDaoImpl" > <!-- 为构造方法中的第 0(第一个)个参数注入名为 BeanName 的 bean --> <bean id="u" c:ver:_0-ref="BeanName" class="demo.dao.impl.UserDaoImpl" > <!-- 为构造方法中名为 ver 的参数注入值 123 --> <bean id="u" c:ver="123" class="demo.dao.impl.UserDaoImpl" >
同样的可以使用命名空间 p 来替代简单的 property 标签,语法和 c 命名空间类似。
接口注入,这个使用不多,就不再浪费篇幅。
注入类型除了上面提到的 bean 和简单的值类型,还可以是集合类型:
|
|
bean 生存范围和生命周期
实例化的 bean 在装配时候其生生存范围是非常重要的,实际应用中会使用到的有两种:
- singleton,单例,即只会创建一个,默认值。
- prototype,原型,每次有 ref 时候会创建一个 bean。
在配置里可以使用 scope 属性来指定,如 <bean id="u" class="demo.dao.impl.UserDaoImpl" scope="prototype" />
。
spring 默认情况下 bean 会全部初始化生成,可以在 bean 标签上加上 lazy-init="true"
来实现依赖时候的懒加载。
由于 bean 的声明周期由 spring 来管理,所以可以声明式的将指定其初始化和销毁时候调用的方法,如:
|
|
需要注意的是这只适用于 scope 为 prototype 的情况下,在实验时候需要显式的调用 ctx.destroy();
才能触发销毁。
autowire 自动装配
userService 的 bean 需要注入的 bean 除了通过 property 子标签显式指定,还可以通过 autowire 属性如 <bean id="userService" class="demo.service.UserService" autowire="byType"/>
进行自动装配,自动装配方式如下几种常用的:
- no: 不匹配,但是 bean 必须定义ref元素
- byName: 根据名称,通过 bean 的 name 值来匹配需要装配 bean 下同名的属性名的值
- byType: 根据类型,根据 bean 的类型匹配需要装配 bean 下同名同类型的属性,在有多个该类型的 bean 时候,会冲突
- constructor: 构造函数
也可以写在 beans 里如 <beans autowire="byName">
注解方式
使用注解之前需要修改下 beans.xml
配置文件,添加上 context 的提示,并引入 <context:annotation-config />
这个标签会初始化一些用于处理注解的 bean。
The implicitly registered post-processors include AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, and the aforementioned RequiredAnnotationBeanPostProcessor.
|
|
Autowired
上面的配置就舍弃了在配置里组装 bean,只是声明了 bean,那么在 java 代码里就需要使用注解来完成装配的功能。在 UserService.java 中:
|
|
@Autowired
注解是根据参数类型而非名称来进行装配的,所以可以修饰多个参数的 set 方法,或者构造函数,所以这里的方法名就没有配置方式的那么严谨了。如果出现多个同类型的 bean 如果其中一个指定了和参数类似名一致的 name ,spring 会自动装配该 bean,如果没有指定 name 那么可以使用 @Qualifier
注解指定其需要注入的 bean 名称,使用注解之后其匹配权重最高:类型 < 名称 < Qualifier注解:
|
|
默认的 @Autowired
是 required = true 的,可以使用 @Autowired(required=false)
,这样就不一定需要需要注入。为了规范性,就有 @Required
这个单独的注解,声明式的要求注入。
Resource
Resource 注解是 JSR-250 规范里的,spring 里实现了该注解,该注解基于所修饰的属性、set方法的 名称 来装配,也可以利用 name 参数显式指定名称:
|
|
在 JSR-300 规范里还可以使用 @Inject
和 @Named
,由于 Inject 没有 required 属性,所以需要使用 @Nullable
来指定其可不用注入。
|
|
@javax.inject.Named
和 @javax.annotation.ManagedBean
还可以用于声明 bean,类似于下面将要提到的 @Component
。
Component
除了装配关系可以使用注解完成,bean 的声明也是可以使用注解来完成的,需要在 beans.xml 里添加上扫描的包的路径,以实现 bean 的发现。
|
|
实际上使用 <context:component-scan>
时候是默认能使了 <context:annotation-config />
了的。
The use of
<context:component-scan>
implicitly enables the functionality of<context:annotation-config>
. There is usually no need to include the<context:annotation-config>
element when using<context:component-scan>
.
之后为需要用 spring 来接管的 bean 添加上注解
|
|
除了 @Component
实际上还可以使用 ,@Repository
@Service
@Controller
来注解,3 个注释分别和持久层(Dao)、业务层和控制层(Web 层)相对应,实质上是没有区别的只是语义显得明确些(唯一的区别是这三个的 scope 默认 singleton),这一点这会在 Spring MVC 里体现的淋漓尽致。
注解细则
对应配置方式,注解方式还有下面的些细节:
@Scope("xxx")
注解为该 bean 指定生存范围@PostConstruct
对应 init-method@PreDestroy
对应 destroy-method@Lazy
对应配置式里的懒加载
JavaConfig
在第三方库不可修改的情况下,要想将里面的资源装配到自己的应用上,使用注解的自动装配的入侵代码的方式是不容易实现的,这时候需要使用显式的装配。显式装配有两种可选方案:JavaConfig 和 XML ,XML 配置式前面已经提到,下面介绍 JavaConfig 的方式。
JavaConfig 的方式就是需要自己实现 第三方到自己的转换 的过程,JavaConfig 又称 配置类,实现步骤分为以下几部:
- 创建一个配置类,使用
@Configuration
来注解 - 在该类中,创建多个返回第三方对象的方法,并用
@Bean
注解,最后如不使用注解里的 name 属性显式指定名称,返回的对象会以 ID 名为 方法名 为标识放入spring 的容器池/上下文(context)中,以供装配。
|
|
之后去掉 UserDaoImpl 的 @Component 的注解,在测试用例中发现也是能够完成装配的。
配置类的测试
编写测试类的时候可以不使用 beans.xml 文件而使用 JavaConfig 的方式初始化 Spring context,如下代码:
|
|
要是嫌麻烦的还可以以下面的方式完成 spring 上下文的构建,在测试类里面手动扫描包:
|
|
进一步的,在配置类里使用 @ComponentScan 注解指定需要扫描的包完成 Spring context 的构建,之后只需要指定配置类 AnnotationConfigApplicationContext(UserDaoConfig.class)
即可,不需要书写多个 bean 的类。
|
|
配置的模块化
使用 @Import
注解可以实现配置的模块化
|
|
之后就只需要 AnnotationConfigApplicationContext(ConfigB.class)
即可,该注解可以以 list 的方式导入多个配置类:
|
|
类似前面 XML 为主混入注解方式,这里也可以对偶的注解为主混入 XML,即使用 @ImportResource
注解可以将配置类需要的 xml 方式什么的上下文导入,如:
|
|
如果需要在 xml 里使用 JavaConfig/xml 也非常方便
|
|
纵观前面的示例可以看出 spring 的 xml 配置式、JavaConfig、注解方式可以混搭在一起。
环境抽象
开发过程中经常会遇到生产和开发的环境切换问题,特别是数据库的连接,为了方便在不同环境下激活不同的 beans , spring 中可以使用 @Profile
注解来限定 bean 加入上 spring 下文的条件。
|
|
同时你还可以使用 !
&
|
的语法衔接 profile。除了修饰配置类,还可以直接修饰 Bean 一级别。
|
|
在 xml 中使用的话,需要使用内嵌的 <beans>
标签加上 profile="xxx"
属性来完成,这时候 profile 的与或非的关系就由 <beans>
的树形结构来表示了。
|
|
当前激活 profile 目标由两个独立的属性决定 spring.profiles.active
和 spring.profiles.default
, 如果 active 为空,使用 default 值,而默认情况下 active 是为空。而设定这两个参数的方法有下列多种
- 作为 DispatcherServlet 的初始化参数。
- 作为 web 应用的上下文,即在 servlet 配置中设置
<init-param>
<context-param>
的 active 或者 default 的参数。 - 作为 JNDI 条目(
java:comp/env/entries)
)。 - 作为系统的环境变量。
- 作为 JVM 的系统属性,即
-Dspring.profiles.active="profile1,profile2"
。 - 在集成测试上使用
@ActiveProfile
的配置。
在构建上下文时候设置
|
|
或者给配置类加上配置注解 @PropertySource
|
|
在 src/main/resources/application.properties
文件里
|
|
值得注意的几点
- 没有限定 profile 的资源都是默认加入上下文的。
- 设置 profile 是同时含多个的,如上面的举例
-Dspring.profiles.active="profile1,profile2"
。
spring.profiles.active
配置除了在代码里根据 @Profile
区分加入上下文的资源,还可以将配置分块,根据场景引入不同的配置(连接信息等),参见。
处理外部值
处理外部值最简单方式是声明属性源(使用 @PropertySource
),之后通过注入 spring 注入 env 成员变量,或是是使用占位符 @Value("${}")
|
|
除了属性值之外,@Value
注解还可以注入简单值,这样注解方式也是可以直接注入 value 的,将其应用到构造函数参数上也是可以实现 构造函数参数的简单值注入
|
|
对应 xml 的配置,需要这样
|
|
Environment&Properties
org.springframework.core.env.Environment 接口提供了多种获取和检查属性值的方式,包括提供默认值,提供转换类,检查是否存在
|
|
Properties 文件里是可以衔接系统环境变量的,如有系统环境变量 ENV_MYSQL_HOST=localhost
ENV_MYSQL_PORT=3306
可以在 properties 文件里引用。properties 还支持数组,和 Random,支持以 ${}
的方式引用其他的属性
|
|
spring 默认 properties,properties文档,除了 properties 格式的配置文件,还可以使用 yaml 文件来编写配置。
SpEL 表达式
类似于与占位符的 ${}
形式,SpEL(Spring Expression Language) 为 #{}
,其中为表达式。SpEL 用法较多,见文档,下面简单介绍几种用法
- 引用其他其他 bean 的属性、调用方法,如
#{userDao.version}
#{userDao.getSomething()}
- 字面量,如
#{3.14}
为浮点数类型,#{Hello}
为字符串类型,#{false}
为 Boolean - 表达式中支持运算符,支持包的引入,如
#{T(java.lang.Math).PI * 2}
- 正则表达式
- 计算集合类型,索引,过滤,投影
借助 SpEL 表达式可以方便的利用 @Value
注入 map 数据:
|
|
|
|
配置类细则
可以使用
@Bean(initMethod = "init")
和@Bean(destroyMethod = "cleanup")
指定生命周期的钩子。同样使用
@Scope("prototype")
指定其生存范围。可以指定多个名称
@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
。如果需要其他的 bean 来完成,直接使用方法即可(spring 会拦截该方法,直接注入上下文空间中的 bean):
|
|
同样的,如果需要其他的 bean 来完成,也可以通过参数的类型注入,当有多个时候相同类型的 bean 时候,参数可以使用
@Qualifier
注解来区分类型。除了使用
@Qualifier
来区别 autowire 时候多个同类型对象,还可以对@Component
上附加加上@Primary
注解声明其自动装配时候的权重。
参考
JSR-300 与 spring 的注解对比
Spring | javax.inject.* |
---|---|
@Autowired | @Inject |
@Component | @Named / @ManagedBean |
@Scope(“singleton”) | @Singleton |
@Qualifier | @Qualifier / @Named |
@Value | - |
@Required | - |
@Lazy | - |
ObjectFactory | Provider |
- spring4 overview
- The IoC Container
- 《Spring 实战》