Spring

Spring

Spring 入门

前言

现阶段我们属于刚刚学完 JavaSE ,或者 JavaWeb 的基础知识,刚学会使用 Servlet 来开发简单的小 Web 应用。学完了基础,自然要开始接触框架了。作为入门的框架,首先推荐先学习 Spring ,原因大致如下:

  • 几乎当下所有的企业级 JavaEE 开发都离不开 Spring框架;
  • Spring框架不局限于某一个部分 / 模块的技术,对于表现层、业务层、持久层等都有提供解决方案;
  • Spring框架最强大的地方在于与其他技术的整合
  • SpringFramework 是后续 SpringBoot 、乃至微服务 SpringCloud 的最最基础,早早地打下基础,可以更好地为以后更高阶的技术学习铺路
  • SpringFramework 被很多面试官拿来作为经典面试考题,且难度有逐年上升的趋势

参考书籍推荐:从 0 开始深入学习 Spring

Spring 概述

Spring框架是什么?

Spring 是一个开源的、松耦合的分层的可配置的一站式企业级Java开发框架。它的核心功能是IOC和AOP,它使开发人员更容易的构建出企业级Java应用,并且它可以根据应用开发的组件需要,整合对应的技术

  • 松耦合 :基于IOC和AOP的特性,去除了Java类之间的依赖关系。
  • 分层:提供了表现层、业务层、持久层等领域的解决方案。
  • 可配置
  • 企业级:Spring框架不仅能构建JaveEE Web项目,也可以用在普通的JavaSE和GUI项目上。
  • 第三方整合:Spring框架可以很方便的整合进其他的第三方技术,如持久层框架 MyBatis / Hibernate 等。

为什么使用Spring框架?

