《Spring实战》第三版笔记

这是一篇《Spring实战》第三版的读书笔记。此书已经到了第四版了,看第三版确实已经有些out了。但对于一个对Spring不怎么熟悉的开发者,看看中文版以快速入门还是OK的。从周四晚上到周日傍晚,大约历经十多个小时的阅读,整理了一些笔记,仅供自己查看吧,因为里面省略了不少自己不感兴趣的内容或者是认为暂时没有必要深究的细节。所以主观的部分比较多。本笔记目前主要包括第1至7章,以及第11章。以下为笔记内容。


第一部分 Spring的核心

第一章 Spring之旅

Spring的主要特性是 依赖注入(DI)和面向切面编程(AOP)。

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

Spring不会强迫你实现它的接口或继承它的类。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO.

依赖注入的方式之一:构造器注入
没有依赖注入的类,将其他类的实例作为自己的成员变量,并且直接在自己的构造函数中new;
具有依赖注入的类,将接口作为自己的成员变量,将该接口的实现作为自己构造函数的参数,在构造函数内赋值给自己的接口成员变量。

装配:创建应用组件之间协作的行为。
Spring有多种装配方式,其中之一是用XML. 
<bean id="knight" class="xxx.xxx.xxx">
  <constructor-arg ref="quest" />
</bean>
<bean id="quest" class="yyy.yyy.yyy" />  // yyy.yyy.yyy是一个具体的Quest接口的实现类

Spring通过应用上下文(ApplicationContext)装载Bean的定义并把它们组装起来,如ClassPathXmlApplicationContext. 

AOP编程:将遍布应用各处的功能分离出来形成可重用的组件。
AOP类似于装饰器的概念,但是是实现在XML文件中的。

Spring的JdbcTemplate使得在执行数据库操作时,避免传统的JDBC样板式代码成为可能。

---------------------------
Spring容器:
在基于Spring的应用中,应用对象生存于Spring容器中。Spring容器创建对象、装配它们、配置它们管理它们的生命周期。 
应用上下文(ApplicationContext)就是最常用的容器。

应用上下文,最常见的有3种:
ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext

---------------------------
Spring框架由6个定义明确的模块组成:
- Data access & Integration (JDBC, Transaction, ORM, JMS, OXM)
- Web & Remoting (Struts, Servlet, Web, ...)
- AOP (AOP, Aspects)
- Instrument (Instrument, Instrument Tomcat)
- Core Spring Container (Beans, Core, Context, Expression, Context Support)
- Testing (Test)

-----------------------------------------------
第二章 装配Bean

Spring核心框架自带了10个namespace,如表2.1所示:
aop, beans, context, jee, jms, lang, mvc, oxm, tx, util

声明Bean: 
<bean id="duke" class="xx.xx.xx" />

构造器注入:
<bean id="duke" class="xx.xx.xx" />
    <constructor-arg value="10" />
    <constructor-arg ref="another_bean_id" />
</bean>

对于构造方法是private的情况:
<bean id="stage" 
      class="xx.xx.xx" 
      factory-method="getInstance" />

所有Spring Bean都默认是单例。当容器分配一个Bean时(无论是通过装配还是调用容器的getBean()方法),它总是返回Bean的同一个实例。
为了让Spring在每次请求时都为Bean产生一个新的实例,只需配置Bean的scope属性为prototype即可。
<bean id="Ticket" class="xx.xx.xx" scope="prototype" />
其他各种作用域见表2.2

init-method
destroy-method

default-init-method
default-destroy-method

另外一种注入的方法 -- 使用<property>元素来配置Bean的属性
<bean id="aa" class="bb" >
  <property name="song" value="1234 song" />
</bean>
一旦此类被实例化,Spring就会调用指定属性的setter方法为该属性赋值。

内部Bean (inner Bean)
当不希望A类被除了B类以外的其他类使用时,将A类的Bean定义在B类的Bean的内部,比如<property>属性的定义内部,又如构造方法的入参。
注意:内部Bean没有id属性,因为没有太大必要。所以,内部Bean其实不能被重用。

