Spring Framework
Spring 简介
Spring 框架即以 interface 21 框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日,发布了1.0版本。创始人是 Rod Johnson
。
Spring 是一个轻量级__控制反转(IoC)__和__面向切面(AOP)__的容器框架。
Spring 框架由 7 部分组成: Spring Core
、 Spring AOP
、 Spring ROM
、 Spring DAO
、 Spring Web
、 Spring Context
、 Spring Web MVC
。
Spring 框架所需要的 Jar 包
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.3</version>
</dependency>
</dependencies>
控制反转(IoC)
IoC 是 Inversion of Control
的缩写,多数书籍翻译成“控制反转”。1996年,Michael Mattson
在一篇有关探讨面向对象框架的文章中,首先提出了 IoC 这个概念。
IoC 理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。
对象获得依赖对象的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
控制反转(IoC)与依赖注入(DI)
2004年,Martin Fowler
探讨了一个问题,既然 IoC 是控制反转,那么到底是“哪些方面的控制被反转了呢?”,经过详细地分析和论证后,他得出了答案:“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由 IoC 容器主动注入。于是,他给“控制反转”取了一个更合适的名字叫做“依赖注入(Dependency Injection)”。他的这个答案,实际上给出了实现 IoC 的方法:注入。所谓依赖注入,就是由 IoC 容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IoC)是从不同的角度描述的同一件事情,就是指通过引入 IoC 容器,利用依赖关系注入的方式,实现对象之间的解耦。
IoC 中最基本的技术就是“反射(Reflection)”编程。
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
注:
- 这个
id
属性是标识单个 bean 定义的字符串。 - 这个
class
属性定义 bean 的类型,需要使用完全限定的类名。
依赖注入(DI)的方式
基于构造函数的依赖注入
package cn.worstone.bean;
public class User {
private String name;
private int age;
public User(){
super();
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
-
构造函数参数解析
<bean id="user" class="cn.worstone.bean.User" name="userBean"> <constructor-arg type="int" value="26"></constructor-arg> <constructor-arg type="java.lang.String" value="XXX"></constructor-arg> </bean>
-
构造函数参数类型匹配
<bean id="user" class="cn.worstone.bean.User" name="userBean"> <constructor-arg index="1" value="26"></constructor-arg> <constructor-arg index="0" value="XXX"></constructor-arg> </bean>
基于 Setter 的依赖注入
package cn.worstone.bean;
import java.util.*;
public class User {
private String name;
private int age;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> cards;
private Set<String> games;
private Properties properties;
public User(){
super();
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public void setCards(Map<String, String> cards) {
this.cards = cards;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
package cn.worstone.bean;
public class Address {
private String address;
public void setAddress(String address) {
this.address = address;
}
}
<bean id="user" class="cn.worstone.bean.User" name="userBean">
<property name="age" value="26"></property>
<property name="name" value="孙传振"></property>
<property name="address" ref="address"></property>
<property name="books">
<array>
<value>《红楼梦》</value>
<value>《水浒传》</value>
<value>《西游记》</value>
<value>《三国演义》</value>
</array>
</property>
<property name="hobbies">
<list>
<value>绘画</value>
<value>编程</value>
<value>音乐</value>
<value>跑步</value>
</list>
</property>
<property name="cards">
<map>
<entry key="身份证" value="210381199502280000"></entry>
<entry key="银行卡" value="690000000000000000"></entry>
</map>
</property>
<property name="games">
<set>
<value>王者荣耀</value>
<value>英雄联盟</value>
<value>刺客信条</value>
</set>
</property>
<property name="properties">
<props>
<prop key="username">first</prop>
<prop key="password">19326</prop>
</props>
</property>
</bean>
<bean id="address" class="cn.worstone.bean.Address">
<property name="address" value="辽宁省海城市"></property>
</bean>
XML 快捷方式
-
具有 p-namespace 的 XML 快捷方式
使用 p-namespace,您可以使用
bean
元素的属性(而不是嵌套<property/>
元素)来协作描述 bean 的属性值。
需要添加 XML 文件约束xmlns:p="http://www.springframework.org/schema/p"
<bean id="user" class="cn.worstone.bean.User" name="userBean" p:age="26" p:address-ref="address"> <property name="name" value="XXX"></property> </bean>
-
具有 c-namespace 的 XML 快捷方式
使用 c-namespace,您可以使用
bean
元素的属性(而不是嵌套<constructor-arg/>
元素)来协作描述 bean 的属性值。
需要添加 XML 文件约束xmlns:c="http://www.springframework.org/schema/c"
<bean id="user" class="cn.worstone.bean.User" name="userBean" c:age="26" c:name="XXX" c:address-ref="address"> <constructor-arg name="age" value="26"></constructor-arg> <constructor-arg name="name" value="XXX"></constructor-arg> <constructor-arg name="address" ref="address"></constructor-arg> </bean>
<bean id="user" class="cn.worstone.bean.User" name="userBean" c:_0="XXX" c:_1="26" c:_2-ref="address"> <constructor-arg index="0" value="XXX"></constructor-arg> <constructor-arg index="1" value="26"></constructor-arg> <constructor-arg index="2" ref="address"></constructor-arg> </bean>
在 Spring 中使用 ApplicationContext 去获取 bean 的时候,在加载 xml 配置文件的时候,会创建配置文件中所有的 bean。
Bean Scopes
scope | description |
---|---|
singleton | 将单个 bean 定义范围限定为单个对象实例。(默认值) |
prototype | 将单个 bean 定义的作用域限定为任意数量的对象实例。 |
request | 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义的后面创建的 bean 实例.仅在支持 web 的 Spring ApplicationContext 上下文中有效。 |
session | 将单个 bean 定义的范围限定为 HTTP Session 的生命周期。仅在支持 web 的 Spring ApplicationContext 上下文中有效。 |
application | 将单个 bean 定义的作用域限定为 ServletContext 的生命周期。仅在支持 web 的 Spring ApplicationContext 上下文中有效。 |
websocket | 将单个 bean 定义的作用域限定为 WebSocket 的生命周期。仅在支持 web 的 Spring ApplicationContext 上下文中有效。 |
Bean 生命周期回调
从Spring 2.5开始,可以使用三个方式来控制 bean 生命周期行为:
-
实现
InitializingBean
和DisposableBean
回调接口。 -
自定义
init()
和destroy()
方法。 -
通过
@PostConstruct
和@PreDestroy
注解。
通常,@PostConstruct
和 @PreDestroy
注解被认为是在现代 Spring 应用程序中接收生命周期回调的最佳方式。使用这些注解意味着 bean 没有耦合到特定的 Spring 接口。如果不希望使用注解,但仍然要删除耦合,可以考虑使用 <bean/>
标签的 init-method
和 destroy-method
属性自定义 init()
和 destroy()
方法。
为同一个 bean 初始化方法的调用顺序为:
- 用注解
@PostConstruct
的方法 - 重写由
InitializingBean
回调接口定义的afterPropertiesSet()
方法 - 自定义的
init()
方法
销毁方法的调用顺序相同:
- 用注解
@PreDestroy
的方法 - 重写由
DisposableBean
回调接口定义的destroy()
方法 - 自定义的
destroy()
方法
初始化回调
- 自定义的
init()
方法
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
- 实现
InitializingBean
回调接口(该方法不推荐,会让 bean 耦合到 Spring 的接口中)
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
销毁回调
- 自定义的
destroy()
方法
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
- 实现
DisposableBean
回调接口(该方法不推荐,会让 bean 耦合到 Spring 的接口中)
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
默认初始化方法和销毁方法
如果生命周期回调方法的名称在整个项目中标准化,所有开发人员都使用相同的方法名称并确保一致性。则可以将 Spring 容器配置为查找在每个 bean 初始化以及销毁时的回调方法名称。
public class DefaultUserService implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.userDao == null) {
throw new IllegalStateException("The [userDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="userService" class="com.something.DefaultUserService">
<property name="userDao" ref="userDao" />
</bean>
</beans>
<beans/>
标签的 default-init-method
属性使 Spring IoC 容器将 bean 中的init()
方法识别为 bean 初始化回调的方法。在创建和组装 bean 时,如果 bean 有 init()
方法,则会在适当的时间调用它。
也可以使用 <beans/>
标签的 default-destroy-method
属性,以类似的方式(在XML中)配置 destroy()
方法。
自动装配
Spring容器可以自动装配协作bean之间的关系。您可以通过检查的内容,让Spring为您的bean自动解决协作者(其他bean)ApplicationContext
。自动装配具有以下优点:
- 自动装配可以大大减少指定属性或构造函数参数的需要。
- 随着对象的发展,自动装配可以更新配置。例如,如果您需要向类中添加依赖项,则无需修改配置即可自动满足该依赖项。因此,自动装配在开发过程中特别有用,而不必担心在代码库变得更稳定时切换到显式接线的选择。
使用基于XML的配置元数据时,您可以使用元素的autowire
属性为 bean定义指定自动装配模式<bean/>
。自动装配功能具有四种模式。您可以为每个bean指定自动装配,因此可以选择要自动装配的装配。下表描述了四种自动装配模式:
模式 | 解释 |
---|---|
no | (默认)无自动装配。Bean引用必须由ref 元素定义。对于大型部署,不建议更改默认设置,因为明确指定协作者可提供更大的控制力和清晰度。在某种程度上,它记录了系统的结构。 |
byName | 按属性名称自动布线。Spring寻找与需要自动装配的属性同名的bean。例如,如果一个bean定义被设置为按名称自动装配,并且包含一个master 属性(即它具有一个 setMaster(..) 方法),那么Spring将查找一个名为的bean定义,master 并使用它来设置该属性。 |
byType | 如果容器中恰好存在一个该属性类型的bean,则使该属性自动装配。如果存在多个错误,则将引发致命异常,这表明您可能无法byType 对该bean使用自动装配。如果没有匹配的bean,则什么都不会发生(未设置该属性)。 |
constructor | 类似于byType 但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个bean,则将引发致命错误。 |
使用byType
或constructor
自动装配模式,您可以装配阵列和键入的集合。在这种情况下,将提供容器中与期望类型匹配的所有自动装配候选,以满足相关性。Map
如果期望的密钥类型为,则可以自动连接强类型实例String
。自动装配Map
实例的值包括与期望类型匹配的所有Bean实例,并且 Map
实例的键包含相应的Bean名称。
自动接线的局限性和缺点
当在项目中一致使用自动装配时,自动装配效果最佳。如果通常不使用自动装配,则可能使开发人员仅使用自动装配来连接一个或两个bean定义感到困惑。
考虑自动装配的局限性和缺点:
- 显式依赖项
property
和constructor-arg
设置始终会覆盖自动装配。您不能自动装配简单的属性,例如基元Strings
,和Classes
(以及此类简单属性的数组)。此限制是设计使然。 - 自动装配不如显式接线精确。尽管如前所述,Spring还是谨慎地避免在可能导致意外结果的含糊不清的情况下进行猜测。Spring管理的对象之间的关系不再明确记录。
- 接线信息可能不适用于可能会从Spring容器生成文档的工具。
- 容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或
Map
实例,这不一定是问题。但是,对于需要单个值的依赖项,不会任意解决此歧义。如果没有唯一的bean定义可用,则会引发异常。
在后一种情况下,您有几种选择:
- 放弃自动布线,转而使用明确的布线。
- 通过将其
autowire-candidate
属性设置为来避免自动装配bean定义。false
- 通过将
primary
其<bean/>
元素的属性设置为,将单个Bean定义指定为主要候选者true
。 - 通过基于注释的配置实现更细粒度的控件。
基于注释的容器配置
在配置Spring时,注释是否比XML更好?
基于注释的配置的引入提出了一个问题,即这种方法是否比XML“更好”。简短的答案是“取决于情况”。长的答案是每种方法都有其优缺点,通常,由开发人员决定哪种策略更适合他们。由于定义方式的不同,注释在声明中提供了很多上下文,从而使配置更短,更简洁。但是,XML擅长连接组件而不接触其源代码或重新编译它们。一些开发人员更喜欢将布线放置在靠近源的位置,而另一些开发人员则认为带注释的类不再是POJO,而且,配置变得分散并且难以控制。
无论选择如何,Spring都可以容纳这两种样式,甚至可以将它们混合在一起。值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注释,而无需接触目标组件的源代码