优点 描述
非侵入式设计 基于Spring开发的应用中的对象无需继承框架提供的任何类,这样我们再更换框架时,之前写过的代码几乎可以继续使用。
方便解耦,简化开发 通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交给Spring框架管理,避免硬编码所造成的过渡耦合。
AOP编程的支持 面向切面的编程能帮助开发人员实现无耦合的日志记录,性能统计,安全控制。
声明式事务的支持 在Spring中,开发人员可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发的效率。
方便集成其他框架 在 IOC 和 AOP 的基础上可以整合其他优秀的框架,如持久层框架 MyBatis / Hibernate 等。(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

Spring 发展历史

聊到 SpringFramework 的发展历史,这里面的故事蛮有意思的,给小伙伴们讲讲那当年的故事。小伙伴大概知道有这么回事就行,没必要记住具体的内容,咱讲这部分也就图一乐。

image-20221223003149292

EJB思想的提出

这个事得说回上个世纪的 1997 年,IBM 公司咱都知道,老大哥了是吧。人家那里头的大佬多啊,面对当时的 J2EE 开发,为了整一套标准的 Java 扩展开发,IBM 的大佬们费心研究,提出了一个技术思想:EJB ( Enterprise JavaBean ) ,并且还扬言说,做企业级开发就得按照我说的这么来!按照我这样做是标准的、规范的!

EJB的诞生与程序猿的痛苦

提出 EJB 来之后,这个思想被 Sun (那个时候 Java 还是 Sun 的)看到了,呦呵你这个东西好啊,那我是 Java 他爹啊,你这思想我能给你整合进来,壮大咱 Java 的规模和势力啊。于是在 1998 年,Java 中就有了 EJB 的标准规范,它跟当时 J2EE 的其它技术一起联合(包括 JMS 消息、JNDI 命名目录接口、JSP 服务端页面技术等等),称之为 J2EE 开发的核心技术。随后,IBM 召集的这群大佬就把 EJB 的实现给造出来了,而且在 2002 年 EJB 出了 2.0 版,那个时候基本上 EJB 已经可以横行 J2EE开发了,大家都拿 EJB 当做企业级开发的标准。

不过 EJB 虽然很牛,但学起来实在是太麻烦了,而且它本身是个重量级的技术,与应用业务的代码侵入度实在是有点高,所以搞得大家用 EJB 的时候都好痛苦。但话又说回来,人家 IBM 那么多大佬提出来、实现好的技术,你一句难学、不好用就行了?那是不是你本身太笨了才搞得你学不会呢?

img

也由于这个原因吧,当时的 J2EE 开发者们都是一边嘟囔着难用难学,但又不好说出来,只能含泪使用。

Spring框架的诞生

既然大家都用,难免会有一些铁头娃,人家就是觉得,你不好用还赖得着我脑子笨?你不好用大家还都就变成猪头了? 于是,一个伟大的神仙级人物要登场了。

时间到了 2002 年,有一个人叫 Rod Johnson ,他写了一本书:《Expert One-on-One J2EE design and development》 ,里面对当时现有的 J2EE 应用的架构和框架存在的臃肿、低效等问题提出了质疑,并且积极寻找和探索解决方案。大概的意思就是说,“我觉得 J2EE 开发挺好的,就是特喵的有些迷惑的设计实在是,徒增成本,方向错了”。

过了 2 年,2004 年 SpringFramework 1.0.0 横空出世,随后 Rod Johnson 又写了一本书,当时在 J2EE 开发界引起了巨大轰动,它就是著名的 《Expert one-on-one J2EE Development without EJB》,这本书中直接告诉开发者完全可以不使用 EJB 开发 J2EE 应用,而是可以换用一种更轻量级、更简单的框架来代替,那就是 Spring框架

后来开发界的程序猿们用了 SpringFramework 感觉确实比 EJB 好,而且 SpringFramework 提供的一些特性也比 EJB 好,于是大家就慢慢转投 SpringFramework 了。

Spring框架的迭代

下面咱列出一个 SpringFramework 的重要版本更新时间及重大特性:

版本 对应jdk版本 重要特性
SpringFramework 1.x jdk 1.3 基于 xml 的配置
SpringFramework 2.x jdk 1.4 改良 xml 文件、初步支持注解式配置
SpringFramework 3.x Java 5 注解式配置、JavaConfig 编程式配置、Environment 抽象
SpringFramework 4.x Java 6 SpringBoot 1.x、核心容器增强、条件装配、WebMVC 基于 Servlet3.0
SpringFramework 5.x Java 8 SpringBoot 2.x、响应式编程、SpringWebFlux、支持 Kotlin

Spring 框架结构

Spring 框架中包含了哪些模块?

Spring Framework 5.x 结构图如下:

image-20221223022005181

在Spring5.x 版本中,Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。

上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍:

Core Container 核心容器

Spring 框架的核心模块,Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。没有这些核心组件,也不可能有 AOP、Web 等上层的功能。对应的源码模块如下:

  • spring-beans:提供了框架的基础部分,包括控制反转和依赖注入。
  • spring-core:封装了Spring框架的基本核心工具类。
  • spring-context:集成了beans功能并添加资源绑定、数据验证、国际化、容器生命周期、事件传播等功能。
  • spring-expression:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。

中间层模块

  • spring-aop:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
  • spring-aspects:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • spring-instrument:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • spring-messaging:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
  • spring-jcl: Spring 5.x中新增了日志框架集成的模块。

数据访问/集成模块

  • spring-jdbc:提供了一个 JDBC 的样例模板,使用这些模板能消除传统冗长的 JDBC 编码还有必须的事务控制,而且能享受到 Spring 管理事务的好处。
  • spring-orm:提供与流行的“对象-关系”映射框架无缝集成的 API,包括 JPA、JDO、Hibernate 和 MyBatis 等。而且还可以使用 Spring 事务管理,无需额外控制事务。
  • spring-oxm:提供了一个支持 Object /XML 映射的抽象层实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream。将 Java 对象映射成 XML 数据,或者将XML 数据映射成 Java 对象。
  • spring-jms: 指 Java 消息服务,提供一套 “消息生产者、消息消费者”模板用于更加简单的使用 JMS,JMS 用于用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
  • spring-tx:支持编程和声明式事务管理。

Web模块

  • spring-web:提供了基本的 Web 开发集成特性,例如多文件上传功能、使用的 Servlet 监听器的 IOC 容器初始化以及 Web 应用上下文。
  • spring-webmvc:提供了一个 Spring MVC Web 框架实现。Spring MVC 框架提供了基于注解的请求资源注入、更简单的数据绑定、数据验证等及一套非常易用的 JSP 标签,完全无缝与 Spring 其他技术协作。
  • spring-websocket:提供了简单的接口,用户只要实现响应的接口就可以快速的搭建 WebSocket Server,从而实现双向通讯。
  • spring-webflux: Spring WebFlux 是 Spring Framework 5.x中引入的新的响应式web框架。与Spring MVC不同,它不需要Servlet API,是完全异步且非阻塞的,并且通过Reactor项目实现了Reactive Streams规范。Spring WebFlux 用于创建基于事件循环执行模型的完全异步且非阻塞的应用程序。

Test模块

  • spring-test:Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。Spring的测试模块对 JUnit(单元测试框架)、TestNG、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static,private 方法)等等常用的测试框架支持的都比较好。