使用namespace p来装配属性 和 使用<property> 是等价的,如: p:song = "1234 song"  等价于 <property name="song" value="1234 song" />

装配集合(List、Set、Map、Property)

装配null值
<property name="someNullProp"><null/></property>

--------
SpEL (Spring Expression Language):
在运行期进行装配

SpEL的特性:
- 使用Bean的ID来引用Bean
- 调用方法 和 访问对象的属性
- 对值进行运算
- 正则表达式匹配
- 集合操作

<property name="count" value="#{5}" />
#{} 标记表示这是一个SpEL表达式。
#{'a string'}, #{1e4}, #{88.9}, #{true}, #{false}

使用Bean的ID将一个Bean装配到另一个Bean的属性中:
<property name="instrument" value="#{saxphone}" />
以上等价于
<property name="instrument" ref="saxphone" />

一个例子:
<bean id="carl" class="xxx.xxx" 
    <property name="song" value="#{karl.song}" />
</bean>
等价于:
Instrumentlist carl = new Instrumentlist();
carl.setSong(kenny.getSong());

null-safe存取器:
用?.来代替.来访问方法。如果方法调用者为null值,则不会再去调用方法。

操作类
T(java.lang.Math)  返回Math类的对象
T() 真正的价值在于,通过该运算符可以访问指定类的静态方法和常量。
value="#{T(java.lang.Math).PI}"

多种运算符
比较值
逻辑表达式

SpEL通过matches运算符支持表达式中的模式匹配。

<util:list>元素用于配置List集合
使用[]访问集合成员:
value="#{city_list[2]}", value="#{cities['Shanghai']}"

查询集合成员:
一个查询运算符.?[]就可以做到: #{cities.[population gt 1e5]}

投影集合:
投影运算符.![]
#{cities.![name]} 将返回一个String类型的集合,是每个城市的名字

-----------------------------------------------------------------

第三章 最小化Spring XML的配置

Spring提供了几种技巧,帮助我们减少XML的配置数量。
- 自动装配(autowiring): 有助于减少甚至消除<property>元素和<constructor-arg>元素,让Spring自动识别如何装配Bean的依赖关系。
- 自动检测(autodiscovery): 让Spring自动识别哪些类需要被配制成Spring Bean,从而减少对<bean>元素的使用。

4种自动装配的策略:
- byName
- byType
- constructor
- autodetect:先尝试constructor,不行再尝试byType

- byName:A Bean的id 和 B Bean的某属性的名字一样的时候,通过配置autowire属性,就可以利用此信息自动装配B Bean的那个属性了。
          即:为属性自动装配ID与该属性的名字相同的Bean. 
          
- byType: 和byName类似,只是换成了检查属性的类型。
          当找到多个合适的Bean时,有歧义,抛出异常。

- constructor: 寻找符合该Bean某一个构造函数所有入参的Bean。
                如有多个Bean适合某一个构造参数,或者有多个构造函数都能找到Bean来填充它们的入参,这2种歧义都会抛出异常。
                

默认自动装配

混合使用自动装配和显式装配

使用注解装配

Spring 3 支持3种不同的用于自动装配的注解:
- Spring自带的 @Autowired 注解
- JSR-330的 @Inject 注解
- JSR-250的 @Resource 注解

使用@Autowired
可选的自动装配
限定歧义的依赖
创建自定义的限定器

借助@Inject实现基于标准的自动装配

在注解注入中使用表达式
@Value 配合 SpEL

自动检测Bean
<context:annotation-config> 有助于完全消除<property> 和 <constructor-arg> 元素,但仍需要<bean>. 
<context:component-scan> 除了完成与<context:annotation-config>一样的工作,
还允许Spring自动检测Bean和定义Bean,这意味着不使用<bean>元素。

<context:component-scan>会扫描指定的package和子package,并查找出能自动注册为Spring Bean的类。

默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,这些特殊注解如下:
- @Component  通用的构造型注解,标识该类为Spring组件;
- @Controller  标识该类定义为Spring MVC Controller
- @Respository 标识该类定义为数据仓库
- @Service  标识将该类定义为服务
- 使用@Component标注的任意自定义注解

