本系列文章旨在记录和总结自己在 java Web开发之路上的知识点、经验、问题和思考,原来已经分享在我的CSDN博客,现在分享在头条,希望能帮助更多码农和想成为码农的人。版权声明:本文为CSDN博主「普通的码农」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。原文链接:
目录
- 介绍
- 再谈 Spring 框架及其模块
- Spring的工作模式
- 工程结构
- 准备食材 – 开发业务组件
- 菜品制作清单 – 配置 元数据
- 聘请厨师兼菜品管理员 – Spring IoC容器
- IoC的含义和好处
- 总结
介绍
本篇文章介绍Spring IoC( Inversion of Control,控制反转 )是如何使用 XML 来装配 Bean 的。这里,Bean其实就是对象,还有很多其他说法,比如Java Bean、POJO(Plain Old Java Object,普通Java对象)等,都是差不多的意思,不必深究。
不过,以后文章中的Bean就专门指Spring IoC容器管理的对象。
准备工作:
- 在Eclipse中新建Java工程:spring-ioc-test,可以参考 。
- 在该工程中添加好Spring IoC模块的那几个JAR包,可以参考 。
再谈Spring框架及其模块
我们以后提到Spring框架,就是指官网中的Spring Framework这个项目,而不是指Spring Boot、Spring Data等其他项目,也不是指Spring Framework这个项目包含的某个模块比如Spring MVC等。
介绍了Spring框架的模块划分,它来源于《Spring实战第4版》:
而Spring官方文档()则将Spring框架分为以下几个部分来介绍:
也介绍过Spring的基础、核心模块就是生产、组装/装配对象,用专业一点的术语就是Spring框架提供了一个IoC(Inversion of Control,控制反转)容器,具体用的技术就是DI(Dependency Injection,依赖注入)。
所以综合以上两种分类,我们以后就使用 IoC 表示Spring框架的核心容器这一个模块,主要包括spring-beans-5.1.7.RELEASE.jar(后续简写为beans,下同)、context、context-indexer、context-support等JAR包。core这个JAR包包括了很多基础的工具,expression这个JAR包主要实现了Spring表达式语言,也一并归到IoC模块吧。
而将官方文档中Core模块中的 AOP (Aspect Oriented Programming,面向切面编程)独立出来。
Spring的工作模式
再次描述一下Spring的工作模式:
我们就根据这个工作模式来构建我们的应用系统。
整个系统就好像是厨师根据菜品制作清单,使用各种食材制作出一个个菜肴。
- 厨师就是Spring IoC容器,生产并管理Bean;
- 菜品制作清单就是配置元数据,描述了Bean是如何生产(依赖于代码级构件)和装配(依赖于其他Bean)的;
- 食材就是业务组件,Bean的代码级构件;
- 每个菜肴就是一个Bean。
感觉这个比喻也不怎么恰当,不过还是有一定类似之处的。
工程结构
先给出整个工程的结构:
这里就不再赘述如何 了,前面的文章已经介绍过了,新建XML文件也是类似。也不再赘述 了(图中的spring节点)。
配置元数据XML文件需要放在src节点下,这样后面使用 classpath XmlApplicationContext就可直接传入XML文件名称即可,因为src属于classpath下的一个路径,ClassPathXmlApplicationContext从类名上看就是从classpath中去寻找XML文件。
当然,还有FileSystemXmlApplicationContext之类的可以从其他地方去寻找XML文件。Spring框架提供了足够多的IoC容器给我们选择。
准备食材 – 开发业务组件
首先,在工程中开发你所需要的业务组件,我的业务组件包括:
- 服务层的 service A、ServiceB
- 数据访问层的RepositoryA、RepositoryB
所谓服务层,就是专门提供某种业务服务的。
一个业务服务可能要涉及到多种业务数据(业务实体)的读写操作,各种业务数据可能来自不同的数据源,比如有的来自文件系统,有的来自关系数据库,有的来自缓存,有的来自 NoSql 等等。所以,为了屏蔽这些差异,就有了数据访问层。
实际上,分层就是单一职责原则的体现,服务层就是用来计算数据的,数据访问层就是用来操作(读/写)数据的。
它们的依赖关系如下:
其实,这就是 架构设计 ,本质上就是划分职责,确定它们的依赖关系。当然,实际中的应用系统比这个复杂得多,这里只是用于演示基本原理和流程。
下面是各组件的代码。
ServiceA.java
package test.service; import test.repository.RepositoryA; public class ServiceA { private ServiceB serviceB; private RepositoryA repositoryA; public ServiceA (ServiceB serviceB, RepositoryA repositoryA) { this.serviceB = serviceB; this.repositoryA = repositoryA; } public void doWork() { System.out.println("ServiceA doWork start"); serviceB.doWork(); repositoryA.save(); System.out.println("ServiceA doWork end"); } }
ServiceB.java
package test.service; import test.repository.RepositoryB; public class ServiceB { private RepositoryB repositoryB; public RepositoryB getRepositoryB() { return repositoryB; } public void setRepositoryB(RepositoryB repositoryB) { this.repositoryB = repositoryB; } public void doWork() { System.out.println("ServiceB doWork start"); repositoryB.update(); System.out.println("ServiceB doWork end"); } }
RepositoryA.java
package test.repository; public class RepositoryA { public void save() { System.out.println("RepositoryA save ..."); } }
RepositoryB.java
package test.repository; public class RepositoryB { public void update() { System.out.println("RepositoryB update ..."); } }
菜品制作清单 – 配置元数据
接下来,就需要编写我们的菜品制作清单,就是Spring IoC容器需要的配置元数据(Configuration Metadata,这是官方文档的说法)。
配置元数据的提供有三种方式:
- 基于XML文档的最原始的方式;
- 基于Java注解的自动扫描和装配的方式:Spring 2.5引入;
- 基于Java配置的方式:Spring 3.0引入。
这篇文章我们先使用基于XML文档的方式。
spring-ioc-test.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="" xmlns:xsi="" xsi:schemaLocation=" "> <bean id="svcA" class="test.service.ServiceA"> <constructor-arg name="serviceB" ref="svcB" /> <constructor-arg name="repositoryA" ref="repoA" /> </bean> <bean id="svcB" class="test.service.ServiceB"> <property name="repositoyB" ref="repoB" /> </bean> <bean id="repoA" class="test.repository.RepositoryA" /> <bean id="repoB" class="test.repository.RepositoryB" /> </beans>
关于XML的知识,我们暂且不细说,它类似于前面说的HTML,也是一种标记语言,也是使用尖括号,实际上它们很相似,渊源也颇深。
这里只关注以下几个重点标记。
- beans标记 :它是整个配置的根标记。
- bean标记 :定义了一个Bean,Spring IoC就是根据这个标记的内容来 生产和装配 Bean的。它有两个属性, id 表示该Bean在某个Spring IoC容器(一个应用中可以有多个Spring IoC容器)中的唯一标识; class 就是你的业务组件了,必须是全限定名称。
- constructor-arg标记 :用于使用 构造器 生成Bean的时候,传递所依赖的 构造器参数 ,属性 name 表示参数名,跟代码中的构造器参数一致;属性 ref 表示该参数的值是一个引用,指向其他的Bean,填写其他Bean的id即可。这就是 构造器注入 。
- property标记 :用于Bean已经生成,使用 setter方法注入 所依赖的Bean,这里的属性 name 指的是该Bean的某个属性的名字,跟代码中的类的属性名一致;属性 ref 的含义跟上面的一样。
上面的代码还可以看到,不依赖于任何其他Bean的Bean(比如上面的repoA和repoB)就不需要constructor-arg标记和/或property标记了。
这些标记其实也不难记,因为标记名称很容易就望文生义,顾名思义。
聘请厨师兼菜品管理员 – Spring IoC容器
食材和菜品制作清单已经准备好,最后就只需要厨师来烹制了,那我们应该怎么样聘请厨师呢?
SpringMain.java:
package test;
import org.springframework.context. ApplicationContext ;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import test.service.ServiceA;
public class SpringMain {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-ioc-test.xml");
ServiceA svcA = context.getBean("svcA", ServiceA.class);
svcA.doWork();
}
}
- ApplicationContext :这个接口即代表/描述了 Spring IoC容器 ,即它就是负责生产/实例化、配置、装配、管理那些Bean, 所以以后提到Spring IoC容器,其实就是一个ApplicationContext实例,聘请厨师不过就是生成一个ApplicationContext实例而已 。
- ClassPathXmlApplicationContext :这是一个 具体的 Spring IoC容器,它在classpath中寻找指定的XML格式的配置元数据,然后根据清单生产并装配Bean。
- getBean方法 :从容器中获取一个Bean,根据该Bean的id即可。
- 剩下的就是 使用 从容器中获取到的Bean了。
当然,只有在程序运行时,才会真正实例化Spring IoC容器,然后它才根据配置元数据,生产和装配Bean。
IoC的含义和好处
其实,我们也可以像下面那样在代码中进行Bean的生产和装配:
Main.java:
package test; import test.repository.RepositoryA; import test.repository.RepositoryB; import test.service.ServiceA; import test.service.ServiceB; public class Main { public static void main(String[] args) { RepositoryA repoA = new RepositoryA(); RepositoryB repoB = new RepositoryB(); ServiceB svcB = new ServiceB(); svcB.setRepositoyB(repoB); ServiceA svcA = new ServiceA(svcB, repoA); svcA.doWork(); } }
这就是IoC(Inversion of Control,控制反转)的含义了,原来需要我们的代码甚至是Bean本身内部来 控制 它所依赖的Bean的生产,而现在将这种 控制权 交给了Spring IoC容器了。
那这样做有什么好处呢?最显然的好处是,代码中的new语句少了,这不就 消除重复 了吗。问题是这种重复不又跑到配置文件里去了吗,而且看起来配置文件要敲击的文字反而更多,这是基于XML的配置元数据的弊端,据说以前的Spring应用开发工程师疲于应付配置文件的这种繁冗。
这样带来的好处是依赖关系的 集中配置 ,这又是另外一大原则“ 物以类聚 ”的体现,而不是将依赖关系散布在代码的各个角落,这样很不清晰和不利于管理。
其次,基于XML的配置元数据很灵活, 依赖关系发生改变后不需要重新编译代码 。
其实,IoC的好处需要实践去体会,我理解的也不是很深刻。
总结
- 所谓 架构设计 ,本质上就是划分职责,确定它们的依赖关系。
- IoC 就是指Bean的生产和装配交给专门的组件来控制,而无需Bean本身来控制。
- DI(依赖注入) 实际上就是由容器来将依赖关系注入到某个Bean。
- IoC其中一个好处是 解耦 。
- Spring IoC容器由 ApplicationContext 接口描述(当然,最底层是 BeanFactory 这个接口),具体的有很多种容器可以选择,比如ClassPathXmlApplicationContext等。
- 基于XML的配置元数据主要是用于 生产 Bean的 <beans />和<bean />标记 ,用于 依赖注入 的 <constructor-arg />和<property />标记 ,前者用于 构造器注入 ,后者用于 setter方法注入 。