解惑

Spring、Spring MVC、Spring Boot 之间什么关系?

很多人对 Spring,Spring MVC,Spring Boot 这三者傻傻分不清楚!这里简单介绍一下这三者,其实很简单,没有什么高深的东西。

Spring

Spring 包含了多个功能模块,其中最重要的是 Spring-Core(主要提供 IoC 依赖注入功能的支持) 模块, Spring 中的其他模块(比如 Spring MVC)的功能实现基本都需要依赖于该模块。

Spring MVC

Spring MVC 是 Spring 中的一个很重要的模块,主要赋予 Spring 快速构建 MVC 架构的 Web 程序的能力。MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。

Spring Boot

使用 Spring 进行开发各种配置过于麻烦比如开启某些 Spring 特性时,需要用 XML 或 Java 进行显式配置。于是,Spring Boot 诞生了!Spring 旨在简化 J2EE 企业应用程序开发。Spring Boot 旨在简化 Spring 开发(减少配置文件,开箱即用!)。

Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程序,你还是需要使用 Spring MVC 作为 MVC 框架,只是说 Spring Boot 帮你简化了 Spring MVC 的很多配置,真正做到开箱即用!

IoC 由来

在开始学习 Spring框架的核心功能IoC之前,咱先看以下一个场景,来看看IoC技术是如何从Servlet时代衍生而来的。

Servlet时代的三层架构

这个场景对应的三层架构中的组件以及依赖是这样的:

img

Dao层

简单定义一个DemoDao结构,其中声明了一个findAll方法模拟从数据库查询一组数据:

1
2
3
public interface DemoDao {
List<String> findAll();
}

编写它对应的实现类 DemoDaoImpl ,由于没有引入数据库的相关驱动,故这里只是用写死的临时数据模拟 Dao 与数据库的交互:

1
2
3
4
5
6
7
public class DemoDaoImpl implements DemoDao {
@Override
public List<String> findAll() {
// 此处应该是访问数据库的操作,用临时数据代替
return Arrays.asList("aaa", "bbb", "ccc");
}
}

Service层

编写一个 DemoService 接口,并声明 findAll 方法:

1
2
3
public interface DemoService {
List<String> findAll();
}

编写它对应的实现类 DemoServiceImpl ,并在内部依赖 DemoDao 接口:

1
2
3
4
5
6
7
8
9
public class DemoServiceImpl implements DemoService {

private DemoDao demoDao = new DemoDaoImpl();

@Override
public List<String> findAll() {
return demoDao.findAll();
}
}