当使用<context:component-scan>时,基于注解的自动扫描只是一种扫描策略。
<context:component-scan>还有其他扫描策略来查找候选Bean. 

过滤组件扫描
为<context:component-scan>配置<context:include-filter> 和/或者 <context:exclude-filter> 子元素,
我们可以随意挑战扫描行为。

使用Spring基于Java的配置
@Bean

----------------------------------------------

第四章 面向切面的编程

4.1 什么是AOP(面向切面编程)

继承和委托(Delegatio)是实现重用通用功能的面向对象技术。但是,它们都有缺陷。
切面(Aspect)是另一种选择。

AOP术语:
- 通知(advice)
- 切点(pointcut)
- 连接点(joinpoint)

通知:定义切面是什么,以及何时使用。
Sprint切面有5种类型的通知:
- Before 在方法被调用前通知
- After  在方法完成之后调用通知,无论方法是否执行成功
- After returninig  在方法成功执行之后
- After throwing    在方法抛出异常之后
- Around  通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为

连接点(Joinpoint):是在应用执行过程中能够插入切面的一个点。
这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。

切点(pointcut):一个切面并不需要通知应用的所有连接点;切点有助于缩小切面所通知的连接点的范围。
通知定义了切面的“什么”和“何时”,而切点定义了“何处”。

切面(Aspect):是通知和切点的结合。

引入(Introduction):允许我们向现有的类添加新方法和属性。

织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程。
切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入。
- 编译期:切面在目标类编译时被织入。
          这需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面。
- 类加载期:切面在目标类加载到JVM时被织入。
            这种方式需要特殊的类加载器(ClassLoader),它是增强该目标类的字节码。
            AspectJ 5的LTW(load-time weaving)就是支持此种方式织入切面。
- 运行期:切面在应用运行的某个时刻被织入。
          织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以此种方式织入切面。


Spring对AOP的支持
Spring提供了4种各具特色的AOP支持
- 基于代理的经典AOP
- @AspectJ注解驱动的切面
- 纯POJO切面
- 注入式AspectJ切面

前3种都是Spring基于代理的AOP变体。因此Spring对AOP的支持局限于方法拦截。
如果要实现构造器拦截或属性拦截,就要考虑在AspectJ里实现切面,利用Spring的DI把Spring Bean注入到AspectJ的切面中。

Spring的通知是Java编写的

Spring在运行期通知对象
直到应用需要被代理的Bean时,Spring才创建代理对象。
如果使用的是ApplicationContext,在其从BeanFactory中加载所有Bean时,Spring创建被代理的对象。

Spring只支持方法连接点
对AspectJ和Jboss,除了方法切点,它们还提供字段和构造器接入点。
Spring缺少对字段连接点的支持,也无法让我们创建细粒度的通知,比如拦截对象字段的修改;
Spring也不支持构造器连接点。

4.2 使用切点选择连接点

编写切点

execution(* com.springinaction.springidol.Instrument.play(..) 
          && within(com.springinaction.springidol.*))
* 表示返回任意类型
(..) 标识任意参数
within指示器限定切点范围
因为&在XML中有特殊含义,所以在基于XML的配置中,用and、or和not分别代替&&、||和!

使用Spring的bean()指示器
bean()使用Bean ID或Bean name作为参数来限制切点至匹配特定的Bean. 
execution(* com.springinaction.springidol.Instrument.play(..) 
          and bean(eddie))

4.3 在XML中声明切面

Spring的AOP配置元素简化了基于POJO切面的声明:
<aop:advisor>   定义AOP通知器
<aop:after>
<aop:after-returning>
<aop:after-throwing>
<aop:around>    定义AOP环绕通知
<aop:aspect>    定义切面
<aop:aspectj-autoproxy>  启用@AspectJ注解驱动的切面
<aop:before>
<aop:config>     顶层的AOP配置元素。大多数的<aop:*>元素必须包含在此元素内。
<aop:declare-parents>  为被通知的对象引入额外的接口,并透明地实现
<aop:pointcut>    定义切点

