Spring & Spring Boot

背景

想把Spring(这篇文章的Spring比较狭义,其实主要说的是Spring IOC)看透一些,所以读了一下Spring & Spring Boot的源码。读的过程中记了一些认为比较重要的点和一些想法,但是太过发散和零碎,所以打算整理一下。

整篇文章的主题或者说想要解决的问题如下:

  • Spring是为了解决什么问题而产生的?采用了什么思想?
  • Spring为了解决问题是怎么做的?核心概念是什么?设计思想是什么样的?
  • Spring Boot是怎么更进一步简洁化的?
  • Spring的思想及其实现能给我们带来什么意义?

这篇文章类似总纲形式的,会外接一些其他的文章。

Spring带来的新局面

我们要盖一间房子,一个房子有基本的门、窗、各种家具(床、椅子等)。假如我们是原始人,只能就地取材,随便砍一棵树就拿来加工做成门,窗,家具等。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @author jinhy
* @Date 2018-05-15
*/
public class Hourse {
/**
* 就地取材的墙
*/
private BasicWall basicWall;
/**
* 就地取材的门
*/
private BasicDoor basicDoor;
/**
* 就地取材的窗
*/
private BasicWindow basicWindow;
public Hourse(BasicDoor basicDoor, BasicWindow basicWindow, BasicWall basicWall) {
this.basicDoor = basicDoor;
this.basicWindow = basicWindow;
this.basicWall = basicWall;
}
}

作为原始人的我们用身边的素材搭建了我们的房子。恰好还特别合适住的非常舒服。但是有一天刮了一阵大风,吹坏了我们的门。等风过去我们赶紧修吧,发现根本找不到合适的门了,我们要换门得顺便调整一下我们的墙和窗户。可真是费死劲了。

大家苦思冥想,突然有个人说他是从未来穿越过来的,他说未来的房子门坏了随便去个地方重新买一个就好了,因为所有制造的门都是符合一个标准的,都能装到我们的房子上。大家一想靠谱呀。于是一起协商做了一个标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Hourse {
/**
* 就地取材的墙
*/
private IWall wall;
/**
* 就地取材的门
*/
private IDoor door;
/**
* 就地取材的窗
*/
private IWindow window;
public Hourse(IDoor door, IWindow window, IWall wall) {
this.door = door;
this.window = window;
this.wall = wall;
}
}

这样我们就达成了门坏了随便换,不管木头的石头的还是啥样的,只要是符合标准的随便装。

其实软件开发历史的前期也遇到了这样的问题,稍微一个需求的变化就对软件的整体产生很大的印象,一个小的软件是很容易修改的,但是如果我们想要构建一个庞大的系统,那这样的一个变化可能将使我们大厦倾倒。

所以我们得到的经验就是我们不应该依赖具体的实现,而是依赖它的抽象(标准)。这条原则叫做依赖倒置。概念如下:

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

这样解决了我很大的问题,但是还是有问题——我们发现不管如何,我们最底层还是需要依赖具体的实现,标准或者说是抽象是不可能替代具体实现。就像我们上面说的盖房子,我们不可能拿一个门的图纸来替代门呀。这样也就会导致我们在某些地方还是需要去创建对象,new Object()。那这又依赖了具体的实现了呀,哎~

TODO:还需要补充代码!这样更能体现一下。

又经过一番苦思冥想,我们发现无论如何都无法把生成具体实现的过程剔除,但是我们可不可以把这个过程从我们的业务流程中拿出去呢?我们定义的流程中依赖的还是抽象,但是我们真正的运行起业务流程的时候拿到的而是具体的实现。具体实现从哪拿?从一个跟业务不想关的仓库中拿。

这就是Spring IOC所给我们提供的东西。

Spring是如何做到的

通过上面的章节,我们理解了Spring的出现是为了解决我们软件开发中的什么问题。

其实怎么解决的我们基本上也已经理解了,就是以仓库为核心当我们的业务代码需要某个具体实现的时候,我们把这个实现提供给程序,这个过程在Spring里叫做依赖注入