Controller层

编写DemoServlet1类,并在内部依赖 DemoService

1
2
3
4
5
6
7
8
9
10
@WebServlet(urlPatterns = "/demo1")
public class DemoServlet1 extends HttpServlet {

DemoService demoService = new DemoServiceImpl();

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().println(demoService.findAll().toString());
}
}

以上部分是咱在 JavaWeb 基础中最熟悉不过的三层架构了,好了到这里咱停下来,咱们引入几个问题。

问题1-需求变更

场景:现在你的手头上已经基本上开发完成了,数据库用的 MySQL 很舒服,临近交付项目,客户要求把数据库换成Oracle。

我们知道,MySQL 跟 Oracle ,在有一些特定的 SQL 上是不一样的(比如分页),这样我还不能只把数据库连接池的相关配置改了就好使,每个 DaoImpl 也得改啊!于是乎,你开始修改起工程里所有的 DaoImpl

1
2
3
4
5
6
7
8
public class DemoDaoImpl implements DemoDao {

@Override
public List<String> findAll() {
// 模拟修改SQL的动作
return Arrays.asList("oracle", "oracle", "oracle");
}
}

经历了一波波折改好了之后,这时客户又打电话要求换回原先的MySQL。改来改去不是个事啊,怎么解决这个问题呢?

引入静态工厂

苦思良久,你终于想到了一个好办法:如果我事先把这些 Dao 都写好了,之后用一个静态工厂来创建特定类型的实现类,这样万一发生需求变更,是不是就可以做到只改一次代码就可以了!

声明一个静态工厂,起个比较别致的名字吧:BeanFactory

1
2
3
4
5
6
public class BeanFactory {
public static DemoDao getDemoDao() {
// return new DemoDaoImpl();
return new DemoOracleDao();
}
}

这时候就可以修改先前的Service实现类,其中引用的 Dao 不再是手动 new ,而是由 BeanFactory 的静态方法返回而得:

1
2
3
4
5
6
7
8
9
public class DemoServiceImpl implements DemoService {

DemoDao demoDao = BeanFactory.getDemoDao();

@Override
public List<String> findAll() {
return demoDao.findAll();
}
}

如此这般,即便 ServiceImpl 再多,Dao 再多,发生需求更改,我也只需要改动 BeanFactory 中的静态方法返回值即可

问题2-源码丢失

场景:线上运行了一段时间后,客户要求对系统中一些功能提出优化和扩展需求,但这个时候当你打开项目源码想运行的时候,发现连编译都无法通过。再仔细查找,你发现DemoDaoImpl的源文件丢失了!导致代码根本无法编译。以下是编译错误的位置:

1
2
3
4
5
public class BeanFactory {
public static DemoDao getDemoDao() {
return new DemoDaoImpl(); // DemoDaoImpl.java不存在导致编译失败
}
}

当前的代码中,因为源码中真的缺少这个 DemoDaoImpl 类,导致编译都无法通过,这种现象就可以描述为 BeanFactory 强依赖于 DemoDaoImpl ,也就是咱可能听过也可能常说的“紧耦合”。

解决紧耦合

这时候源文件丢失的情况下,你又想快点运行项目,这怎么办?想一下在现有知识中,有没有一种办法能解决这个编译都没办法编译的问题?

反射!反射可以声明一个类的全限定名,来获取它的字节码描述,这样也能构造对象!

于是 BeanFactory 可以改造为:

1
2
3
4
5
6
7
8
9
10
11
public class BeanFactory {

public static DemoDao getDemoDao() {
try {
return (DemoDao) Class.forName("com.linkedbear.architecture.c_reflect.dao.impl.DemoDaoImpl").newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("DemoDao instantiation error, cause: " + e.getMessage());
}
}
}