使用Spring的AOP配置元素声明一个audience切面:
<aop:config>
    <aop:aspect ref="audience">
        <aop:before pointcut="execution(* com.sia.si.Performer.perform(..))" 
                    method="turnOffCellPhones" />
        <aop:after-returning pointcut="execution(* com.sia.si.Performer.perform(..))"
                    method="applaud" />
    </aop:aspect>
</aop:config>

可以定义一个命名切点以消除冗余的切点定义
<aop:pointcut id="performance" 
              expression="execution(* com.sia.si.Performer.perform(..))" />
<aop:after-returning pointcut-ref="performance"
                     method="applaud" />

声明环绕通知 <aop:around>
使用环绕通知,可以完成前置通知和后置通知所实现的相同功能,但是只需要在一个方法中实现。
因为整个通知逻辑是在一个方法内实现的,所以不需要使用成员变量保存状态。比如,可以计算方法执行的时间。
public void watchPerformance(ProceedingJoinPoint joinpoint) {
    long start = System.currentTimeMillis();
    joinpoint.proceed();
    long end = System.currentTimeMillis();
}
ProceedingJoinPoint对象能让我们在通知里调用被通知的方法。

为通知传递参数 arg-names

通过切面引入新功能
利用被称为Introduction的AOP概念,切面可以为Spring Bean添加新的方法。
<aop:aspect>
    <aop:declare-parents
      types-matching="com.sia.si.Performer"
      implement-interface="com.sia.si.Contestant"
      default-impl="com.sia.si.GraciousContestant"
    />
</aop:aspect>
在以上例子中,凡符合Performer接口的那些Bean会实现Contestant接口。
这里通过defalut-impl属性指定Contestant的实现;或者亦可由delegate-ref属性来标识。

使用default-impl和delegate-ref的区别是,后者是Spring Bean,它可以被注入、被通知,或使用其他Spring的配置。

4.4 注解切面

使用注解来创建切面是AspectJ 5所引入的关键特性。
@Aspect, @Pointcut, @Before("pointcut_name()"), @AfterReturning("pointcut_name()"), @AfterThrowing("pointcut_name()")
<aop:aspectj-autoproxy />

注解环绕通知
@Around("pointcut_name()")
注意,被环绕通知的方法必须接受一个ProceedingJoinPoint对象作为方法入参,并在对象上调用proceed()方法。

标注引入
等价于<aop:declare-parents>的注解是@AspectJ的@DeclareParents. 

4.5 注入AspectJ切面
Spring通过aspectOf()工厂方法获得切面的引用,然后像<bean>元素规定的那样在该对象上执行依赖注入。


-----------------------------------------

第二部分 Spring应用程序的核心组件

第5章 征服数据库

Spring支持JDBC、Hibernate、Java持久化API(Java Persistent API)等。

5.1 Spring的数据访问哲学

DAO(Data Access Object)数据访问对象

为了实现将数据访问层与应用程序的其他部分隔离开来,Spring采用的一个方式就是 提供贯穿整个DAO框架的统一异常体系。

5.1.1 了解Spring的数据访问异常体系
Spring的平台无关持久化异常
Spring的数据访问异常都继承自DataAccessException,该类的特殊之处是它是一个非检查型异常。换句话说,没有必要捕获Spring所抛出的数据访问异常。
为了利用Spring的数据访问异常,就需要使用Spring所提供的数据访问模板。

5.1.2 数据访问模板化
Spring将数据访问过程中固定的和可变的部分明确划分为2个不同的类:模板(template)和回调(callback). 
模板管理过程中固定的部分,而回调处理自定义的数据访问代码。

针对不同的持久化平台,Spring提供了多个可选的模板。
如果直接使用JDBC,可以选择JdbcTemplate;
如果希望使用对象关系映射框架,可以使用Hibernate-Template或JpaTemplate。

尽管直接织入模板是不错的选择,但是Spring还提供了一系列便利的DAO基类,它们可以用于管理模板。

5.1.3 使用DAO支持类
数据访问模板并不是Spring数据访问框架的全部。每个模板提供了一些简便的方法,使得我们不必创建明确的回调实现,从而简化了数据访问。
另外,基于模板-回调设计,Spring提供了DAO支持类,而将业务自己的DAO类作为它的子类。下面是数者之间的关系。

