第一章 Spring之旅
- 一切都要从Bean开始
- 在1996年12月,Sun公司发布了JavaBean 1.00-A 规范。针对Java定义了软件组件模型,使得简单的Java对象不仅可以被重用,而且还可以轻松地构建更复杂的应用。
- 但是似乎看起来太简易了,无法胜任“实际的”工作
- 1998.3,Sun发布了EJB1.0规范,该规范把Java组件的设计理念延伸到了服务器端。并提供了许多必须的企业级服务。
- 但是还是有缺陷,于是开发者寻求更简单的方法
- Java组件开发逐渐回归正轨。编程技术提供了AOP和DI,为POJO提供了类似EJB的声明式编程模型。却没有引入任何EJB的复杂性
- EJB的发展促进了基于POJO的编程模型。
- Spring框架已经成为基于POJO的轻量级开发框架的领导者
1.1 简化Java开发
- Spring是一个开源框架,为了解决企业级应用开发的复杂性而创建的
不仅仅局限于服务器端开发,==任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益==
- POJO(Plain Ordinary Java Object):即普通的Java类,具有一部分getter/setter方法的类可称为POJO
实际意义就是普通的JavaBeans(简单的实体类),特点就是支持业务逻辑的协助类
对于程序员,可以把POJO类当作对象来使用,也可以方便的调用其get、set方法
- JavaBean:是一个可重复用的软件组件。实际上JavaBean是一种Java类,通过封装属性和方法成为具有某种功能或者处理某个业务的对象,简称bean。
JavaBean是一种Java语言写成的可重用组件。他的方法名,构造及行为规范必须符合特殊的约定
JavaBean的任务就是:”Write Once, run anywhere, reuse everywhere
- 简而言之,当一个POJO可序列化,有一个无参的构造函数,使用getter和setter方法来访问属性时,就是Javabean
- POJO(Plain Ordinary Java Object):即普通的Java类,具有一部分getter/setter方法的类可称为POJO
Spring的4种关键策略
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
1.1.1 激发POJO的潜能
- Spring不会强迫你实现Spring规范的接口或继承Spring规范的类
//一个很普通的例子: package com.habuma.spring; public class HelloWorldBean { public String sayHello() { return "Hello World"; } }
1.1.2 依赖注入(DI)
- 在项目中应用 DI , 会发现代码变得更简单且容易理解和测试
当每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,会导致高度耦合和难以测试
//DamselRescuingKnight 只能执行 RescueDamselQuest 探险任务 package sia.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { this.quest = new RescueDamselQuest(); } public void embarkOnQuest() { quest.embark(); } } //damsel少女 rescue营救 knight骑士 quest任务
- 可以看到 DamselRescuingKnight 在构造函数中自行创建了RescueDamselQuest,虽然使两者紧密的耦合了,但是也限制了这个方法,只能执行这一种方法
- 在为 DamselRescuingKnight 编写单元测试的时候也很难。必须保证骑士的 embarkOnQuest方法被调用,探险的embark方法也被调用
- 但是没有一种简单的方式能实现,所以DamselRescuingKnight 无法将被测试
耦合有两面性:
- 紧密耦合的代码难以复用和理解,bug也是打地鼠式的出现
- 一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了
- 耦合是必须的,但应当被小心谨慎地管理
- 通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定
对象无需自行创建或管理依赖关系
// BraveKnight 足够灵活可以接受任何赋予他的探险任务 package sia.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this.quest = quest; } public void embarkOnQuest() { quest.embark(); } }
- 不同于之前的DamselRescuingKnight,BraveKnight没有自行创建探险任务,而是把任务作为构造器参数传输。
- 这是依赖注入的方式之一,即==构造器注入==
- BraveKnight没有与任何特定的Quest实现发生耦合,被要求挑战的探险任务只要实现了Quest接口,就可以。
- 这就是DI所带来的的最大收益--==松耦合==。
- 如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能在对象本身毫不知情的情况下,用不同的具体实现进行替换。
将 Quest 装入到Knight中
- 如何将特定的Query传给BraveKnight呢?比如下面的 程序
-
public class SlayDragonQuest implements Quest{ private PrintStream stream; public SlayDragonQuest(PrintStream stream){ this.stream = stream; } public void embark(){ stream.println("Embarking on quest to slay the dragon!"); } }
- 创建应用组件之间协作的行为称为==装配(wiring)==
- Spring有多种装配bean的方式,比如XML
-
<?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.xsd"> <bean id = "knight" class = "sia.knights.BraveKnight"> <constructor-arg ref = "quest" /> </bean> <bean id="quest" class="sia.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> </bean> </beans>
- 在这里,BraveKnight 和 SlayDragonQuest 被声明为Spring中的bean 。就BraveKnight 这个bean来讲,它在构造时传入了对SlayDragonQuest 这个bean的引用,将其作为构造器参数。
- 同时,SlayDragonQuest 这个bean的声明使用了Sqel,将PrintStream传入到了SlayDragonQuest 的构造器中
- Spring还支持Java来描述配置
package sia.knights.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import sia.knights.BraveKnight; import sia.knights.Knight; import sia.knights.Quest; import sia.knights.SlayDragonQuest; @Configuration public class KnightConfig { @Bean public Knight knight() { return new BraveKnight(quest()); } @Bean public Quest quest() { return new SlayDragonQuest(System.out); } }
- 只有Spring通过它的配置,能够了解这些组成部分是如何装配的,可以在不改变所依赖的类的情况下,修改依赖关系
观察它如何工作
- Spring通过应用上下文(Application Context)装载 bean 的定义并把它们组装起来。Spring Application Context应用上下文全权负责对象的创建和组装。
- 因为 Knights.xml 中的bean是使用XML文件进行配置的。所以选择 ClassPathXmlApplicationContext 作为应用上下文相对是比较合适的。
1.1.3 应用切面
- DI 能够让相互协作的软件组件保持松散耦合,而面向切面编程(AOP)允许把遍布应用各处的功能分离出来形成可重用的组件
- 系统由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担额外的职责,诸如日志、事务管理和安全这样的系统服务。
- 这些系统服务通常被称为==横切关注点==,因为他们会跨越系统的多个组件
- 如果将这些关注点分散到多个组件中去,代码会带来双重复杂性
- 一个向地址簿增加地址条目的方法应该只关注如何添加地址,而不应该关注它是不是安全的或者是否需要支持事务
- AOP能使这些服务模块化,并以申明的方式将它们应用到它们需要影响的组件中去。AOP能确保POJO的简单性
AOP应用
- 这一节中又用到了骑士的例子。骑士做的事被流传了下来,但并不是通过他本人的宣传,而是吟游诗人的吟唱。
- 因为骑士只要做骑士的事,宣传则是别人的事。每个人做好分内的事,项目里每个模块做好自己的事
- 唯一有关联的,就是让每个有依赖的模块之间知道对方,互相注入等等
1.1.4 使用模板消除样式代码
- Spring旨在通过模板封装来消除样式代码。
- Spring的 JdbcTemplate 使得执行数据库操作时,可以避免写传统的 JDBC 样板
public Employee getEmployeeById(long id) { return jdbcTemplate.queryForObject( "select id, firstname, lastname, salary " + "from employee where id=?", new RowMapper<Employee>() { public Employee mapRow(ResultSet rs, int rowNum) throws SQLException { Employee employee = new Employee(); employee.setId(rs.getLong("id")); employee.setFirstName(rs.getString("firstname")); employee.setLastName(rs.getString("lastname")); employee.setSalary(rs.getBigDecimal("salary")); return employee; } }, id); }
1.2 容纳你的bean
- Spring应用中,应用对象生存于Spring容器。Spring容器负责创建对象、装配他们、配置他们并管理他们的整个生命周期,从生存到死亡
- 容器是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联
- Spring容器并不是只有一个。Spring自带了多个容器实现,可以归为两种
-
- bean工厂,提供基本的DI支持
- 应用上下文基于 BeanFactory 构建,并提供应用框架级别的服务
- 应用上下文更受欢迎
1.2.1 使用应用上下文
- 最常用的 5 种:
-
- AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文
- AnnotationConfigWebApplicationContext: 从一个或多个基于Java的配置类中加载 Spring Web 应用上下文
- ClassPathXMLApplicationContext: 从类路径下的一个或多个XML 配置文件中加载上下文定义,把应用上下文的定义文件作为类资源
- FileSystemXMLApplicationContext: 从文件系统下的一个或多个XML配置文件中加载上下文定义
- XMLWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义
从文件系统装载
ApplicationContext context = new FileSystemXmlApplicationCOntext("c:/knights.xml");
从类路径下装载
ApplicationContext context = new ClassPathXmlApplicationContext("knight.class");
- 两者区别是,使用文件加载是在指定的文件系统路径下查找Knight.xml文件;而类加载时在所有的类路径(包含JAR文件)下查找knight.xml文件
从 Java 配置中加载应用上下文
ApplicationContext context = new AnnotationConfigApplicationContext(com.springinaction.knights.config.KnightConfig.class);
这里没有指定加载Spring应用上下文所需的XML文件,AnnotationConfigApplicationContext通过一个配置类加载bean
1.2.2 bean的生命周期
- 传统的Java应用,很简单,new进行实例化,可以使用,bean不在使用,Java进行GC
- 正确理解Spring bean的生命周期非常重要,因为或许要利用Spring提供的扩展点来自定义bean的创建过程
- Spring 对 bean 进行实例化
- Spring 将值和 bean 的引用注入到 bean 对应的属性中;
- 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传递给 setBeanName()方法;
- 如果 bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法,将 BeanFactory 容器实例传入;
- 如果 bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法,将 bean 所在的应用上下文的引用传入进来;
- 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBefore-Initialization() 方法;
- 如果 bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet() 方法。类似地,如果 bean 使用 initmethod 声明了初始化方法,该方法也会被调用;
- 如果 bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessAfter-Initialization() 方法;.
- 此时,bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
- 如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。
1.3 俯瞰Spring风景线
- 在Spring框架之外,还存在一个构建在核心框架之上的庞大生态圈,他讲Spring扩展到不同的领域
1.3.1 Spring模块
- Spring框架有20个不同的模块,每个模块有3个JAR文件
- aop、aspects、beans、context、core、expression、instrument、jdbc、jms、messaging、test、web、webmvc
Spring 核心容器:
- 容器是最核心的部分,管理着Spring应用中对bean的管理。该模块还包括了Spring Bean 工厂,提供了 DI 功能。
- 除了bean工厂和应用上下文,也提供了许多企业服务。例如Email、JNDI、EJB等
- ==所有的Spring模块都构建于核心容器之上。当配置应用时,隐式的使用了这些类==
Spring的AOP
- 是Spring应用系统中开发切面的基础,与DI一样,AOP可以帮助应用对象解耦。
- 借助AOP,可以将遍布系统的关注点,从他们所应用的对象中解耦出来
数据访问与集成
- 使用Spring的JDBC和DAO模块抽象了这些样板式代码,使我们的数据库代码变得简单明了,还可以避免因为关闭数据块资源失败的问题
- 对于市面上很多ORM进行了集成
Web与远程调用
- Spring不仅能与多种流行的MVC框架进行集成,还自带了一个强大的MVC框架
Instrumentation
- Spring的Instrumentation为JVM提供了添加代理功能
测试
- 为使用JNDI、Servlet、Portlet编写单元测试集成了一系列的mock对象实现
1.3.2 Spring Portfolio
- Spring除了核心的框架,Portfolio 还包括多个构建于核心Spring框架之上的框架和类库。
Spring Web Flow
- 为基于流程的会话式 Web 应用提供了支持
Spring Web Service
- Spring Web Service 提供了契约优先的 Web Service 模型,服务的实现都是为了满足服务的契约而编写的。
Spring Security
- 安全对于许多应用都是一个非常关键的切面。利用 Spring AOP,Spring Security 为 Spring 应用提供了声明式的安全机制。
Spring Integration
- Spring Integration 提供了多种通用应用集成模式的 Spring 声明式风格实现。
Spring Batch
- 如果需要开发一个批处理应用,你可以通过 Spring Batch,使用 Spring 强大的面向 POJO 的编程模型
==Spring Boot==
- 重点
- Spring Boot 是一个崭新的令人兴奋的项目,它以 Spring 的视角, 致力于简化 Spring 本身。
- Spring Boot 大量依赖于自动配置技术,它能够消除大部分(在很多场 景中,甚至是全部)Spring 配置。它还提供了多个 Starter 项目,不管你使用 Maven 还是 Gradle,这都能减少 Spring 工程构建文件的大小。
1.4 Spring 的新功能
1.5 小结
- Spring 致力于简化企业级 Java 开发,促进代码的松散耦合。成功的关键在于依赖注入和AOP