使用反射之后,错误现象不再是在编译器就出现,而是在工程启动后,由于 BeanFactory 要构造 DemoDaoImpl 时确实还没有该类,所以抛出 ClassNotFoundException 异常。这样 BeanFactoryDemoDaoImpl 的依赖程度就相当于降低了,也就可以算作“弱依赖”了。

问题3-硬编码

场景:躲得了初一躲不了十五,这个问题最终还是得解决,你费劲八道的终于把 DemoDaoImpl.java 找了回来,这下终于运行期也不报错了。但这样在切换 MySQL 和 Oracle 库时还是会出现一个问题:由于类的全限定名是写死在 BeanFactory 的源码中,导致每次切换数据库后还得重新编译工程才可以正常运行,这显得貌似很没必要,应该有更好的处理方案。

外部化配置

机智的你利用现有的 JavaSE 知识,立马能想到:哎,我可以借助 IO 来实现文件存储配置啊!这样每次 BeanFactory 被初始化时,让它去读配置文件,这样就不会出现硬编码的现象了!

于是创建一个factory.properties配置文件,并在其中声明如下内容:

1
2
demoService=com.linkedbear.architecture.d_properties.service.impl.DemoServiceImpl
demoDao=com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl

这样BeanFactory就可以根据别名来匹配对应的Dao全限定类名了。首先可以在 BeanFactory 中加入一个静态变量:

1
2
public class BeanFactory {
private static Properties properties;

然后使用静态代码块初始化properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
private static Properties properties;

// 加载factord.properties文件
static {
properties = new Properties();
try {
// 必须使用类加载器读取resource文件夹下的配置文件
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
} catch (IOException e) {
// BeanFactory类的静态初始化都失败了,那后续也没有必要继续执行了
throw new ExceptionInInitializerError("BeanFactory initialize error, cause: " + e.getMessage());
}
}

配置文件读取到之后,下面的 getDemoDao 方法也可以进一步改了:

1
2
3
4
5
6
7
8
9
10
public static DemoDao getDemoDao() {
try {
Class<?> beanClazz = Class.forName(properties.getProperty("demoDao"));
return beanClazz.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException("[" + beanName + "] instantiation error!", e);
}
}

写到这里,是不是感觉怪怪的。。。都抽象化到这种地步了,还有必要在这里面写死 “demoDao” 吗?肯定没必要了吧,干脆做一个通用得了,你传什么别名,BeanFactory 就从配置文件中找对应的全限定类名,反射构造对象返回:

1
2
3
4
5
6
7
8
9
10
11
public static Object getBean(String beanName) {
try {
// 从properties文件中读取指定name对应类的全限定名,并反射实例化
Class<?> beanClazz = Class.forName(properties.getProperty(beanName));
return beanClazz.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException("[" + beanName + "] instantiation error!", e);
}
}

最后,在DemoServiceImpl里就可以转用getBean方法,并指定需要获取的指定名称的类的对象:

1
2
public class DemoServiceImpl implements DemoService {
DemoDao demoDao = (DemoDao) BeanFactory.getBean("demoDao");

到这里,你突然发现一个现象:这下你可以把所有想抽取出来的组件都可以做成外部化配置了!

对于这种可能会变化的配置、属性等,通常不会直接硬编码在源代码中,而是抽取为一些配置文件的形式( properties 、xml 、json 、yml 等),配合程序对配置文件的加载和解析,从而达到动态配置、降低配置耦合的目的。

问题4-多重构建

改到这里可能你会感觉,是不是哪里不对劲,是不是还有改进的空间呢?这样,咱在 ServiceImpl 的构造方法中连续多次获取 DemoDaoImpl

1
2
3
4
5
6
7
8
9
public class DemoServiceImpl implements DemoService {

DemoDao demoDao = (DemoDao) BeanFactory.getBean("demoDao");

public DemoServiceImpl() {
for (int i = 0; i < 10; i++) {
System.out.println(BeanFactory.getBean("demoDao"));
}
}

咱只来看打印的这些 DemoDao 的内存地址:

1
2
3
4
5
6
7
8
9
10
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@44548059
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@5cab632f
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@24943e59
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@3f66e016
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@5f50e9eb
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@58e55b35
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@5d06d086
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@55e8ed60
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@daf5987
com.linkedbear.architecture.d_properties.dao.impl.DemoDaoImpl@7f6187f4

可以发现每次打印的内存地址都不相同,证明是创建了10个不同的 DemoDaoImpl !但是,真的有必要吗。。。

引入缓存

如果对于这些没必要创建多个对象的组件,如果能有一种机制保证整个工程运行过程中只存在一个对象,那就可以大大减少资源消耗。于是可以在 BeanFactory 中加入一个缓存区:

1
2
3
4
5
public class BeanFactory {
// 缓存区,保存已经创建好的对象
private static Map<String, Object> beanMap = new HashMap<>();

// ......

之后在 getBean 方法中,为了控制线程并发,需要引入双检锁保证对象只有一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static Object getBean(String beanName) {
// 双检锁保证beanMap中确实没有beanName对应的对象
if (!beanMap.containsKey(beanName)) {
synchronized (BeanFactory.class) {
if (!beanMap.containsKey(beanName)) {
// 过了双检锁,证明确实没有,可以执行反射创建
try {
Class<?> beanClazz = Class.forName(properties.getProperty(beanName));
Object bean = beanClazz.newInstance();
// 反射创建后放入缓存再返回
beanMap.put(beanName, bean);
} catch (ClassNotFoundException e) {
throw new RuntimeException("BeanFactory have not [" + beanName + "] bean!", e);
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException("[" + beanName + "] instantiation error!", e);
}
}
}
}
return beanMap.get(beanName);
}

改良完成,重新测试,观察这一次打印的结果:

1
2
3
4
com.linkedbear.architecture.e_cachedfactory.dao.impl.DemoDaoImpl@4a667700
com.linkedbear.architecture.e_cachedfactory.dao.impl.DemoDaoImpl@4a667700
com.linkedbear.architecture.e_cachedfactory.dao.impl.DemoDaoImpl@4a667700
......

果然只会有一个对象了,最终目的达到。

IOC的思想引入

到这里,整个场景的演绎就算结束了,下面咱来总结一下这里面出现的几个关键点。