应用程序DAO -(继承)-> DAO支持类 -> 数据访问模板 -> 持久化框架 -> 数据库

Spring所支持的大多数持久化功能都依赖于数据源。因此,在创建模板和DAO之前,需要在Spring中配置一个数据源以便DAO访问数据库。

5.2 配置数据源

Spring提供了在SPring Context中配置数据源Bean的3种方式:
- 通过JDBC驱动程序定义的数据源
- 通过JNDI查找的数据源;
- 连接池的数据源;

对于真正的生产环境,建议通过应用服务器的JNDI来获取连接池中的数据源。

5.2.1 使用JNDI数据源
可以像使用Spring Bean那样配置JNDI中的数据源的引用并将其装配到需要的类中。
位于jee namespace下的<jee:jndi-lookup>元素可以用于检索JNDI中的任何对象(包括数据源)并将其用于Spring Bean中。

5.2.2 使用数据源连接池
若不从JNDI中查找数据,第二个选择就是直接在Spring中配置数据源连接池。
Spring并没有提供数据源连接池的实现,但可以借助于其他项目,比如DBCP(Jakarta Commons Database Connection Pooling)。 

5.2.3 基于JDBC驱动的数据源
通过JDBC驱动定义数据源是最简单的配置方式。


5.3 在Spring中使用JDBC

5.3.1 应对失控的JDBC代码

5.3.2 使用JDBC模板
Spring为JDBC提供了3个模板类供使用。
- JdbcTemplate  支持最简单的数据库访问以及简单的索引参数查询
- NamedParamterJdbcTemplate  将查询值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数
- SimpleJdbcTemplate  利用Java 5的一些特性,如自动装箱、泛型以及可变参数列表来简化JDBC模板的使用

使用SimpleJdbcTemplate访问数据

使用命名参数

使用Spring的JDBC DAO支持类
创建一个通用的父类,其中会有SimpleJdbcTemplate属性,
然后让所有的DAO类继承这个类并使用父类的SimpleJdbcTemplate进行数据访问。
这是个好想法,因此Spring提供了内置的基类:
- JdbcDaoSupport
- SimpleJdbcDaoSupport
- NamedParameterJdcDaoSupport
每个分别对应于不同的Spring JDBC模板。


5.4 在Spring中集成Hibernate

我们可能需要一些复杂的特性,而JDBC并不能提供:
- 延迟加载(lazy loading)
- 预先抓取(eager fetching)
- 级联 (cascading)

Spring对多个持久化框架都提供了支持,包括Hibernate、iBATIS、Java Persistent API等。

5.4.1 Hibernate概览

HibernateTemplate的职责之一是管理Hibernate的Session,如打开和关闭session并确保每个事务使用相同的session.
如果没有HibernateTemplate,那就要让自己的DAO充满了session管理的样板代码。

HibernateTemplate的不足之处是存在一定程度的侵入性,DAO类会与Spring API产生耦合。

Hibernate 3所引入的上下文Session(Contexual session)是Hibernate本身所提供的保证每个事务使用同一Session的方案,
因此没有必要再使用HibernateTemplate来保证这一行为了。

5.4.2 声明Hibernate的Session工厂

使用Hibernate的主要接口是org.hibernate.Session. Session接口提供了基本的数据访问功能,如增删改查等。

SessionFactory负责Session的打开、关闭以及管理。

5.4.3 构建不依赖于Spring的Hibernate代码
可以直接将Hibernate Session装配到DAO类中。

5.5 Spring与Java Persistent API(Java持久化API)

5.5.1 配置实体管理器工厂
JPA定义了2种类型的实体管理器:
- 应用程序管理类型(Application-managed)
- 容器管理类型(Container-managed)

使用应用程序管理类型的JPA

使用容器管理类型的JPA

从JNDI获取实体管理器工厂

5.5.2 编写基于JPA的DAO
Spring对JPA集成提供了JpaTemplate模板以及对应的支持类JpaDaoSupport。
但是,为了实现更纯粹的JPA的方式,基于模板的JPA已经被弃用了。

-----------------------------------------------

第6章 事务管理 

