第1章 Spring之旅

说明

  • 1、本文抄写了《Spring 实战》重点内容,参考了GitHub上的代码
  • 2、每个人的学习方式不一样,但目的是一样的,活学活用。最近一直在听《我们不一样》
  • 3、本文只为记录作为以后参考,要想真正领悟Spring的强大,请看原书。
  • 4、代码和笔记在这里GitHub,对你有帮助的话,欢迎点赞。

本章内容:

  • Spring的bean容器
  • 介绍Spring的核心模块
  • 更为强大的Spring生态系统
  • Spring的新功能

对于Java程序员来说,这是一个很好的时代..........

Spring是Java历史中很重要的组成部分。

在诞生之初,创建Spring的主要目的是用来代替更加重量级的企业级Java技术,尤其是EJB。相对于EJB来说,Spring提供了 更加轻量级和简单的编程模型.它增强了简单老式的Java对象(Plain Old Java Object POJO)的功能,使其具备了之前只有EJB和其他企业级Java规范才具有的功能。

尽管J2EE能够赶上Spring的步伐,但Spring也没有停止前进(我们程序员也一样,根本停不下来...),Spring继续在其他领域发展,移动开发、社交API集成、NoSQL数据库、云计算以及大数据都是Spring正在涉足和创新的领域。Spring的前景会更加美好(Java也是,Java9的模块化,只是我们需要学习的还有很多).

对于Java开发者来说,这是一个很好的时代

1.1简化Java开发

Spring是一个开源框架,最早由Rod Johnson创建,Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事,但Spring不仅仅局限于服务器端的开发,任何Java应用都能在简单性、可测试性、和松耦合等方面从Spring中受益。

一个Sping组件可以是任何形式的POJO。所有的理念都可以追溯到Spring最恨本的使命上:简化Java开发。

为了降低Java开发的复杂性,Spring采取了以下4种关键策略:

  • 基于POJO的轻量级和最小入侵性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和惯例进行声明式编程:
  • 通过切面和模板减少样板代码;

1.1.1 激发POJO的潜力

很多框架通过强迫应用继承他们的类或实现它们的接口从而导致应用于框架绑死。

Spring避免因自身的API而弄乱你的应用代码。Spring不会强迫你实现Spring所规范的接口或继承Spring所规范的类,相反,在基于Spring的构建的应用中,它的类通常没有任何迹象表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。

package com.guo.springpublic class HelloWordBean {public String sayHello() {return "Hello World"}
}

可以看到,这是一个简单普通的Java类——POJO。没有任何地方表明它是一个Spring组件。Spring的非侵入式编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。

尽管简单,但POJO一样可以拥有魔力,Spring赋予POJO魔力的方式之一就是通过DI来装配它们。

1.1.2 依赖注入

依赖注入现在已经演变成一项复杂的编程技巧或设计模式的理念

任何一个有实际意义的应用都会由两个或更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。按照传统的做法,每个对象负责管理与自己相互协作的对象的引用(即它所依赖的对象),这就会导致高度耦合和难以测试的代码。

/*** Created by guo on 20/2/2018.* damsel表示:少女*/
public class DamselRescuingKnight implements Knight {private RescueDamselQuest quest;public DamselRescuingKnight ( RescueDamselQuest quest) {//与RescueDamselQuest紧耦合this.quest = new RescueDamselQuest();}@Overridepublic void embarkOnQuest() {quest.embark();}
}

DamselRescueingKnight在它的构造函数中自行创建了RescueDamselQuest。这使得两者紧紧的耦合在一起。因此极大的限制了骑士执行探险的能力。在这样一个测试中 ,你必须保证当骑士embarkOnQuest方法被调用的时候,探险embark方法也要被调用。但是没有一个简单明了的方式能够测试。

耦合具有两面性:

