马上就要走上工作岗位了,之后的技术学习就要更有目的性了。
趁现在还有时间,打算从Spring入手,培养一下自己学习新技术的能力。
本文内容将主要从Spring的官网文档出发,从最权威的资料中学习Spring的相关知识。
从Spring 5.3.14 版本出发,系统学习了解Spring。
Spring 简介
Spring 功能
Spring 为企业环境下使用Java语言提供了解决方案,可以支持Groovy和Kotlin,可以方便地为应用程序构建出许多框架。
官网中的英文介绍表述地十分清楚。
Spring makes it easy to create Java enterprise applications. It provides everything you need to embrace the Java language in an enterprise environment, with support for Groovy and Kotlin as alternative languages on the JVM, and with the flexibility to create many kinds of architectures depending on an application’s needs.
Spring 由来
Spring是为了解决Java 平台企业版(J2EE)规范错综复杂的问题,在2003年提出的。它本质上是对Java EE的一个辅助补充,而不是与Java EE竞争的关系,Spring编程模型不包含Java EE 规范,而是集成了从Java EE规范下精心挑选过的一些规范,包括:
- Servlet API (JSR 340)
- WebSocket API (JSR 356)
- Concurrency Utilities (JSR 236)
- JSON Binding API (JSR 367)
- Bean Validation (JSR 303)
- JPA (JSR 338)
- JMS (JSR 914)
- as well as JTA/JCA setups for transaction coordination, if necessary.
JSR: Java Specification Requests
暂时看不懂这些规范,就先不管了
Spring 设计哲学
设计哲学包含如下五条:
- 在每个层级都允许编程人员近可能晚地决定具体实现方式。例如开发人员可以通过配置文件便捷地替换某些持久化组件,而不需要改动现有的代码。
- 包容不同的观点。对任何事情都接纳多种解决方案,而不进行过多的限制。
- 保持强大的向后兼容性。强制在版本之间进行很少的重大更改。
- 重视API的设计。设计好的API在之后很长的时间不会轻易改动。
- 重视代码质量。强调有意义的、最新和准确的javadoc。是极少数可以声称代码结构清晰,包之间没有循环依赖关系的项目之一。
Spring 核心技术
Spring技术中最核心的是Inversion of Control(IoC)container 和 Aspect-Oriented Programming(AOP)。
IoC Container & Bean
IoC Container
IoC技术又被称之为依赖注入(Dependency Injection),任何对象所需要的依赖都是在构造函数参数、工厂方法参数和构建后给对象设置属性的过程中声明的。
出来乍到,看上面这段说明一脸懵逼,阅读了廖雪峰关于这个概念的解释,才略有理解。
简单的理解,原本有一个书店的类,它会自己创建一个数据库连接
1
2
3
4
5
6
7
8
9
10
11 public class BookService {
private HikariConfig config = new HikariConfig();
private DataSource dataSource = new HikariDataSource(config);
public Book getBook(long bookId) {
try (Connection conn = dataSource.getConnection()) {
...
return book;
}
}
}问题来了,其他类,例如客户类、购物车、历史订单类也都会包含这个数据库连接,那么谁来负责对数据库连接类进行管理呢?
问题的本质:在原始程序中,控制权在程序本身,程序的控制流程完全由开发者控制。
而在Spring中控制权从应用程序转移到了IoC容器,所有组件不再由应用程序自己创建和配置,而是由IoC容器负责。所以上述BookService类中的数据库将会变更成由参数传入的方式或者属性设置的方式。
1
2
3
4
5
6
7 public class BookService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}而这个类实例化过程中的依赖,可以通过配置文件的方式进行声明
1
2
3
4
5
6
7
8
9
10 <beans>
<bean id="dataSource" class="HikariDataSource" />
<bean id="bookService" class="BookService">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 另外一个用户类,也依赖相同的数据库连接 -->
<bean id="userService" class="UserService">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
IoC技术实现的关键在org.springframework.beans
和 org.springframework.context
两个包中,其中BeanFactory接口提供了能够管理任何类型对象的高级配置机制,ApplicationContext 是 BeanFactory 的子接口,添加了如下的新功能:
- Easier integration with Spring’s AOP features
- Message resource handling (for use in internationalization)
- Event publication
- Application-layer specific contexts such as the
WebApplicationContext
for use in web applications.
基本理解不了添加的内容。就先把英文内容抄过来了
在Spring中,构成应用程序主体并由IoC容器管理的对象称之为Bean,Bean之间的相互依赖关系由开发者通过配置元信息给出。
Contanier 简介
Spring Ioc Container 对应着org.springframework.context.ApplicationContext
,根据配置信息负责Bean的实例化、配置和组装。配置信息可以在XML、Java 装饰器和Java代码中。示意图如下图所示,
配置元信息
XML
1 |
|
id属性是一个字符串类型,标识了一个bean
class属性使用类的全名标识bean的类型
实例化 Container
Java 入口代码
1 | ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml"); |
services.xml描述了service layer object,如下:
1 |
|
daos.xml描述了data access layer object,如下:
1 |
|
property name 代表Bean的一个属性名字,而 ref代表了该属性对应的值,也就是另一个bean。通过id和ref对体现了beans之间的相互依赖关系。
使用 Container
1 | // create and configure beans |
可以通过使用getBean来获取一个Bean对象,但在应用程序中,应该从不使用该方法
没有看出来 using the Container这部分,到底说了个啥
Bean 简介
Bean 包含如下元信息:
- 对应Java类的名字,描述了Bean的类型
- 行为配置项,描述了Bean在Container中行为,例如使用范围、生命周期、回调等
- 对其他Bean的引用,这些引用关系可以称之为合作者或者依赖
- 其他的一些配置设置
BeanFactory不仅支持通过配置元信息的方式创建Bean,也支持通过DefaultListableBeanFactory的registerSingleton()和registerBeanDefinition()的方法来手动传入一个由开发人员手动创建的Bean。但这不是主流的做法。
Bean 命名
Bean通过id和name两个属性来设置,通常只会设置一个id属性,如果需要更多的名字,可以使用name来添加别名,在name值中通过使用逗号、分号和空格的方式可以添加多个别名。那么为什么需要别名呢?例如下面的场景:
subsystemA
需要一个数据库对象,subsystemB
也需要一个数据库对象,他们暂时用到的数据库对象是同一个对象,但为了考虑之后的扩展性,他们可能分别使用不同的数据库对象。为之后尽可能少地修改代码,别名的作用就发挥出来了。
1
2
3
4 <!-- 通过给myApp-dataSource对象起别名的方式,让其他bean虽然引用不同的名字,但暂时还是用到同一个Bean的作用 -->
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<!-- 之后要换用其他的Bean,仅需要修改下面一行代码就行了 -->
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
Bean 初始化
该部分讲解了如何使用配置文件实现在构造函数、静态工厂构造Bean的方式, 具体内容可参考
该部分有个需要注意的地方,由于Spring允许开发人员使用工厂来构造Bean,因此Bean的runtime type是需要额外重要的,Spring可以通过BeanFactory.getType
函数来获取某个Bean的实际类型。
依赖注入
这部分具体介绍了依赖注入实现的形式:基于构造函数依赖注入(Constructor-based Dependency)、基于属性赋值的依赖注入(Setter-based Dependency)
具体使用方式就不在这里赘述了,这里主要关心两种依赖注入方式的差别
构造函数依赖注入一般用于必须的依赖
基于属性赋值的依赖注入一般用于可选的依赖
如果确实需要循环依赖的场景,可以通过属性赋值依赖注入的方式实现
这里开发人员可以相信Spring总是会做正确的事情,包括:
- 在Container加载的过程中可以检测到引用不存在、循环引用的问题
- Spring创建Bean时,总是尽可能晚地去设置属性和解析依赖
- Spring允许用户设置Bean为预实例化单例模式,以尽快创建该Bean,而不是等到它被需要的时候
- 如果没有循环依赖的问题,那么被依赖的Bean在完全创建完成之后,才会创建要去依赖的Bean
相关例子可参考
详细用法可参考依赖和配置文件的细节
此部分还有depends-on、Lazy-initialized、autowiring、Method Injection等知识。看不下去了,继续向下了。
Bean 作用域
Bean 作用域(Bean Scope)描述了Bean起作用的范围,Spring 框架支持6种作用域,其中四种在ApplicationContext中才有,Spring还支持开发人员自定义作用域。
- singleton:默认作用域,在每个Spring IoC Container中,一份定义有且仅有一个一个实例
- prototype:一份Bean定义可以对应任意数量的实例
- request:由ApplicationContext支持,每个HTTP请求周期中会有一个Bean实例
- session:由ApplicationContext支持,每个HTTP Session周期中会有一个Bean实例
- application:由ApplicationContext支持,每个ServletContext周期中会有一个Bean实例
- websocket:由ApplicationContext支持,每个WebSocket周期中会有一个Bean实例
此外Spring也支持thread 作用域,自定义作用域可参考.
作用域可以通过scope关键字来指定
1 | <bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/> |
Singleton 作用域
默认作用域,每个Spring IoC Container中全局唯一,示意图如下。
这里最关心的就是Spring的 Singleton Scope和设计模式中的单例模式之间的区别:
Spring Singleton Scope 是由Spring来保证每个Container中仅有一个Bean;而设计模式中的单例模式是通过代码级别的限制,使得只能创建唯一的一个Bean。他们之间存在很大的不同
Prototype 作用域
每个Bean在被引用的时候,都会重新生成一个实例,示意图如下。
Prototype 作用域和其他作用域之间的区别是:
Prototype并不管理Bean的完整生命周期,Spring Contianer只负责Bean的实例化(instantiate)、配置(configure)和组装(assemble),之后的管理工作需要由开发人员来管理
Singleton Bean依赖Prototype Bean的时候,Spring IoC Container只负责在Singleton Bean创建的时候创建一个Prototype Bean,如果Singleton Bean需要重复的获取到Prototype Bean,可以参考Method Injection。
ApplicationContext 相关作用域
需要进行一些app的初始化Web相关的配置
Scoped Bean 作为依赖时
短生命周期的Bean需要被更长生命周期的Bean依赖时,应该选择去依赖一个AOP代理(AOP proxying)而不是一个短生命周期的Bean。AOP代理是一个暴露出和相关Bean相同接口的一个对象,它可以检索到目标作用域范围内的当前存活的Bean,并代理访问当前存活Bean的同名方法。
代理的类型有多种,例如基于CGLIB的代理或者基于标准的JDK接口的代理
XML配置如下,通过关键代码<aop:scoped-proxy/>
来让Spring创建一个代理。
1 | <!-- an HTTP Session-scoped bean exposed as a proxy --> |
Q:生命周期内有多个Bean的时候?
Request 生命周期的Bean,肯定需要并发存在,这个时候如何处理?
自定义作用域
可以通过实现org.springframework.beans.factory.config.Scope
接口来自定义作用域,该接口包含四个必须实现的方法
Object get(String name, ObjectFactory<?> objectFactory)
返回一个Scope的BeanObject remove(String name)
移除一个Scope的Bean,并将对象返回void registerDestructionCallback(String name, Runnable destructionCallback)
范围内指定对象被销毁时,应该调用的回调String getConversationId()
获取对话标识???
基本看不懂,需要深入源码的时候再了解吧
实现好自定义的Scope之后,可以通过代码或者XML配置的方式来注册自定义的Scope
自定义Bean的性质
英文原标题是:Customizing the Nature of a Bean
Bean包含如下性质:
- 生命周期相关的回调(LifeCycle Callbacks)
- ApplicationContetextAware and BeanNameAware
- Other Aware Interfaces
Lifecycle Callbacks
和Bean声明周期相关的接口有如下:
Initialization Callbacks
在Container设置后好Bean所有必要的属性之后执行的方法,该方法可以在XML中通过init-method
关键字指定或者通过Bean实现InitializingBean接口实现
Destruction Callbacks
在Container准备销毁