6.1 理解事务
6.1.1 ACID
6.1.2 理解Spring对事务管理的支持

如果你的应用程序只使用一种持久化资源,Spring可以使用持久化机制本身所提供的事务性支持,包括JDBC、Hibernate、以及JPA. 
但是如果应用程序的事务跨多个资源,那么Spring会使用第三方的的JTA实现来支持分布式(XA)事务。

编码式事务允许用户在代码中精确定义事务的边界;而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。

6.2 实现事务管理器
6.2.1 JDBC事务
6.2.2 Hibernate事务
6.2.3 Java持久化API事务

6.4 声明式事务

6.4.1 定义事务属性

隔离级别:
并发,可能会导致以下问题:
- 脏读(Dirty Read):发生在一个事务读取了另一个事务改写但尚未提交数据时。
  如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable Read):发生在一个事务执行相同的查询2次或以上,但每次都得到不同数据时。
  这是因为另一个并发事务在2次查询期间更新了数据。
- 幻读(Phantom read):与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务T2插入了一些数据。
  在随后的查询中,T1就会发现多了一些原本不存在的记录。
  
几种隔离级别:
READ UNCOMMITED:会导致脏读、幻读或不可重复读。
READ COMMITED: 允许读取并发事务已经提交的数据。可以阻止脏读,但幻读或不可重复读仍可发生。
REPEATABLE READ: 对同一字段的多次读取结果是一致的。可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE: 完全服从ACID的隔离级别。也是最慢的。

只读
事务超时
回滚规则

6.4.2 在XML中定义事务

Spring提供了一个tx配置命名空间,借助它可以极大地简化Spring中的声明式事务。
<tx:advice id="txAdvice">
  <tx:attributes>
    <tx:method name="save*" propagation="REQUIRED" />
    <tx:method name="*" propagation="SUPPORTS"
               read-only="true" />
  </tx:attributes>
</tx:advice>

对于<tx:advice>来说,事务属性定义在<tx:attributes>元素中,该元素包含了一个或多个<tx:method>元素。
<tx:method>元素为某个(或某些)name属性(使用通配符)指定的方法定义事务参数。
<tx:method>有多个属性来帮助定义方法的事务策略。

6.4.3 定义注解驱动的事务

<tx:annotation-driven />
<tx:annotation-driven transaction-manager="txManager" />

<tx:annotation-driven>元素告诉Spring检查上下文中所有的Bean并查找使用@Transactional注解的Bean,<tx:annotation-driven>会自动为它添加事务通知。
通知的事务属性是通过@Transactional注解的参数来定义的。

 
第7章 使用Spring MVC构建web应用程序

7.1 Spring MVC起步
7.1.1 跟踪Spring MVC的请求

1. request离开浏览器后的第一站是Spring的DispatcherServlet,它是一个前端控制器。

2. DispatcherServlet查询一个或多个处理器映射(Handler Mapping)来确定request的下一站(某个Spring MVC控制器)在哪里。
   Handler Mapping根据request所携带的URL信息进行决策,再将结果告诉DispatcherServlet. 

3. DispatcherServlet将request发送给选定的Controller. 

4. Controller将业务逻辑委托给一个或多个服务对象。
   Controller完成逻辑处理后,通常会产生一些信息,即模型(model)。这些model是用于将来返回给用户并在浏览器上显示的。
   
5. 上述model里的信息需要以一种用户友好的方式进行格式化。所以信息需要发送给一个视图(view),比如以前是JSP。
   
6. Controller最后做的一件事就是将模型数据打包,并且标示出用于渲染输出的视图名称。
   接下来就是将request连同 model 和 view名称 发送回 DispatcherServlet. 
   
7. DispatcherServlet将会使用视图解析器(view resolver)来将逻辑视图名称匹配为一个特定的视图实现(它可能是也可能不是一个JSP)。
   
8. request的最后一站是视图实现,在这里 view 将使用 model 数据渲染输出,并通过这个输出将response对象传回给客户端。


7.1.2 搭建 Spring MVC

DispatcherServlet必须在web应用程序的web.xml中配置。

servlet的配置:
<servlet-mapping>
  <servlet-name>spitter</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

