《Spring实战 第三版》一

第一章《Spring之旅》

1.几个关键术语

POJO

"Plain Ordinary Java Object",简单普通的java对象。
所谓对象,即为类的实例化。那么什么样的类实例化后被称为POJO呢?
指的是这些类:每个属性使用private修饰,每个属性都有setter和getter方法
没有从任何类继承(Object除外),也没有实现任何借口

JavaBean

1996年12月,Sun公司发布了JavaBean 1.00-A规范。
JavaBean规范针对Java定义了软件组件模型
这个规范规定了一整套编码策略,使简单的Java对象不仅可以被重用
而且还可以轻松地构建更复杂的应用
该规范内容为:所有属性为private;必须有一个公共的缺省构造函数,即需要提供无参的构造函数
类的属性使用getter和setter来访问
实现serializable接口,即使该类可序列化。

EJB

1998年3月,Sun发布了EJB1.0规范,该规范把Java组件的设计理念延伸到了服务器端
并提供了许多企业必须的企业级服务
但它不再像JavaBean规范那么简单,事实上除了名字,EJB已经和JavaBean没有任何关系了
尽管现实中有很多成功的系统是基于EJB构建的
但EJB没有实现它的最初设想:简化企业级应用开发

2.Spring

Spring框架已经成为基于POJO的轻量级开发框架的领导者
Spring是一个开源框架,由Rod Johnson创建
它是为了解决企业级应用开发的复杂性而创建的
使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情
总之,Spring最根本的使命就是:简化开发
为了降低Java开发的复杂性,Spring采用了一下4种关键策略:

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

2.依赖注入

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

public class DamselRescuingKnight implements Knight()
{
    private RescueDamselQuest quest;
    public DamselRescuingKnight()
    {
        //DamselRescuingKnight与RescueDamselQuest紧密耦合在一起
        quest = new RescueDamselQuest();
    }
    public void embarkOnQuest() throws QuestException
    {
        quest.embark();
    }
}

从上面的的代码可以看到:
DamselRescuingKnight在它的构造函数中自行创建了RescueDamselQuest
这使得RescueDamselQuest紧密地与RescueDamselQuest耦合到了一起
极大地限制了骑士执行探险的能力
换句话说,这个骑士只能用来救援少女,而其它的什么也做不了

耦合具有两面性:一方面,紧密耦合的代码难以测试,难以复用,难以理解
另一方面,一定程度的耦合又是必须的——完全不耦合的代码什么也做不了

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

public class BraveKnight implements Knight
{
    private Quest quest;
    public BraveKnight(Quest quest)
    {
        this.quest = quest;//Quest被注入进来,Quest是一个接口
    }
    public void embarkOnQuest() thorws QuestException
    {
        quest.embark();
    }
}

从上面的代码可以看到:不同于之前的DamselRescuingKnight
BraveKnight没有自行创建探险任务
而是在构造时把探险任务作为构造函数的参数传入。这是依赖注入的方式之一,即构造函数注入
BraveKnight没有与任何特定的Quest实现发生耦合
对它来说,被要求挑战的探险任务只要实现了Quest接口,那么具体是哪一类型的探险就无关紧要了
这就是依赖注入最大的好处——松耦合

3.装配

现在BraveKnight类可以接受你传递给它的任意一种Quest实现,但我们如何把特定的Quest传给它呢?
创建应用组件之间协作的行为同城称为装配。
Spring有多种装备Bean的方式,采用XL配置通常是最常见的装配方式

<?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="BraveKnight">
    <constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="SlayDragonQuest"/>
</beans>

该配置文件让BraveKnight接受了一个SlayDragonQuest任务

4.应用上下文

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

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class KnightMain
{
    public static void main(String[] args)
    {
        //获取应用上下文
        ApplicationContext context = new ClassPathApplicationContext("beans.xml");
        //从应用上下文中获取对象
        BraveKnight knight  = (BraveKnight) context.getBean("knight");
        //调用对象的方法
        knight.embarkOnquest();
    }
}

书上代码复现见github

5.应用切面

依赖注入可以相互协作的软件组件保持松散耦合
AOP(Aspect Oriented Programming)编程允许你把遍布应用各处的功能分离出来形成可重用的组件

为了示范在Spring中如何应用切面,让我们重新回到骑士的例子,并为它添加一个切面
假设使用吟游诗人这个服务类来记载骑士的所有事迹

public class Minstrel {
    public void singBeforeQuest(){  //探险值之前调用
        System.out.println("Fa la la,the knight is so brave");
    }
    public void singAfterQuest(){   //探险之后调用
        System.out.println("Tee hee he;the brave knight did embark on a quest");
    }
}

在骑士执行每一个任务之前,singBeforeQuest方法被调用
在骑士执行完一个任务之后,singAfterQuest方法被调用
于是我们对BraveKnight进行调整

public class BraveKnight implements Knight{
    private Quest quest;
    private Minstrel minstrel;
    public BraveKnight(Quest quest,Minstrel minstrel){
        this.quest = quest;
        this.minstrel = minstrel;
    }
    public void embarkOnQuest(){
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

这样就可以达到预期效果,当然需要对beans.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="BraveKnight">
    <constructor-arg ref="quest" />
    <constructor-arg ref="minstrel"/>
</bean>
<bean id="quest" class="SlayDragonQuest"/>
<bean id="minstrel" class="Minstrel"/>
</beans>

但是仔细想想,又不太对(这里指的是业务逻辑上的问题)
管理吟游诗人是骑士的责任吗?
在作者看来,吟游诗人应该做他份内的事,根本不需要骑士命令他这么做
毕竟,用诗歌记载骑士的探险事迹是吟游诗人的职责,为什么还需要骑士提醒吟游诗人去做他份内的事情呢?
此外,因为骑士需要知道吟游诗人,你就必须把引诱诗人注入到BraveKnight类中
这不仅使得BraveKnight的代码复杂化了,而且如果我们还需要一个不需要吟游诗人的骑士呢?
若是如此,还需要引入空值校验逻辑,这样一来不就使得代码越来越复杂了吗

但还好,Spring提供了AOP,利用AOP,可以声明吟游诗人必须歌颂骑士的探险事迹
而骑士就不再直接访问吟游诗人的方法了,即BraveKnight不再需要注入Minstrel
把Minstrel抽象为一个切面,只需要在Spring配置文件中声明它

<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="knight" class="BraveKnight">
    <constructor-arg ref="quest" />
</bean>
<bean id="quest" class="SlayDragonQuest"/>
<bean id="minstrel" class="Minstrel"/>
<aop:config>
    <!--定义切面-->
    <aop:aspect ref="minstrel">
        <!--定义切入点-->
        <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
        <!--引用切入点 定义前置通知-->
        <aop:before pointcut-ref="embark" method="singBeforeQuest"/>
        <!--引用切入点 定义后置通知-->
        <aop:after pointcut-ref="embark" method="singAfterQuest"/>
    </aop:aspect>
</aop:config>
</beans>

这里使用了Sprin的AOP配置的命名空间把Minstrel Bean声明为一个切面

至此,Minstrel可以向BraveKnight提供服务,但BraveKnight完全不知道Minstrel的存在
而且Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用
这就是Spring Aop的神奇所在
关于书中BraveKnight代码复现,请见github

6.使用模板样式消除样板代码

Java的API会导致样板式的代码,如JDBC就是这样一个例子
Spring旨在通过模板封装来消除样板式代码,Spring的JdbcTemplate使得在执行数据库连接时
避免传统的JDBC样板式代码成为了可能

猜你喜欢

转载自www.cnblogs.com/ASE265/p/12391086.html