  • 静态工厂可将多处依赖抽取分离
  • 外部化配置文件+反射可解决配置的硬编码问题
  • 缓存可控制对象实例数

接下来,是时候引出这一章的主题了。

首先,对比以上的两种代码写法:

1
2
private DemoDao dao = new DemoDaoImpl();
private DemoDao dao = (DemoDao) BeanFactory.getBean("demoDao");

上面的是强依赖 / 紧耦合,在编译期就必须保证 DemoDaoImpl 存在;下面的是弱依赖 / 松散耦合,只有到运行期反射创建时才知道 DemoDaoImpl 是否存在。

再对比看,上面的写法是主动声明了 DemoDao 的实现类,只要编译通过,运行一定没错;而下面的写法没有指定实现类,而是由 BeanFactory 去帮咱查找一个 name 为 demoDao 的对象,倘若 factory.properties 中声明的全限定类名出现错误,则会出现强转失败的异常 ClassCastException

仔细体会下面这种对象获取的方式,本来咱开发者可以使用上面的方式,主动声明实现类,但如果选择下面的方式,那就不再是咱自己去声明,而是将获取对象的方式交给了 BeanFactory 。这种将控制权交给别人的思想,就可以称作:控制反转(Inverse of Control , IOC)。而 BeanFactory 根据指定的 beanName 去获取和创建对象的过程,就可以称作:依赖查找(Dependency Lookup , DL )

终于了解什么是 IOC ,以及它的实现方式之一:依赖查找。下面咱就可以真正的快速入门 SpringFramework 了