Spring的mvc namespace包含了一个这样的元素<mvc:resources>,它会处理所有的静态资源请求。
<mvc:resources mapping="/resources/**" 
               location="/resources/*" />


7.2 编写基本的控制器

可以为应用程序提供的每一种资源编写一个单独的控制器,而不是为每个case编写一个控制器。

7.2.1 配置注解驱动的Spring MVC

Spring自带了多个处理器映射(Handler Mapping)供我们选择。
DeafaultAnnotationHandlerMapping 将 request 映射到使用 @RequestMapping 注解的方法。
实现注解驱动的Spring MVC并不仅仅是将request映射到方法上,在构建控制器的时候,还需要使用注解将请求参数绑定到控制器的方法参数上已进行校验和信息转换。
所以,仅仅使用DeafaultAnnotationHandlerMapping还不够,还要添加一行到spitter-servlet.xml中已得到Spring MVC所提供的注解驱动特性:
<mvc:annotation-driven/>
以上的标签注册了多个特性,包括JSR-303校验支持、信息转换和对域格式化的支持。

7.2.2 定义首页的控制器

@Controller    // 声明为Controller
public class HomeController {
    ...

    @Inject    // 注入SpitterService
    public HomeController(SpitterService spitterService) {
        this.spitterService = spitterService;
    }
    
    @RequestMapping({"/", "/home"});    // 处理对首页的请求
    
