主要内容
- Spring的使命——简化Java开发
- Spring容器
- Spring的总体架构
- Spring的新发展
如今的Java程序员遇上了好时候。在将近20年的历史中,Java的发展历经沉浮。尽管有不少为人诟病的产品,例如applets、EJB、Java Data Object(JDO)和数不清的日志框架,Java仍是发展为一个庞大且丰富的开发平台,不少企业级应用都是基于JVM平台构建。Spring是JVM开发平台中的一颗明珠。css
Spring最开始出现的目的是替代企业级开发框架EJB,相比EJB,Spring提供更轻量和更易用的编程模型。Spring的重要特色是非侵入式加强POJO(plain old java object)的能力。html
在后续的发展过程当中,EJB也效仿Spring的作法提供了简单的以POJO为中心的编程模型,如今的EJB框架也拥有依赖注入(DI)和面向切面编程(AOP)能力,能够论证是受Spring成功的影响。java
尽管J2EE一直在追赶Spring的发展,可是Spring自己也没有中止进步。如今,Spring在一些J2EE刚刚涉入或者彻底没有涉入的领域飞速发展:移动开发、社交API整合、NoSQL数据库、云计算和大数据。就目前来看,Spring的将来一片光明。android
重要的事情再强调一遍:如今的Java程序员遇上了好时候。程序员
这篇文章会从一个比较高的层次探索Spring,介绍Spring框架解决了哪些主要问题。web
Spring是一种开源框架,由Rod Johnson发明,并在其著做《Expert One-on-One:J2EE设计与开发》。Spring的初衷是下降企业级开发的复杂性,并试图经过POJO对象实现以前EJB这类重型框架才能实现的功能。Spring不只仅对服务端开发有用,任何Java应用均可受益于Spring的简洁、易测试和低耦合等特性。spring
Spring框架中使用beans或JavaBeans来表示应用程序中的组件,但这并不意味着该组件必须严格知足Java Bean的规范。数据库
Spring作了不少事情,可是归根究竟是一些基本的思路,而全部这些思路最终都导向Spring的使命:简化Java开发。express
Spring经过下列四种策略来简化Java开发:编程
几乎Spring的每条特性均可以追溯到这四条策略之一,接下来分别对这四条策略进行阐述,并给出具体的代码说明Spring如何简化Java开发。
若是你作Java开发足够久,你应该遇到过不少会束缚程序员能力的开发框架,这些框架要求程序员继承框架提供的类或者实现它提供的接口,例如EJB框架中的session beans,另外,在EJB以前的不少框架中也有相似的侵入式编程模型,如Struts、WebWork、Tapestry等等。
Spring尽可能避免让本身的API污染你的应用代码。Spring几乎不会强制要求开发人员实现某个Spring提供的接口或者继承某个Spring提供的类,在Spring应用中的Java类看起来和普通类同样,不过,Spring如今常用注解来修饰Java类,可是这个类仍是一个POJO。
举个代码例子说明,看以下的HelloWorldBean
package com.spring.sample; public class HelloWorldBean { public String sayHello() { return "Hello World"; } }
能够看出,这就是一个简单的Java类-POJO,没有什么特殊的标志代表它是一个Spring组件。Spring这种非侵入式编程模型使得这个类在Spring和非Spring框架下具有相同的功能。
尽管形式很是简单,POJO的能力值却可能很是高,例如Spring能够经过依赖注入编织这些POJOs来激发POJO的能力。
依赖注入听起来比较吓人,貌似一种很是复杂的编程技术或者设计模式。实际上依赖注入并不复杂,经过在工程中应用依赖注入技术,能够获得更简单、更容易理解和测试的代码。
除了Hello-world级别的程序,稍微复杂一点的Java应用都须要多个类配合实现功能。通常而言,每一个类本身负责获取它要合做的类对象的引用,这会致使代码高度耦合且难以测试。
首先看以下代码:
package com.spring.sample.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() { this.quest = new RescueDamselQuest(); //与RescueDamselQuest紧耦合 } public void embarkOnQuest() { quest.emark(); } }
能够看出,DamselRescuingKnight在它的构造函数中建立了本身的Quest实例——RescueDamselQuest实例,这使得DamselRescuingKnight与RescueDamselQuest紧密耦合,若是须要刺杀Damsel,则这个刀可使用,可是若是须要刺杀恐龙,则这个刀就派不上用场了。
更糟的是,给DamselRescuingKnight写单元测试很不方便,在这个测试中,你必须确认:当调用knight的emarkOnQuest函数时,quest的embark函数也正确调用,但这并不容易。
耦合是一头双头怪:一方面,紧耦合的代码难以测试、难以复用而且难以理解,而且常常陷入“修复一个bug但引入一个新的bug”的开发怪圈中;另外一方面,应用程序必须存在适当的耦合,不然该应用没法完成任何功能。总之,耦合是必要的,可是应该控制组件之间的耦合程度。
经过使用依赖注入(DI)技术,对象之间的依赖关系由Spring框架提供的容器进行管理,而不须要某个对象主动建立本身须要的引用,以下图所示:
再看一个BraveKnight类的例子:
package com.spring.sample.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { // Quest实例被注入 this.quest = quest; } public void embarkOnQuest() { quest.emark(); } }
该对象再也不局限于一种quest实例,在构造过程当中利用构造函数的参数传入quest实例,这种类型的依赖注入称为构造注入。
还有一点须要注意,使用接口定义quest实例,这就是面向接口编程,使得BraveKnight再也不局限于某种特定的Quest实现,这就是DI带来的最大的好处——松耦合。
在上述例子代码能够看出,Spring至关于将依赖注入的位置从BraveKnight类中剥离出来,那么具体的依赖注入代码如何写呢?开发人员如何规定给BraveKnight注入哪一个Quest实现,例如SlayDragonQuest?
package com.spring.sample.knights; import java.io.PrintStream; public class SlayDragonQuest implements Quest { private PrintStream stream; public SlayDragonQuest(PrintStream stream) { this.stream = stream; } public void emark() { stream.println("Embarking on quest to slay the dragon!"); } }
在Spirng框架中,最通用的方法是经过写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="com.spring.sample.knights.BraveKnight"> <constructor-arg ref="quest" /> </bean> <bean id="quest" class="com.spring.sample.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> </bean> </beans>
在这个xml配置文件中分别定义了BraveKnight和SlayDragonQuest两个bean:在BraveKnightbean的定义中,经过构造器函数传入一个SlayDragonQuest的引用;在SlayDragonQuest的定义中,经过SpEL语言将System.out传入它的构造函数。
Spring 3.0引入了JavaConfig,这种写法比xml文件的好处是具有类型安全检查,例如,上面XML配置文件能够这么写:
package com.spring.sample.knights.config; import com.spring.sample.knights.BraveKnight; import com.spring.sample.knights.Knight; import com.spring.sample.knights.Quest; import com.spring.sample.knights.SlayDragonQuest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class KnightConfig { @Bean public Knight knight() { return new BraveKnight(quest()); } @Bean public Quest quest() { return new SlayDragonQuest(System.out); } }
不管是基于XML的配置仍是基于Java文件的配置,都由Spring框架负责管理beans之间的依赖关系。
在Spring应用中,由application context负责加载beans,并将这些beans根据配置文件编织在一块儿。Spring框架提供了几种application context的实现,若是使用XML格式的配置文件,则使用ClassPathXmlApplicationContext;若是使用Java文件形式的配置文件,则使用AnnotationConfigApplicationContext。
package com.spring.sample.knights; import com.spring.sample.knights.config.KnightConfig; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class KnightMain { public static void main(String[] args) { // ClassPathXmlApplicationContext context = // new ClassPathXmlApplicationContext("classpath:/knight.xml"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class); Knight knight = context.getBean(Knight.class); knight.embarkOnQuest(); context.close(); } }
上述代码中,根据KnightConfig.java文件建立Spring应用上下文,能够把该应用上下文当作对象工厂,来获取idknight的bean。
若是你想了解更多关于DI的知识,能够查看Dhanji R. Prasanna's Dependency Injectionhttps://www.manning.com/books/dependency-injection一书。
依赖注入(DI)实现了模块之间的松耦合,而利用面向切面编程(AOP)能够将涉及整个应用的基础功能(安全、日志)放在一个可复用的模块中。
AOP是一种在软件系统中实现关注点分离的技术。软件系统由几个模块构成,每一个模块负责一种功能,不过在系统中有些需求须要涉及到全部的模块,例如日志、事务管理和安全等。若是将这些需求相关的代码都分散在各个模块中,一方面是不方便维护、另外一方面是与原来每一个模块的业务逻辑代码混淆在一块儿,不符合单一职责原则。
下面这张图能够体现这种复杂性,左边的业务逻辑模块与右边的系统服务模块沟通太过密切,每一个业务模块须要本身负责调用这些系统服务模块。
AOP能够模块化这些系统服务,而后利用声明式编程定义该模块须要应用到那些业务逻辑模块上。这使得业务模块更简洁,更专一于处理业务逻辑,简而言之,切面(aspects)确保POJO仍然是普通的Java类。
能够将切面想象为覆盖在一些业务模块上的毯子,以下图所示。在系统中有一些模块负责核心的业务逻辑,利用AOP能够为全部这些模块增长额外的功能,并且核心业务模块无需知道切面模块的存在。
继续上面的例子,若是须要一我的记录BraveKnight的所做所为,下面代码是该日志服务:
package com.spring.sample.knights; import java.io.PrintStream; public class Minstrel { private PrintStream stream; public Minstrel(PrintStream stream) { this.stream = stream; } public void singBeforeQuest() { stream.println("Fa la la, the knight is so brave!"); } public void singAfterQuest() { stream.println("Tee hee hee, the brave knight did embark on a quest!"); } }
而后在XML文件中定义Minstrel对应的切面:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="knight" class="com.spring.sample.knights.BraveKnight"> <constructor-arg ref="quest" /> </bean> <bean id="quest" class="com.spring.sample.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> </bean> <bean id="minstrel" class="com.spring.sample.knights.Minstrel"> <constructor-arg value="#{T(System).out}" /> </bean> <aop:config> <aop:aspect ref="minstrel"> <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/> <aop:before method="singBeforeQuest" pointcut-ref="embark" /> <aop:after method="singAfterQuest" pointcut-ref="embark" /> </aop:aspect> </aop:config> </beans>
在这个配置文件中增长了aop配置名字空间。首先定义Minstrel的bean,而后利用<aop:config>标签订义aop相关的配置;而后在<aop:aspect>节点中引用minstrel,定义方面;aspect负责将pointcut和要执行的函数(before、after或者around)链接在一块儿。
还有一种更先进的写法,利用注解和Java配置文件,能够参考aop docs
Spring框架中的一些子模块也是基于AOP实现的,例如负责事务处理和负责安全的模块。
在编程过程当中有没有感受常常须要写重复无用的代码才能实现简单的功能,最经典的例子是JDBC的使用,这些代码就是样板式代码(boilerplate code)。
以JDBC的使用举个例子,这种原始的写法你必定见过:
public Employee getEmployeeById(long id) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try { conn = dataSource.getConnection(); stmt = conn.prepareStatement("select id, name from employee where id=?"); stmt.setLong(1, id); rs = stmt.executeQuery(); Employee employee = null; if (rs.next()) { employee = new Employee(); employee.setId(rs.getLong("id")); employee.setName(rs.getString("name")); } return employee; } catch (SQLException e) { } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { } } if (conn != null) { try { conn.close(); } catch (SQLException e) { } } } return null; }
能够看到,上面这么一坨代码中只有少数是真正用于查询数据(业务逻辑)的。除了JDBC的接口,其余JMS、JNDI以及REST服务的客户端API等也有相似的状况出现。
Spring试图经过模板来消除重复代码,这里所用的是模板设计模式。对于JDBC接口,Spring提供了JdbcTemplate模板来消除上面那个代码片断中的样板式代码,例子代码以下:
public Employee getEmployeeById(long id) { return jdbcTemplate.queryForObject( "select id, name from employee where id=?", new RowMapper<Employee>() { public Employee mapRow(ResultSet resultSet, int i) throws SQLException { Employee employee = new Employee(); employee.setId(resultSet.getLong("id")); employee.setName(resultSet.getString("name")); return employee; } }); }
你没有看错,就是利用回调函数实现的,有兴趣的读者能够深刻研究下JdbcTemplate的源码实现。
咱们上面已经演示了Spring简化Java开发的四种策略:面向POJO开发、依赖注入(DI)、面向切面编程和模板工具。在举例的过程当中,咱们稍微提到一点如何使用XML配置文件定义bean和AOP相关的对象,可是这些配置文件的加载原理是怎样的?这就须要研究下Spring的容器,Spring中所定义的bean都由Spring容器管理。
基于Spring框架构建的应用中的对象,都由Spring容器(container)管理,以下图所示。Spring容器负责建立对象、编织对象和配置对象,负责对象的整个生命周期。
容器是Spring框架的核心,经过依赖注入(DI)管理构成Spring应用的组件。正是由于有容器管理各个组件之间的协做关系,使得每一个Spring组件都很好理解、便于复用和单元测试。
Spring容器有多种实现,能够分为两类:
Spring提供了多种application context,可列举以下:
经过应用上下文实例,能够经过getBean()方法得到对应的bean。
在传统的Java应用中,一个对象的生命周期很是简单:经过new建立一个对象,而后该对象就可使用,当这个对象再也不使用时,由Java垃圾回收机制进行处理和回收。
在Spring应用中,bean的生命周期的控制更加精细。Spring提供了不少节点供开发人员定制某个bean的建立过程,掌握这些节点如何使用很是重要。Spring中bean的生命周期以下图所示:
能够看出,bean factory负责bean建立的最初四步,而后移交给应用上下文作后续建立过程:
本节主要总结了如何启动Spring容器,以及Spring应用中bean的生命周期。
除了Spring的核心模块,Spring还提供了其余的工具组件,这些组件扩展了Spring的功能,例如webservice、REST、mobile和NOSQL,造成了丰富的开发生态。
Spring 4.0you 20个独立的模块,每一个包含三个文件:二进制库、源文件和文档,完整的库列表以下图所示:
按照功能划分,这些模块能够分红六组,以下图所示:
这些模块几乎能够知足全部企业级应用开发的需求,可是开发人员并不须要彻底使用Spring的这些模块,能够自由选择符合项目需求的第三方模块——Spring为一些第三方模块提供了交互接口。
Spring框架的核心模块,其余全部模块都基于该模块构建。Spring容器负责管理Spring应用中bean的建立、配置和管理。在这模块中有Spring bean factory,该接口提供了最基本的依赖注入(DI)功能;基于bean factory,该模块提供了集中Spring应用上下文的实现,能够供开发人员选择。
除了bean factory和application context,该模块还支持其余企业级服务,例如email、JNDI access、EJB integration和scheduling。
Spring框架经过AOP模块提供面向切面编程的能力。经过AOP模块,一些系统层面的需求(事务、安全)能够与它们真正要做用到的模块相互解耦合。
Spring的JDBC和data-access object模块将数据库操做的一些样板式代码封装起来,免去了开发人员的不少工做量。这个模块还对数据库层的异常进行了封装,并向上提供含义更丰富的异常信息。
Spring并未实现本身的ORM框架,可是它提供了跟其余几个ORM框架整合的能力,例如Hibernate、Mybatis、Java Persistence AP等等,并且这些ORM框架都支持使用Spring提供的事务管理模块。
Spring提供了本身的 WEB开发框架——Spring MVC,除此以外,这个模块还提供远程调用支持:Remote Method Invocation(RMI)、Hessian、Burlap和JAX-WS。
不常使用
能够与经常使用的JUNIT、Mockito、Spock等测试框架整合使用。
若是只是学习Spring的核心模块,将会错过很多Spring社区提供的经典项目,下面介绍的这些项目使得Spring几乎能够覆盖整个Java开发(PS:带*的项目值得每位Spring用户仔细学习)。
基于Spring MVC框架拓展,利用该框架能够构建流式web应用。
虽然核心的Spring 框架提供了将Spring Bean 以声明的方式发布为Web Service,可是这些服务基于一个具备争议性的架构(拙劣的契约置后模型)之上而构建的。这些服务的契约由Bean 的接口来决定。 Spring Web Service 提供了契约优先的Web Service模型,服务的实现都是为了知足服务的契约而编写的。
安全对于许多应用都是一个很是关键的切面。利用Spring AOP,Spring Security为Spring 应用提供了声明式的安全机制。咱们将在第9 章讲解如何为应用添加SpringSecurity。你能够在主页http://static.springsource.org/spring-security/site 得到关于SpringSecurity 更多的信息。
许多企业级应用都须要与其余应用进行交互。Spring Integration 提供了几种通用的应用集成模式的Spring 声明式风格的实现。
咱们不会在本书覆盖Spring Integration 内容,可是若是你想了解更多关于SpringIntegration 的信息, 我推荐Mark Fisher、Jonas Partner、Marius Bogoevici 和IweinFuld 编写的《Spring Integration in Action》;或者还能够访问Spring Integration 的主页http://www.springsource.org/spring-integration。
当咱们须要对数据进行大量操做时,没有任何技术能够比批处理更能胜任此场景的。若是须要开发一个批处理应用,你能够借助于Spring 强大的面向POJO 的编程模型来使用Spring Batch 来实现。
Spring Batch 超出了本书的范畴,可是你能够阅读Thierry Templier 和Arnaud Cogoluègnes编写的《Spring Batch in Action》,或者访问Spring Batch 的主页http://static.springsource.org/spring-batch。
Spring Data用于简化数据库相关的开发工做。尽管多年以来关系型数据库都是企业级应用开发的主流,可是随着移动互联网的发展,对NoSQL这类菲关系型数据库的需求也愈来愈强。
不管你选择NoSQL仍是关系型数据库,Spring Datat都能提供简洁的编程模型,例如很是方便的repository机制,能够为开发人员自动建立具体的SQL实现。
社交网络是互联网冉冉升起的一颗新星,愈来愈多的应用正在融入社交网络网站,例如Facebook 或者Twitter。若是对此感兴趣,你能够了解下Spring Social,Spring 的一个社交网络扩展模块。
Spring Social 相对还比较新颖,我并无计划将它放入本书,可是你能够访问http://www.springsource.org/spring-social 了解Spring Social 更多的相关信息。
移动应用是另外一个引人瞩目的软件开发领域。智能手机和平板设备已成为许多用户首选的客户端。Spring Mobile 是Spring 新的扩展模块用于支持移动Web 应用开发。
与Spring Mobile 相关的是Spring Android 项目。这个新项目旨在经过Spring 框架为开发基于Android 设备的本地应用提供某些简单的支持。最初,这个项目提供了Spring 的RestTemplate 版本(请查看第11 章了解RestTemplete)能够用于Android 应用。
再次声明,这两个项目已超出了本书的范围,可是若是你对这两个项目感兴趣,能够访问http://www.springsource.org/spring-mobile 和http://www.springsource.org/spring-android 了解更多相关的信息。
Spring Boot是Spring社区中发展速度最快的框架之一,它旨在简化Spring的使用,解决Spring开发时遇到的“配置地狱”问题。
Spring Boot经过大量使用自动配置技术,能够取消大量的XML配置文件,同时该框架提出了starter的概念,用于简化pom文件。能够参考个人一系列博文:《Spring Boot Cookbook》阅读笔记
主要总结下Spring社区的趋势:
曾樑
哇塞,新年第一天就搞起了
水底鱼
最近在学习spring,写的真专业~谢谢!
小寞_TT
更糟的是,给DamselRescuingKnight写单元测试很不方便,在这个测试中,你必须确认:当调用knight的emarkOnQuest函数时,quest的embark函数也正确调用,但这并不容易。
为何写单元测试不方便?为何调用knight的emarkOnQuest函数时,quest的embark函数也正确调用是不容易的?有解答吗