Spring 是当前主流的 Java Web 开发框架,它是为了解决企业应用开发的复杂性问题而产生 的。 对于一个 Java 开发者来说,掌握 Spring 框架的使用,已是其必备的技能之一。 本章将对 Spring 框架的基础知识进行详细的讲解。
Spring 概述
- 什么是 Spring
- Spring 是由 Rod Johnson 组织和开发的一个分层的 Java SE/EE fu川一stack (一站式)轻量 级开源框架,它以 loC ( Inversion of Control ,控制反转)和 AOP ( Aspect Oriented Programming , 面向切面编程)为内核,使用基本的 JavaBean 来完成以前只可能由 EJB ( Enterprise Java Beans , Java 企业 Bean) 完成的工作,取代了 EJB 的靡肿、 低效的开发模式。
- Spring 致力于 Java EE 应用各层的解决方案,在表现层它提供了 Spring MVC 以及与 Struts 框架的整合功能;在业务逻辑层可以管理事务、 记录日志等;在持久层可以整合 MyBatis、 Hibernate、 JdbcTemplate 等技术。 因此,可以说 Spring 是企业应用开发很好的"一站式"选 择。 虽然 Spring 贯穿于表现层、 业务逻辑层和持久层,但它并不想取代那些已有的框架,而是 以高度的开放性与它们进行无缝整合。
- Spring 框架的优点
Spring 具有简单、可测试和松辑合等特点,从这个角度出发, Spring 不仅可以用于服务器 端开发,也可以应用于任何 Java 应用的开发中。 关于 Spring 框架优点的总结,具体如下。
- 非侵入式设计
Spring 是一种非侵入式( non-invasive )框架,它可以使应用程序代码对框架的依赖 、最小化。- 方便解耦、 简化开发
Spring 就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给 Spring 容器 管理,大大地降低了组件之间的搞合性。- 支持 AOP
Spring 提供了对 AOP 的支持,它允许将一些通用任务,如安全、 事务、 曰志等进行集中式 处理,从而提高了程序的复用性。- 支持声明式事务处理
只需要通过配置就可以完成对事务的管理,而无须手动编程。- 方便程序的测试
Spring 提供了对 Junit4 的支持,可以通过注解方便地测试 Spring 程序。- 方便集成各种优秀框架
Spring 不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如 Struts、 Hibernate、 MyBatis、 Quartz 等)的直接支持。- 降低 Java EE API 的使用难度
Spring 对 Java EE 开发中非常难用的一些 API (如 JDBC、 JavaMail 等),都提供了封装, 使这些 API 应用难度大大降低。
- Spring 的体系结构
Spring 框架采用的是分层架构,它一系列的功能要素被分成 20 个模块,这些模块大体分为 Core Container、 Data Access/lntegration、 Web、 AOP (Aspect Oriented Programming)、 Instrwmentation, Messaging 和 Test ,如图所示。
在图中,包含了 Spring 框架的所有模块,其中,灰色背景模块为本书中所涉及的主要 模块。 接下来分别对体系结构中的模块作用进行简单介绍,具体如下。
- Core Container (核心窑器)
Spring 的核心容器是其他模块建立的基础,它主要由 Beans 模块、 Core 模块、 Context 模 块、 Context-support 模块和 SpEL ( Spring Expression Language , Spring 表达式语言)模块 组成,具体介绍如下。
• Beans 模块:提供了 BeanFactory,是工厂模式的经典实现, Spring 将管理对象称为 Bean。
• Core 核心模块:提供了 Spring 框架的基本组成部分,包括 loC 和 DI 功能。
• Context 上下文模块: 建立在 Core 和 Beans 模块的基础之上,它是访问定义和配置的任 何对象的媒介。 其中 ApplicationContext 接口是上下文模块的焦点。
• Context-support 模块:提供了对第三方库嵌入 Spring 应用的集成支持,比如缓存 ( EhCache、 Guava、 JCache)、 邮件服务 (JavaMail)、 任务调度( CommonJ、 Quartz) 和模 板引擎( FreeMarker、 JasperReports、 速率)。
• SpEL 模块:是 Spring 3.0 后新增的模块,它提供了 Spring Expression Language 支持, 是运行时查询和操作对象图的强大的表达式语言。- Data Access/lntegration (戴帽访问l集成)
数据访问/集成层包括 JDBC、 ORM 、 OXM、 JMS 和 Transactions 模块,真体介绍如下。
• JDBC 模块:提供了一个 JDBC 的抽象层,大幅度地减少了在开发过程中对数据库操作的 编码。
• ORM 模块:对流行的对象关系映射 API ,包括 JPA、 JDO 和 Hibernate 提供了集成层支持。
• OXM 模块:提供了一个支持对象/XML 映射的抽象层实现,如 JAXB、 Castor、 XMLBeans、 JiBX 和 XStream。
• JMS 模块:指 Java 消息传递服务,包含使用和产生信息的特性,自 4. 1 版本后支持与 Spring-message 模块的集成。
• Transactions 事务模块:支持对实现特殊接口以及所有 POJO 类的编程和声明式的事 务管理。- Web
Spring 的 Web 层包括 WebSocket、 Servlet、 Web 和 Portlet 模块,具体介绍如下。
• WebSocket 模块: Spring 4.0 以后新增的模块,它提供了 WebSocket 和 SockJS 的实现, 以及对 STOMP 的支持。
• Servlet 模块:也称为 Spring-webmvc 模块,包含了 Spring 的模型一视图一控制器(MVC) 和 REST Web Services 实现的 Web 应用程序。
• Web 模块:提供了基本的 Web 开发集成特性,例如: 多文件上传功能、 使用 Servlet 监 听器来初始化 loC 容器以及 Web 应用上下文。
• Portlet 模块:提供了在 Portlet 环境中使用 MVC 实现,类似 Servlet 模块的功能。- 其他模块
Spring 的其他模块还有 AOP、 Aspects、 Instrumentation 以及 Test 模块,具体介绍如下。
• AOP 模块:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能 进行分离,以降低搞合性。
• Aspects 模块:提供了与 AspectJ 的集成功能, AspectJ 是一个功能强大且成熟的面向切 面编程 (AOP) 框架。
• Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器 中使用。
• Messaging 模块: Spring 4.0 以后新增的模块,它提供了对消息传递体系结构和协议 的支持。
• Test 模块:提供了对单元测试和集成测试的支持。
- Spring 的下载及目录结构
Spring 的第一个版本是在 2004 年发布的,经过 10 多年的发展, Spring 的版本也在不断地 升级优化中。我的代码都是基于Spring 4.3.6编写实现的, 建议大家也下载该版本。 Spring 开发所需的 JAR 包分为两个部分,具体如下。
- Spring 框架包 Spring 4.3.6 版本的框架压缩包,名称为 spring-framework-4.3.6.RELEASE-dist.zip ,此压缩包可以通过地址 ''https://repo.spring.io/release/org/springframework/spring/4.3.6.RELEASE/" 下载。 下载完成后,将压缩包解压到自定义的文件夹中,解压后的文件目录结 构如图所示。 在图的目录中, docs 文件夹中包含 Spring 的 API 文档和开发规范j libs 文件夹中包含开发需要的 JAR 包和源码 i schema 文件夹中包含开发所需要的 schema 文件,这些文件中定 义了 Spring 相关配置文件的约束。
打开 libs 目录可以看到 60 个 JAR 文件,如下图所示。
从图中可以看出. libs 目录中的 JAR 包分为三类,其中以 RELEASE.jar 结尾的是 Spring 框架 class 文件的 JAR 包;以 RELEASE - javadoc. jar 结尾的是 Spring 框架 API 文档的压缩包; 以 RELEASE-sources.jar 结尾的是 Spring 框架源文件的压缩包。 整个 Spring 框架由 20 个模块 组成,该目录下 Spring 为每个模块都提供了这三类压缩包。
在 libs 目录中,有四个 Spring 的基础包,它们分别对应 Spring 核心容器的四个模块,具体 介绍如下。
• spring-core-4.3.6.RELEASE.jar: 包含 Spring 框架基本的核心工具类, Spring 其他组件 都要用到这个包里的类,是其他组件的基本核心。
• spring-beans-4.3.6.RELEASE.jar: 所有应用都要用到的 JAR 包,它包含访问配置文件、 创建和管理 Bean 以及进行 Inversion of Control(loC)或者 Dependency Injection(DI)操作相关的 所有类。
• spring-context -4.3.6.RELEASE.jar: Spring 提供了在基础 loC 功能上的扩展服务,还提 供了许多企业级服务的支持,如邮件服务、任务调度、 JNDI 定位、 EJB 集咸、远程访问、缓存 以及各种视图层框架的封装等。
• spring-expression-4.3.6.RELEASE.jar: 定义了 Spring 的表达式语言。- 第三万依赖包
在使用 Spring 开发时,除了要使用自带的 JAR 包外, Spring 的核心容器还需要依赖 commons.logging 的 JAR 包。 该 JAR 包可以通过网址 "http://commons.apache.org/proper/commons-logging/download_logging.cgi
" 下载。 下载完成后,会得到一个名为 commonslogging-1 .2-bin.zit 的压缩包。 将压缩包解压到自定义目录后,即可找到 commons-Iogging1 .2.jar。
初学者学习 Spring 框架时,只需将 Spring 的 4 个基础包以及 commons-logging-1.2.jar 复制到项目的 lib 目录,并发布到类路径中即可。
Spring 的核心容器
Spring 框架的主要功能是通过其核心容器来实现的,因此在正式学习 Spring 框架的使用之 前,有必要先对其核心容器有一定的了解。Spring 框架提供了两种核心容器,分别为 BeanFactory 和 ApPI icationContext。 本节中将对这两种核心容器进行简单的介绍。
- BeanFactory
BeanFactory 由 org.springframework.beans.facytory.BeanFactory 接口定义,是基础类型 的 loC 容器(关于 loC 的具体含义将在后面的小节讲解,这里只需知道其表示控制反转), 它提供了完整的 loC 服务支持。 简单来说, BeanFactory就是一个管理 Bean 的工厂,它主要负 责初始化各种 Bean ,并调用它们的生命周期方法。
BeanFactory 接口提供了几个实现类,其中最常用的是 org.springframework. beans factory.xml.XmIBeanFactory ,该类会根据 XML 配置文件中的定义来装配 Bean。
创建 BeanFactory 实例时,需要提供 Spring 所管理容器的详细配置信息,这些信息通常采 用 XML 文件形式来管理,其加载配置信息的语法如下。BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("F: /applicationContext.xml")) ;
这种加载方式在实际开发中并不多用,大家了解即可。
- ApplicationContext
ApplicationContext是 BeanFactory 的子接口,也被称为应用上下文,是另一种常用的 Spring 核心容器。 它由 org.springframework.context. ApplicationContext 接口定义,不仅包含了 BeanFactory 的所有功能,还添加了对国际化、资源访问、事件传播等方面的支持。
创建 ApplicationContext 接口实例,通常采用两种方法,具体如下。
- 通过 ClassPathXmlApplicationContext
创建 ClassPathXmlAp~licationContext 会从类路径 classPath 中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,其使用语法如下。ApplicationContext applicationContext = new ClassPathXmlApplicationContext(String configLocation) ;
上述代码中, configLocation 参数用于指定 Spring 配置文件的名称和位置。 如果其值为 "applicationContext.xml" ,则 Spring 会去类路径中查找名称为 applicationContext.xml 的配置 文件。
- 通过 FileSystemXmlApplicationContext
创建 FileSystemXmlApplicationContext 会从指定的文件系统路径(绝对路径)中寻找指定的 XML 配置文件,找到并装载完成 ApplicationContext 的实例化工作,其使用语法如下。ApplicationContext applicationContext = new FileSystemXmlApplicationContext(String configLocation);
与 ClassPathXmlApplicationContext 有所不同的是,在读取 Spring 的配置文件时, FileSystemXmlApplicationContext 不再从类路径中读取配置文件,而是通过参数指定配置文件 的位置,例如 "D:/workspaces/appIicationContext. xml"。 如果在参数中写的不是绝对路径,那 么方法调用的时候,会默认用绝对路径来找。 这种采用绝对路径的方式,会导致程序的灵活性变差,所以这个方法一般不推荐使用。
在使用 Spring 框架时,可以通过实例化其中任何一个类来创建 ApplicationContext 容器。 通常在 Java 项目中,会采用通过 ClassPathXmlApplicationContext 类来实例化 ApplicationContext 容器的方式,而在 Web 项目中, ApplicationContext 容器的实例化工作会交由 Web 服务器来完成 。 Web 服务器实例化 ApplicationContext 容器时,通常会使用基于 ContextLoaderListener 实现的方式,此种方式只需要在 web.xml 中添加如下代码。<!-- 指定 Spring 配置文件的位置,多个配置文件时,以逗号分隔 --> <context-param> <param-name>contextConfigLocation</param-name> <!-- Spring 将加载 spring 目录下的 applicationContext .xml 文件 --> <param-value> classpath:spring/applicationContext.xml </param-value> </context-param> <!-- 指定以 ContextLoaderListener 方式启动 Spring 容器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
在后面章节讲解三大框架整合以及项目时,将采用基于 ContextLoaderListener 的方式 由 Web 服务器实例化 ApplicationContext 容器。
创建 Spring 容器后,就可以获取 Spring 容器中的 Beano Spring 获取 Bean 的实例通常采 用以下两种方法。
• Object getBean(String name): 根据容器中 Bean 的 id 或 name 来获取指定的 Bean ,获 取之后需要进行强制类型转换。
• <T> T getBean(Class<T> requiredType) : 根据类的类型来获取 Bean 的实例。 由于此方 法为泛型方法,因此在获取 Bean 之后不需要进行强制类型转换。
- 小提示
BeanFactory 和 ApplicationContext 两种容器都是通过 XML 配置文件加载 Bean 的。 二者的主 要区别在于,如果 Bean 的某一个属性没有注入,使用 BeanFacotry JJn载后,在第一次调用 getBean()方法时会抛出异常,而 ApplicationContext 则在初始化时自检,这样有利于检查所依赖 属性是否注入。 因此 ,在实际开发中,通常都优先选择使用 ApplicationContext ,而只有在系统 资源较少时,才考虑使用 BeanFactory。
Spring的入门程序
通过上一小节的学习,相信读者对Spring的核心容器已经有了一个初步的了解。为了帮助大家快速地学习Spring,下面通过一个简单的入门程序来演示Spring框架的使用。
(1)在Eclipse中,创建一个名为spring01的Web项目,将Spring的4个基础包以及commons-Iogging的JAR包复制到lib目录中,并发布到类路径下,如下图所示。
- (2)在src目录,创建一个com.neuedu.ioc包,并在包中创建接口UserDao,然后在接口中定义一个say()方法,如下面文件所示。
package com.neuedu.ioc; public interface UserDao { public void say(); }
- (3)在com.neuedu.ioc包下,创建UserDao接口的实现类UserDaolmpl,该类需要实现接口中的say()方法,并在方法中编写一条输出语句,如下面文件所示。
package com.neuedu.ioc; public class UserDaoImpl implements UserDao { @Override public void say() { System.out.println("userDao say hello World !"); } }
- (4)在src目录下,创建Spring的配置文件applicationContext.xml,并在配置文件中创建一个id为userDao的Bean,如下面文件所示。
<?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 http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> <!-- 将指定类配置给 Spring,让 Spring 创建其对象的实例 --> <bean id="userDao" class="com.neuedu.ioc.UserDaoImpl"></bean> </beans>
在上述文件中,第2-5行代码是Spring的约束配置。该配置信息不需要大家手写,可以在Spring的帮助文档中找到参见本节中的多学一招)。第7行代码表示在Spring器中创建一个id为userDao的Bean实例,其中class属性用于指定需要实例化Bean的类。
注意:Spring配置文件的名称可以自定义,通常在实际开发中,applicationContext.xml(有时也会命名为beans.xml)。
- (5)在com.neuedu.ioc包下,创建测试类TestloC,并在类中编写main()方法。在main()方法中,需要初始化Spring容器,并加载配置文件,然后通过Spring容器获取userDao实例(即Java对象),最后调用实例中的say()方法,如下面文件所示。
package com.neuedu.ioc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestIoc { public static void main(String[] args) { //1.初始化 spring 容器,加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.通过容器获取 userDao 实例 UserDao userDao = (UserDao) applicationContext.getBean("userDao"); //3.调用实例巾的 say()方法 userDao.say(); } }
行程序后,控制台的输出结果如下图所示。
从图上图可以看出,控制台已成功输出了UserDaolmpl类中的输出语句。在上面的文件main()方法中,并没有通过new关键字来创建UserDao接口的实现类对象,而是通过Spring容器来获取的实现类对象,这就是SpringloC容器的工作机制。
多学一招:快速获取配置文件的约束信息
在Spring的配直文件中,包含了很多约束信息,初学者如果自己动手去编写,不但浪费时间,还容易出错。其实,在Spring的帮助文档中,就可以找到这些约束信息,具体的获取方法下。
打开Spring解压文件夹中的docs目录,在spring-framework-reference文件夹下打开html文件夫,并找到index.html文件,如下图所示。
使用浏览器打开index.html后,在浏览器页面的OverviewofSpringFramework下的7.2.1小节Configurationmetadata中,即可找到配直文件的约束信息,如图所示.在图中,标记处的配直信息就是Spring配直文件的约束信息。初学者只需将标注处的信息复制到项目的配直文件中使用即可。此外,由于使用的是Spring4.3版本,所以还需要在复制后的xsd信息中加入版本号信息,其代码如下所示。http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
学习时,大家可以直接复制相关的源代码。如果涉及配直文件的约束信息,可以将相应源代码中的配直文件约束信息复制过来直接使用。
依赖注入
- 依赖注入的概念
依赖注入(DependencyInjection,简称DI)与控制反转(loC)的含义相同,只不过这两个称呼是从两个角度描述的同一个概念。对于一个Spring初学者来说,这两种称呼很难理解,下面我们将通过简单的语言来描述这两个概念。当某个Java对象(调用者)需要调用另一个Java对象(被调用者,即被依赖对象)时,在传统模式下,调用者通常会采用"new被调用者"的代码方式来创建对象,如下图所示。这种方式会导致调用者与被调用者之间的捐合性增加,不利于后期项目的升级和维护。
在使用Spring框架之后,对象的实例不再由调用者来创建,而是由Spring容器来创建,Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由应用代码转移到了Spring容器,控制权发生了反转,这就是Spring的控制反转。
从Spring容器的角度看,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它依赖的实例,这就是Spring的依赖注入,如下图所示。
- 依赖注入的实现方式
依赖注入的作用就是在使用Spring框架创建对象时,动态地将其所依赖的对象注入Bean组件中,其实现方式通常有两种,一种是属性setter方法注入,另一种是构造方法注入,具体介绍如下。
- 属性setter方法注入:指loC容器使用setter方法注入被依赖的实例。通过调用无参构造器或无参静态工厂方法实例化Bean后,调用该Bean的setter方法,即可实现基于setter方法的依赖注入。
- 构造方法注入:指loC容器使用构造方法注入被依赖的实例。基于构造方法的依赖注入通过调用带参数的构造方法来实现,每个参数代表着一个依赖。
了解了两种注入方式后,下面以属性setter方法注入的方式为例,讲解一下Spring容器在应用中是如何实现依赖注入的。
(1)在com.neuedu.ioc包中,创建接口UserService,在接口中编写一个say()方法,如下面文件所示。package com.neuedu.ioc; public interface UserService { public void say(); }
(2)在com.neuedu.ioc包中,创建UserService接口的实现类UserServicelmpl,在类中声明userDao属性,并添加属性的setter方法,如下面文件所示。
package com.neuedu.ioc; public class UserServicelmpl implements UserService { //声明userDao属性 private UserDao userDao; //添加属性的setter方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } //实现的接口中的方法 @Override public void say() { //调用UserDao中的say()方法,并执行输出语句 this.userDao.say(); System.out.println("userService say hello World !"); } }
(3)在配置文件appIicationContext.xmI中,创建一个id为userService的Bean,该Bean用于实例化UserServicelmpl类的信息,并将userDao的实例注入到userService中,其代码如下所示。
<!-- 添加一个 id 为 userService 的实例 --> <bean id="userService" class="com.neuedu.ioc.UserDaoImpl"> <!-- 将 id 为 userDao 的 Bean 实例注入到 userService 实例中 --> <property name="userDao" ref="userDao"></property> </bean>
上述代码中,<property>是<bean>元素的子元素,它用于调用Bean实例中的setUserDao()方法完成属性赋值,从而实现依赖注入。其name属性表示Bean实例中的相应属性名,ref属性用于指定其属性值。
(4)在com.neuedu.ioc包中,创建测试类TestDI,来对程序进行测试,编辑后如下面文件所示。package com.neuedu.ioc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestDI { public static void main(String[] args) { //1.初始化 spring 容器,加载配置文件 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.通过容器获取 UserService 实例 UserService userService = (UserService)applicationContext.getBean("userService"); //3. 调用实例中的 say() 方法 1 userService.say(); } }
(5)执行程序后,控制台的输出结果如图所示。
从上图可以看出,使用Spring容器通过UserService实现类中的say()方法,调用了UserDao实现类中的say()方法,并输出了结果。这就是Spring容器属性setter注入的方式,也是实际开发中最为常用的一种方式。
- 本章小结
本章主要讲解了Spring框架入门的一些基础知识。首先讲解了Spring框架的概念、作用、优点、体系结构以及如何下载和下载后的目录结构,然后介绍了Spring的两种核心容器。接下来通过一个入门程序来讲解如何使用Spring框架。最后详细讲解了依赖注入和控制反转的概念,并演示了依赖注入中setter方法注入的实现。通过本章的学习,大家可以对Spring框架及其体结构有一个初步的了解,能够初步地掌握Spring框架的使用,并能够理Spring框架中loC和DI的思想,掌握属性setter方法注入的实现。