    public String showHomePage(Map<String, Object> model) {
        model.put("spittles", spitterService.getRecentSpittles(xxx);  // 将Spittle放入模型中
        
        return "home";   // 返回视图名称
    }
}

7.2.3 解析视图
DispatcherServlet会查找一个视图解析器来将Controller返回的逻辑视图名称转换成渲染效果的实际视图。

7.2.4 定义首页的视图

7.2.5 完成Spring应用上下文
ContextLoaderListener是一个Servlet监听器,除了DispatcherServlet创建的应用上下文之外,它能够加载其他的配置文件到一个Spring应用上下文中。

7.3 处理控制器的输入

7.3.1 编写处理输入的控制器

@RequestMapping(value="/spittles", method=GET)
public String listSplittlesForSpitter(@RequestParam("spitter") String username, Model model) {
    ......
}

类级别的@RequestMapping定义了这个控制器所处理的Root URL的路径。

7.3.2 渲染视图

7.4 处理表单

7.5 处理文件上传


第8章 使用Spring Web Flow (略)

第9章 保护Spring应用 (略)

第10章 使用远程服务 (略)

SOA(面向服务的架构)的核心理念是,应用程序可以并且应该被设计成依赖于一组公共的核心服务,而不是为每个应用都重新实现相同的功能。

第11章 为Spring添加REST功能

11.1 了解REST

11.2 编写面向资源的控制器

11.2.1 剖析RESTless的控制器

11.2.2 处理RESTful URL

URL (uniform resource locator) 统一资源定位符
URI (uniform resource identifier) 统一资源标识符
所有的 URL  同时也是 URI. 可以认为任何给定的URL不仅可以定位一个资源还可以标识它。

但是很多URL并没有标识或定位任何东西,它们只是提出要求。这种URL表明要采取一些行动而不是标识事物。这是RESTless URL. 

在URL中嵌入参数
为了使用参数化的URL路径,Spring 3引入了新的@PathVariable注解。

11.2.3 执行REST动作

幂等性:一次请求和多次请求具有相同的作用。
安全性:如果一个方法不改变资源的状态,就是安全的。
所有安全的方法都必须是幂等的,但不是所有幂等的方法都是安全的。

PUT  - 放置资源到服务器上(应该就是创建的意思,但是实际不是create的意思)     - 幂等的方法
POST - 传送(post)数据到服务器上(看起来是更新的意思,但实际是create的意思) - 不是幂等的方法

HTTP规范还定义了其他4个方法:TRACE、OPTIONS、HEAD、CONNECT. 

使用PUT更新资源

@RequestMapping(value="/{id}", method=RequestMethod.PUT)
@ResponseStatus(HttpStatus.NO_CONTENT)    // 说明响应状态要设置为HTTP状态码204,意味着请求被成功处理了,但返回体中不包含任何返回信息
public void putSpittle(@PathVariable("id") long id, 
                       @Valid Spittle spittle) {
    spitterService.saveSpittle(spittle);
}

使用POST创建资源

@RequestMapping(method=RequestMethod.POST);

@ResponseStatus(HttpStatus.CREATED)
public @ResponseBody Spittle createSpittle(@Valid Spittle spittle, BindingResult result, HttpServletResponse response) throws BindException {
    if (result.hasErrors()) {
        throw new BindException(result);
    }
    
    spittleService.saveSpittle(spittle);
    
    response.setHeader("Location", "/spittles/" + spittle.getId());
    return spittle;
}

这里的@RequestMapping 与我们之前所看到的有所不同,它没有设置value属性。
这意味着控制器类级别的@RequestMapping唯一用于确定createSpittle()方法所处理的URL模式。
更具体地讲,createSpittle()将处理URL模式匹配"/spittles"的请求。

11.3 表述资源

Spring提供了2种方法将资源的Java表述形式转换为发送给客户端的表述形式:
- 基于视图渲染进行协商
- HTTP消息转换器

11.3.1 协商资源表述

回顾第7章,当控制器处理方法完成时,通常会返回一个逻辑视图名。如果方法不直接返回逻辑视图名(例如方法返回void),
那么逻辑视图名会来源于request的URL. DispatcherServlet接下来会将视图的名字传递给一个视图解析器(view resolver),
要求它来帮助确定应该用哪个视图来渲染请求结果。

当要将视图名解析为能够产生资源表述的视图时,视图不仅要匹配视图名,而且要适合客户端。
如果客户端想要XML,那么渲染成HTML的视图就不行了,尽管视图名可能匹配。

11.3.2 使用HTTP信息转换器

Spring HTTP信息转换器的工作就是,将处理方法返回的Java对象转换为满足客户端要求的表述形式。

11.4 编写REST客户端
11.4.1 了解RestTemplate的操作
11.4.2 GET资源
11.4.3 PUT资源
11.4.4 DELETE资源
11.4.5 POST资源数据
11.4.6 交换资源

11.5 提交REST表单
HTML 4官方在表单中只支持GET和POST,而忽略了PUT、DELETE和其他HTTP方法。
规避HTML 4和较早浏览器缺陷的一个技巧是将PUT或DELETE请求伪装成POST请求。
这种方式提交一个浏览器支持的POST请求,但是会有一个隐藏域带有实际HTTP方法的名字。
当请求到达服务器端的时候,它会重写为隐藏域指定的请求类型。

Spring通过2个特性来支持POST伪装:
- 通过使用HiddenHttpMethodFilter来进行请求转换
- 使用<sf: form> JSP标签渲染隐藏域

11.5.1 在JSP中渲染隐藏的方法域

11.5.2 发布真正的请求
HiddenHttpMethodFilter是一个Servlet过滤器,并要在web.xml中进行配置。

11.6 小结
系统中的资源采用URL进行标识,使用HTTP methods进行管理,并且会以一种或多种适合客户端的方式来表述。

借助于参数化的URL模式以及将控制器处理方法与特定的HTTP方法关联,控制器能够响应对资源的GET、POST、PUT、DELETE请求。

为了响应这些请求,Spring能够将资源背后的数据以最适合客户端的形式展现。
对于基于视图的响应,ContentNegotiatingViewResolver能够在多个视图解析器产生的视图中选择最适合客户端期望内容类型的那一个。
或者控制器的处理方法使用@ResponseBody注解完全绕过视图解析,并使用信息转换器将返回值转换为客户端的响应。

在REST会话的客户端,Spring提供了RestTemplate,可以在Java代码中基于模板的方式使用RESTful资源。

尽管本章介绍的RESTful交互与上一章介绍的RPC会话有很大的区别,但有一个共同点:它们本质上是同步的。

第12章 Spring消息 (次)

Java消息服务(Java Message Service, JMS)是面向异步消息而定制的标准API。 

第13章 使用JMX管理Spring Bean (次)

第14章 其他Spring技巧 (略)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值