  • 紧密耦合的代码难以岑氏,难以复用,难以理解,并且在典型的表现出"打地鼠"式的BUG特性,(修复一个bug,将会出现一个或更多的bug).
  • 一定的程度耦合又是必须的,完全没有耦合的代码什么都做不了。为了完成更有实际意义的功能,不同的类必须以适当的方式进行交互,总而言之,耦合是必须的,但需要谨慎对待

通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候设定,对象无需自行创建或管理他们的依赖关系,依赖关系将被自动注入到需要它们的对象中。

依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖

BraveKnight足够灵活可以接受任何赋予他的探险任务。

/*** Created by guo on 20/2/2018.*/
public class BraveKnight implements Knight {public Quest quest;public BraveKnight(Quest quest) {          //Quest被注入进来this.quest = quest;}@Overridepublic void embarkOnQuest() {quest.embark();}
}

不同于之前的DamselRescuingKnight,BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造参数传入。这是依赖注入的方式之一,即构造注入(constructor injection).

需要注意的是,传入的探险类型是一个Quest,也就是所有的探险任务都必须实现的一个接口。所以BraveKnight能够响应RescueDamselQuest、SlayDragonQuest、MakeRoundTableRounderQuesst 等任意的Quest实现。

这里的要点是BraveKnight没有有特定的Quest实现发生耦合。对他来说,被要求挑战的探险任务只要实现了Quest接口,那么具体的是那种类型就无关紧要了。这就是DI带来最大的收益——松耦合。**如果一个对象只通过接口(而不是具体的实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不情况的情况下,用不同的具体实例进行替换。

对依赖进行替换的一个最常用方法就是在测试的时候使用mock实现。


import static org.mockito.Mockito.*;public class BraveKnightTest {@Testpublic void knightShouldEmbarkQuest() {Quest mockQuest = mock(Quest.class);               //创建mock QuestBraveKnight knight = new BraveKnight(mockQuest);   //注入mock Quest;knight.embarkOnQuest();verify(mockQuest,times(1)).embark();}
}

可以通过mock框架Mockito去创建一个Quest接口的mock实现。通过这个mock对象,就可以创建一个新的BraveKnight实例,并通过构造器注入到这个mock Quest。当调用embarkOnQUest方法时,你可以要求Mockito框架验证Quest的mock实现的embark方法仅仅被调用了一次。

将Quest注入到Knight中

希望BraveKnight所进行的探险任务是杀死一只怪龙,

public class SlayDragonQuest implements Quest {private PrintStream stream;public SlayDragonQuest(PrintStream stream) {this.stream = stream;}@Overridepublic void embark() {stream.println("Embarking on quest to slay the dragon!!,顺便还可以学英语,一举两得。");}

SlayDragonQuest实现类Quest接口,这样它就适合注入到BraveKnight中了,与其他入门不同的是,SlayDragonQuest没有使用System.out.println();,而是在构造方法中请求一个更为通用的PrintStream。

创建应用组件之间协作的行为成为装配。Spring有多种装配Bean的方式,采用XML是一种常用的方式。
knights.xml,该文件将BraveKnight,SlayDragonQuest和PrintStream装配到一起。

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="knight" class="guo.knights.BraveKnight"><constructor-arg ref="quest"/>                        <!--注入Quest bean--></bean><bean id="quest" class="guo.knights.SlayDragonQuest">     <!--创建SlayDragonQuest--><constructor-arg value="#{T(System).out}"/></bean>
</beans>

在这里,BraveKnight和SlayDragonQuest被声明为Spring中的bean。就BraveKnight bean来讲,他在构造时传入对SlayDragonQuest bean的引用,将其作为构造器参数。同时,SlayDragonQuest bean 的声明使用了Spring表达式语言(Spring Expression Language),将System.out(一个PrintStream)传入到了SlayDragonQuest的构造器中,

在SpEL中, 使用T()运算符会调用类作用域的方法和常量. 例如, 在SpEL中使用Java的Math类, 我们可以像下面的示例这样使用T()运算符:

T(java.lang.Math)

T()运算符的结果会返回一个java.lang.Math类对象.

Spring提供了基于Java的配置可作为XML的替代方案。


import guo.knights.BraveKnight;
import guo.knights.Knight;
import guo.knights.Quest;
import guo.knights.SlayDragonQuest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Created by guo on 20/2/2018.*/
@Configuration
public class KnightConfig {@Beanpublic Knight knight() {return new BraveKnight(quest());}@Beanpublic Quest quest() {return new SlayDragonQuest(System.out);}
}

不管使用的是基于XML的配置还是基于Java的配置,DI所带来的收益都是相同的。尽管BraveKnight依赖于Quest,但是它并不知道传递给它的是什么类型的Quest,与之类似,SlayDragonQuest依赖于PrintStream,但是编译时,并不知道PrintStream长啥样子。只有Spring通过他的配置,能够了解这些组成部分是如何装配起来的。这样就可以在不改变 所依赖的类的情况下,修改依赖关系。

**接下来,我们只需要装载XML配置文件,并把应用启动起来。

Spring通过应用上下文(Application context) 装载bean的定义,并把它们组装起来。Spring应用上下文全权负责对象的创建个组装,Spring自带了多种应用上下文的实现,他们之间的主要区别仅仅在于如何加载配置。

因为knights.xml中的bean是使用XML文件进行配置的,所以选择ClassPathXmlApplicationContext作为应用上下文相对是比较合适的。该类加载位于应用程序类路径下的一个或多个Xml配置文件。

public class KnightMain {public static void main(String[] args) {ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring/knights.xml");    //加载Sprinig应用上下文Knight knight = context.getBean(Knight.class);                                       //获取knight beanknight.embarkOnQuest();                                                              //使用knight调用方法context.close();                                                                     //关闭应用上下文}
}输出如下:
Embarking on quest to slay the dragon!!,顺便还可以学英语,一举两得。

这里的main()方法基于knight.xml文件创建了spring应用上下文。随后他调用该应用上下文获取一个ID为knighht的bean。得到Knighht对象的引用后,只需要简单调用embarkOnQuest方法就可以执行所赋予的探险任务了。只有knights.xml知道哪个骑士执行力那种任务。

1.1.3 应用切面

DI能够让相互协作的软件组件保持松耦合,而面向切面编程(aspect-oriented programming AOP) 允许你把遍布应用各处的功能分离出来形成可重用的组件。

面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术,系统由许多不同的组件组成,每个组件各负责一特定的功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理、和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中。这些系统通常被称为横切关注点。,因此他们会跨越系统多个组件。

如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性。

  • 实现系统关注点功能的代码嫁给你会重复出现在多个组件中。这意味着如果你要改变这些关注点的逻辑,必须修噶各个模块中相关的实现。即使你把这些关注点抽象成一个独立的模块,其他模块只是调用方法。但方法的调用还是会重复出现在各个模块中。
  • 组件会因为那些与自身核心业务无关的代码而变得混乱。一个向地址薄增加地址条目的方法应该只关注如何添加地址。而不应该关注它是不是安全的,或者是否需要事务的支持。

AOP能使这些服务模块化,并以声明的方式将它们应用到它们要影响的组件中去。所造成的结果就是这些组件会具有哥哥你好的内聚性并且会更加关注自身的业务,安全不需要了解涉及系统服务所带来的复杂性。总之AOP能确保POJO的简单性。

我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是由哪些实现各自业务功能模块组成的,借助AOP,可以使用各种功能层去包裹核心业务层,,这些层以声明的方式灵活的应用到系统中,你的核心应用甚至根本不知道他们的存在。这是一个非常强大的理念,可以将安全,事务,日志关注点与核心业务相分离。**

每一个人都熟知骑士所做的任何事情,这是因为咏游诗人用诗歌记载了骑士的事迹并将其进行传唱。假设我们需要使用咏游诗人这个服务类来记载骑士的所有事迹。

咏游诗人是中世界的音乐记录器

/*** Created by guo on 20/2/2018.* 咏游诗人,作为骑士的一个切面*/
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 brabe");      //探险之前调用}public void singAfterQuest() {stream.println("Tee hee hhe,the brave knight " + "did embark on a quest");   //探险之后调用}
}

Minstrel只有两个简单的方法的类,在骑士执行每一个探险任务之前,singBeforeQuest()被调用;在骑士完成探险任务之后,singAfterQuest()方法被调用。在这两种情况下,Minstrel都会通过一个PrintStream类来歌颂骑士的事迹,这个类通过构造器注入进来。

但利用AOP,你可以声明咏游诗人西部歌颂骑士的 探险事迹,而骑士本身不直接访问Minstrel的方法

要将Minstrel抽象为一个切面,你所需要做的事情就是在一个Spring配置文件中声明它,,

<bean id="minstrel" class="guo.knights.Minstrel"><constructor-arg value="#{T(System).out}"/>                                 <!--声明Minstrel bean-->
</bean><aop:config><aop:aspect ref="minstrel"><aop:pointcut id="embark" expression="execution(* * .embarkOnQuest(..))"/>     <!--定义切点--><aop:after pointcut-ref="embark" method="singBeforeQuest"/>                  <!-- 声明前置通知--><aop:after pointcut-ref="embark" method="singAfterQuest"/>                    <!-- 声明后置通知--></aop:aspect>
</aop:config>

这里使用了Spring的aop配置命名空间把Minstrel声明为一个切面。

在这两种方式中,pointcut-ref属性都引用列名为为“embark”的切入点,该切入点实在前面的<poiontcut>元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用的是aspectJ的切点表达式语言。

Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用,其次最重要的是Minstrel可以被应用到BraveKnight中,而BraveKnight不需要显示的调用它,实际上,BraveKnight完全不知道MInstrel的存在

public class KnightAopMain {public static void main(String[] args) {ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("spring/minstrel-AOP.xml");Knight knight = context.getBean(Knight.class);//Knight knight = (Knight) context.getBean("knight");knight.embarkOnQuest();context.close();}
}输出如下:
Fa la la ,the Knight is so brabe
Embarking on quest to slay the dragon!!,顺便还可以学英语,一举两得。
Tee hee hhe,the brave knight did embark on a quest

1.1.4 小节

作者已经为我们展示了Spring通过面向POJO编程、DI、切面、模板技术来简化Java开发中的复杂性。在这个工程中,展示了基于XML的配置文件中如何配置bean和切面,但这些文件是如何加载的呢?他们被加载到哪里呢?接下来让我们了解下Spring容器,这是应用中的所有bean所驻留的地方。

Spring之旅第一站(不得不佩服老外...)相关推荐

  1. Javascript之旅——第一站:从变量说起

    原文出处: 一线码农的博客   欢迎分享原创到伯乐头条 工作这几年,js学的不是很好,正好周末有些闲时间,索性买本<js权威指南>,大名鼎鼎的犀牛书,好好的把js深入的看一看.买过这本书的 ...

  2. asp.net mvc 之旅—— 第一站 从简单的razor入手

    记得2011年mvc3刚出来的时候,我们就有幸将 mvc3 用在我们团购项目上,当时老大让我们用一个星期时间来熟悉mvc,幸好园子里面的老朋友DR 正在写mvc3系列,也恭喜这个系列文章被整理成专题供 ...

  3. Python的学习之旅第一站:基本语法

    初识Python Python是一门简单易学的解释型语言.对于什么是解释型语言,在C语言的第一章可以学习到.感谢大家的支持和鼓励.点个赞,评论和收藏.谢谢啦!我也是初学者,大家一起努力吧,写博客记录自 ...

  4. Sql Server之旅——第一站 那些给我们带来福利的系统视图

    本来想这个系列写点什么好呢,后来想想大家作为程序员,用的最多的莫过于数据库了,但是事实上很多像我这样工作在一线的码农,对sql 都一知半解,别谈优化和对数据库底层的认识了,我也是这样... 一:那些系 ...

  5. DRP之旅第一站--DRP概览

    进入了DRP的项目,相对于以前的知识而言DRP的概念比较陌生,是个新的概念,面对新的概念和知识我们应该如何去跟它打交道. 学习一个新知识应该从哪些角度进行思考: 这个知识概念是什么? 什么是DRP?: ...

  6. HTML(卓音前端之旅第一站)

    HTML相关知识 一.html的语法规范(标签) 特点: 标签关系: 二.基本结构标签(==骨架标签==) 三.常用标签 1. 标题标签 2.==段落和换行标签== 1.段落: 2.换行 3.文本格式 ...

  7. spring in action学习-第一章 spring之旅

    首先我先吐槽一下这本书的封面图,我能理解成一个包着头巾的男人举着个水壶昂首挺胸,最后给你个眼神....开玩笑的这幅插图是约旦西南部卡拉克省的居民,那里的山顶有座城堡,对死海和平原有极佳的视野,这幅图出 ...

  8. spring之旅第四篇-注解配置详解

    spring之旅第四篇-注解配置详解 一.引言 最近因为找工作,导致很长时间没有更新,找工作的时候你会明白浪费的时间后面都是要还的,现在的每一点努力,将来也会给你回报的,但行好事,莫问前程!努力总不会 ...

  9. 2015生命之旅---第一站重庆

    老邓的流水账又来了.... 这篇文章来源于我的2015年7月-九月的一系列出行,跟程序无关,我只想把我的经历告诉大家,让大家与我共勉.为什么会有这次旅行,为什么是生命之旅,这还得从去年说起,去年九月, ...

最新文章

  1. 在VMware ESX Server使用华为存储
  2. 【RS】Improving Implicit Recommender Systems with View Data - 使用浏览数据提升隐式推荐系统...
  3. 如何跟项目经理和开发人员反馈安全测试报告的问题
  4. 工作152:阅读之后台管理登录样式
  5. [wxWidget系列] wxWidget的事件机制
  6. 单点登录相关问题总结
  7. 你看得懂的CSMA介质访问控制原理
  8. 记录点滴——2020年终总结
  9. js生成二维码附带logo
  10. CAD图纸转换成高质量的彩色PDF格式如何操作?
  11. 通信原理;利用MATLAB实现AMI/HDB3编译码
  12. 游戏软件性能测试报告,游戏性能测试及总结
  13. 墨刀和axure你应该用哪个?
  14. jdk和tomcat的关系
  15. 面向对象:兜兜转转了很久,希望你恰好也在
  16. 第27天 LDT与库
  17. 推荐两个适合程序员接国外私单的网站
  18. python爬虫— 利用js2xml 获取 script 数据
  19. Java中构造方法的案例及常用类int lenght类的使用方法举例,new实例化对象方法,静态方法实例方法的举例
  20. bat批量修改文件后缀

热门文章

  1. node.js 详解
  2. 12 -- OpenCV学习—边缘检测
  3. android自定义u形线,Android实战之自定义View折线图
  4. android animate xml,Android Animate旋轉
  5. 把Unity包体构建到steam的后台
  6. 怎么录屏幕视频,不要错过这4种简单的电脑录屏方法
  7. 690. 员工的重要性
  8. 创业家:最危险七团购网 高朋团宝窝窝在列
  9. 基于vue的动态表单自助建站工具
  10. Kubernetes首个严重安全漏洞发现者,谈发现过程及原理机制