下面我们来具体看一下Spring是怎么设计并实现的。(开始我是把所有相关重点流程的代码都贴在了文章里面,然后突然感觉这种方式是不是不对,大家谁没有源码看啊。换一种方式,然后大家可以根据兴趣自己去查看源码。

以流程的方式。而且不会特别在意代码层次,主要是为了表现我们可能关注的一些点。

主流程

这是我们整个业务流程,程序开始前创建了应用上下文,然后我们能从中获取我们的实例。

创建应用上下文对应的代码类似:

1
new ClassPathXMLApplicationContext("applicationContext.xml");
1
2
3
4
5
6
7
8
st=>start: Start
e=>end: End
new application=>operation: 创建应用上下文
get singleton=>operation: 获取实例
create beanfactory=>subroutine: 创建bean工厂
load beandefinitions=>operation: 加载Bean定义
st->new application->get singleton->e

概念解释:

  • 什么是应用上下文?
  • ApplicationContext和BeanFactory比有什么区别?

创建应用上下文

1
2
3
4
5
6
7
8
9
10
11
12
st=>start: Start
e=>end: End
create beanfactory=>operation: 创建bean工厂
load beandefinitions=>operation: 加载Bean定义
prepare beanfactory=>operation: 给bean工厂设置一些东西(如类加载器、Bean的后置处理器等)
invoke beanfactorypostprocessess=>operation: 调用bean工厂的后置处理器
register beanpostprocessors=>operation: 注册Bean的后置处理器
onrefresh=>operation: 初始化其他特殊bean
finishbeanfactoryinitialization=>operation: 实例化剩余单例
st->create beanfactory->load beandefinitions->prepare beanfactory->invoke beanfactorypostprocessess->register beanpostprocessors->onrefresh->finishbeanfactoryinitialization->e

概念解释:

  • 我们可以从哪里获取Bean定义(Bean Denifition)?
  • 我们用什么处理Bean定义?
  • Spring中的后置处理器是什么?
  • 实例化Bean的时机?

Spring Boot

Spring Boot要解决的问题

我们都知道基于Spring的应用总是需要配置大量的东西。

比如当我们使用Spring MVC的时候,我们需要配置组件扫描,配置dispatcher servlet,配置一个view resolver和一些相关的jar等等。当我们使用Hibernete/JPA的时候,我们需要配置一个数据源,entity manager factory(实体管理工厂),事物管理等。这些都是繁琐的。

而Spring Boot就是帮我们解决这些东西的。让我们开发项目的时候花更少的时间在这些配置上。

如何做到自动配置

所有的自动配置逻辑都是在spring-boot-autoconfigure.jar中实现。如图

!autoconfigure.png

这个jar包中嗨哟一个比较关键的文件是/META-INF/spring.factories,这个文件列出了在EnableAutoConfiguration键下启用的所有自动配置类。一些比较常见的自动配置类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\

通常,所有的自动配置类会查看在类路径中的其他类。如果类路径中有特定的类,则通过自动配置为其配置功能。这时通常会使用@ConditionalOnClassConditionalOnMissingBean等来帮助达成这样的效果。

所以比如发现classpath里面有hibernate的jar包且没有相关的bean出现,它就帮你配置一个数据源或者发现了Spring MVC的jar包就帮你配置好Dispatcher Sevlet,这是多么美妙的事情。

当然我们可能想要看看我们符合了哪些条件,框架为我们做了哪些自动配置,我们可以使用两种方式实现:

  • 打开Debug模式

    添加或者修改配置logging.level.org.springframework: DEBUG

  • 使用Spring Boot Actuator

    引入相应的jar包然后浏览器访问查看。

解决复杂的依赖关系

我们之前的Spring项目除了需要配许多配置,还要整理我们复杂的依赖,考虑版本及各种兼容性。

而现在Spring Boot都帮尽可能的减少这方面的工作量,这里它是如何做到的呢?

我们谈一谈Starters。

Starters是一系列的便捷的依赖的描述,它提供一站式的服务,使用为了实现某种目的的Starter,它会帮你引入相应的jar包,并可以搭配auto-configuration为你减少很大的工作量。

比如我们最基础的Spring Boot Starter Web。依赖这个Starter就会帮你完成对Spring、Web MVC、Jackson、Tomcat、Logback等必要的引入和配置。

其他的最长使用的还有spring-boot-starter-test用来单元测试和集成测试,还有spring-boot-starter-jdbc提供传统的JDBC服务等。

其实到这很明显可以知道自动配置和starters不是强依赖的。Startes就是方便我们引入一些依赖。而我们完全可以自由的引入,然后一些Bean的定义和properties会由Auto-Configuration提供。

jinhy wechat
微信扫一扫,欢迎关注我的订阅号~
坚持原创,您的支持将鼓励我继续创作,去追寻星空~