《Spring实战》知识点总结

本文深入探讨Spring框架的核心技术,包括DI、AOP、WebMVC、数据访问、远程服务调用等,同时覆盖了SpringBoot的简化开发实践,旨在帮助开发者全面掌握Spring框架的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下内容纯属个人扯淡,仅供参考,建议拜读原著

1.目录最多对应到章节,名字是也是一一对应的,但不一定是按顺序来。
2.目录的一个小节就是一章的内容,里面会有很多个粗体标题,代表多个知识点,知识点之间既有联系又相互独立
3.每个粗体标题(如:1、基本环境搭建)是一个知识点。
当知识点内容较多时,就可能使用
    1。:一级分层
    1):二级分层
4.(1)用于描述步骤,如果一个知识点内容较少时
    可能不会有1。、1)
    也可能有1。但没有1)
    就能二者都有
5.建议以一个粗体标题为一个阅读单位,它并不对应书上,章节下的小节的名称和顺序,即不存在:1.1、1.1.1这样的小节,而是一个知识点概括在一个粗体标题里

 

目录

读后感

第1部分:Spring核心

概述

第1章:Spring之旅(over)

第2、3章:装备Bean、高级装配(over)

第4章:面向切面的Spring

第2部分:Web中的Spring

概述

第5章、第7章:构建SpringWeb应用程序、使用SpringMVC的高级技术

第6章:渲染Web视图

第8章:使用SpringWebFlow

第9章、第14章:保护Web应用、保护方法应用

第3部分:后端中的Spring

概述

第10章:通过Spring和JDBC征服数据库(over)

第11章:使用对象-关系映射持久化数据

第12章:使用NoSQL数据库

第13章:缓存数据

第4部分:Spring集成

概述

第15章:使用远程服务(over)

第16章:使用SpringMVC创建REST API

第17章:Spring消息

第18章:使用WebSocket和STOMP实现消息功能

第19章:使用Spring发送Email

第20章:使用JMX管理Spring Bean

第21章:借助SpringBoot简化Spring开发


 

读后感

20年5月1日

这本书基本都只是一个引导作用,它教会我们如何去简单使用某个功能,一般的工程实践参考就够了,但实际上IOC、AOP、SpringMVC是要求源码级的深入学习,而其他的Security、Redis、NoSQL、SQL、RestFul等也是架构中很重要的一部分,因此当项目中要用到时,不能停留在只是简单使用,而是需要知道它的方方面面的知识,可以通过看书来得到系统性学习。

大部分的书籍是分两类的:实战型和原理分析型,看书学习首先要辨别书是哪种类型,而学习一个新的技术点,应该是先看完实战类型的,以会使用到业务系统上优先级,在这个过程中我们可以学习到一个技术点他所拥有的功能方方面面。在这样的基础之上再去选择性找深度解析性书籍

20年6月30日

这本书是对Spring及各种相关涉及到的技术点进行整合再简单使用为目标的,因此,更深度要求的使用是需要向那个技术点上另外找资料进行研究学习的

另外,目前更多是使用SpringBoot来对这些技术进行整合,因此相应的SpringBoot整合这些技术点的一些代码就会有所差异,并且需要看自动配置类为我们使用某个技术时提供哪些默认配置支持。因此,实际使用时,应该首先要知道这本书用Spring整合时做了哪些配置,然后百度SpringBoot整合并看自动配置类提供了哪些配置。最后:简单使用部分,不管是SpringBoot还是Spring都是差不多的

第1部分:Spring核心

概述

本章内容主要是讲解Spring的2个核心功能的使用:DI、AOP。

DI部分:3种装配方式(自动装配、基于JavaConfig、基于xml)、条件化装配、处理自动装配的歧义性、作用域、SpringEL。

AOP部分:SpringAOP、AspectAOP

第1章:Spring之旅(over)

1、Spring简化Java开发的4个关键策略

概览

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

1。基于POJO的轻量级和最小侵入性编程

POJO:简单Java对象

轻量级:Spring不强迫你的POJO类需要实现某些接口或继承某个类,而其他Struts、Webwork等框架并非如此

最小倾入性:一个应用可能会集成多个框架,我们可以使用xml、注解方式来表达一个Spring组件,但这个组件在其他框架角度来看,仍然是一个POJO类。

2。通过依赖注入和面向接口实现松耦合

零耦合的代码是不存在的,它无法完成复杂的业务,因此耦合是必须的但应该被谨慎管理

1)面向接口:

紧耦合的代码,如下:

public class DamselRescuingKnight implements Knight {

  private RescueDamselQuest quest;

  public DamselRescuingKnight() {
    this.quest = new RescueDamselQuest();
  }

  public void embarkOnQuest() {
    quest.embark();
  }

}

缺点1:难以复用。RescueDamselQuest是一个具体的类,甚至可能是final class,DamselRescuingKnight负责了RDQ类的创建,DRK强依赖于具体的类RDQ,因此DRK无法接受其他的XxxQuest具体的类

缺点2:难以测试。DRK.embarkOnQuest调用时,RDQ.embark方法也必定会被调用,我们无法断言embark方法的调用次数

通过面向接口来松耦合的代码,如下

public class BraveKnight implements Knight {

  private Quest quest;

  public BraveKnight(Quest quest) {
    this.quest = quest;
  }

  public void embarkOnQuest() {
    quest.embark();
  }

}

易于复用:Quest是所有XxxQuest类父接口,因此BraveKnight可以接受任意实现了Quest接口的子类,BK甚至不知道接受的具体的类是什么

易于测试:正因为松耦合,所以我们才可以完成一个这样的测试需求

public class BraveKnightTest {

  @Test
  public void knightShouldEmbarkOnQuest() {
    Quest mockQuest = mock(Quest.class);
    BraveKnight knight = new BraveKnight(mockQuest);
    knight.embarkOnQuest();
    verify(mockQuest, times(1)).embark();  //要求Mockito框架验证:mockQuest.embark只调用一次,否则测试方法失败
  }

}

2)依赖注入

装配(wiring):创建应用组件之间协作的行为。Spring提供多种装配Bean的配置方式,其中2种如下:(可以用ClassPathXmlApplicationContext、AnnotationConfigApplicationContext加载配置进行简单测试)

xml方式

//xml方式
<bean id="knight" class="sia.knights.BraveKnight">
    <constructor-arg ref="quest" />
</bean>

<bean id="quest" class="sia.knights.SlayDragonQuest"></bean>

JavaConfig方式

//JavaConfig方式
@Configuration
public class KnightConfig {

  @Bean
  public Knight knight() {
    return new BraveKnight(quest());
  }

  @Bean
  public Quest quest() {
    return new SlayDragonQuest();
  }

}

核心思想:xml文件、JavaConfig是Spring组件之间的关系维护者,只有它才知道BraveKnight所使用的具体的Quest是谁

3。基于切面和惯例进行声明式编程

DI是为了松耦合,而AOP是把遍布应用各处的通用型逻辑抽离出来,促使系统实现关注点分离。例如:日志记录、安全控制、事务管理,应用中的业务逻辑不应该包括这些,但是它们又被广泛需要。正常的业务逻辑甚至不知道这些模块的存在

4。通过切面和模板减少样板式代码

JDBC操作访问数据库查询数据、JMS、JNDI、REST等具有大量的重复代码,Spring提供了JdbcTemplate等模板

2、Spring容器与Bean生命周期

1。Spring容器

Spring自带了多个容器的实现,但分为两大类:BeanFactory和ApplicationContext的区别

org.springframework.beans.factory.BeanFactory

org.springframework.context.ApplicationContext

BF只提供简单的DI支持,而AC基于BF提供应用框架级别的服务,例如:

AnnotationConfigApplicationContext
    加载JavaConfig类
AnnotationConfigWebApplicationContext
    加载JavaConfig类,web应用
ClassPathXmlApplicationContext
    加载类路径下的xml文件
FileSystemXmlApplicationContext
    加载文件系统中的xml文件
XmlWebApplicationContext
    加载xml文件,web应用

2。Bean生命周期

以下过程中的"调用"是要求该Bean实现了相应的接口,就会回调相应的方法

3、Spring的核心成员与其他成员

1。核心模块

Spring核心容器:核心的核心。bean工厂、应用上下文、E-mail、JNDI访问、EJB集成和调度

AOP:提供SpringAOP、Aspects支持

数据访问与集成:样板代码抽离,提供语义丰富的异常层;ORM框架集成等

Web与远程调用:MVC框架集成、RMI、Http Invoker等

Instrumentation:提供为JVM添加代理。为Tomcat提供一个织入代理,能为Tomcat传递类文件(使用场景有限)

测试:为测试提供一系列mock实现

2。其他成员

Spring Web Flow
    基于流程的会话式Web应用
    官网
Spring Web Service   
    将Spring Bean以声明式的方式发布
    官网
Spring Security
    安全机制
    官网
Spring Integration
    声明式实现应用间交互
    官网、《Spring Integration in Action》
Spring Batch
    对数据的大量操作批处理
    官网、《Spring Batch in Action》
Spring Data
    为各种数据库访问提供简单的编程模型(自动化实现Repository机制)
Spring Social
    为应用连接社交网络,如建立应用与Facebook、twitter的连接
    官网
Spring Mobile
    移动Web应用开发
Spring For Android
    为Android本地应用提供支持,还能结合Spring Social
    官网
Spring Boot
    减少编程任务,极大减少甚至消除样板式代码,致力于简化Spring本身

4、版本特性

Spring4.0系列1-新特性 Spring 5的那些新特性与增强

 

第2、3章:装备Bean、高级装配(over)

1、3种装配手段

概览

1.隐式的bean发现机制和自动装配
    声明类为组件并指定Bean命名
    组件扫描
    自动装配及注入
2.在JavaConfig中显式配置
3.在xml中显式配置
    xml扩展:注入方式
        构造器注入
            原始标签:<constructor-arg>
                引用类型、字面量、集合
            改进方案:c-命名空间。需要声明相关xsd文件
                引用类型、字面量
        属性注入
            原始标签:<property>
                引用类型、字面量、集合
            改进方案:p-命名空间。需要声明相关xsd文件
                引用类型、字面量、利用util命名空间注入集合
        

隐式方式可以方便的向容器中添加大量组件。但当将第三方jar包中的组件装配到应用中时,由于无法在第三方jar包的类上使用@Component,因此就不能使用隐式方式;

JavaConfig相对xml较强大,类型安全并对重构友好(当你某天要修改类名时,如果相应工程中有引用该类的地方没有修改时,就会编译报错,从而快速定位;而xml方式要求你必须对整个工程的bean的引用关系知晓)

3种注入方式

1.接口注入:不推荐。其强制要求被注入的对象实现不必要的接口,带有倾入性
2.构造器注入:对象在构造完成之后就能进入就绪状态
    当依赖对象较多时,构造器的参数列表就会边长
    通过反射构造对象时,对于相同类型的形参处理会很困难
    构造器无法被继承、无法设置默认值
    对于非必需的依赖处理,就需要引入多个构造器
    参数列表的变动会造成维护成本
3.属性注入:可被继承;允许默认值;良好的IDE支持
    对象无法在构造完成之马上就绪

建议:强依赖关系使用构造器注入,可选性依赖使用属性注入

1。隐式的bean发现机制和自动装配,共3步:

1)声明类为组件并指定Bean命名

public interface CompactDisc {
  void play();
}

/*
1.默认组件名=类名首字母小写,即"sgtPeppers"
2.指定是Bean名,如@Component("lonelyHeartClub")
3.@Named("lonelyHeartClub")
    可替代使用,这是Java依赖注入规范的注解,不推荐
 */
@Component 
public class SgtPeppers implements CompactDisc {

  private String title = "Sgt. Pepper's Lonely Hearts Club Band";  
  private String artist = "The Beatles";
  
  public void play() {
    System.out.println("Playing " + title + " by " + artist);
  }
  
}

定义一个组件SgtPeppers和接口CompactDisc(为什么要定义接口?见第1章-依赖注入部分)

2)组件扫描

@Configuration //这是一个配置类,也是一种组件
/*
1.默认扫描CDPlayerConfig当前所在包及子包下的组件,包括自己
2.@ComponentScan("com.test"),设置为扫描com.test包及子包下的组件
    @ComponentScan(basePackages="com.test"),作用相同
3.@ComponentScan(basePackages={"com.test","com.test2"})
    扫描多个包
4.@ComponentScan(basePacClasses={CDPlayer.class,DVDPlayer.class})
    接口下的所有子类都将被扫描到容器中
    类型安全的方式,便于代码重构
*/
@ComponentScan
public class CDPlayerConfig { 
}

3)自动装配及注入

@Component
public class CDPlayer implements MediaPlayer {

  /*
  1.1.构造器注入
  2.默认情况下,required=true,若没有满足的Bean实例,则会抛异常,可以设置为false但要注意NPE
  3.若有多个Bean实例满足,也会抛异常(后面会进一步讨论"自动装配中的歧义性")
  4.@Inject
    Java依赖注入规范,可替代,但不推荐
  */
  private CompactDisc cd;
  @Autowired
  public CDPlayer(CompactDisc cd) {
    this.cd = cd;
  }

  public void play() {
    cd.play();
  }

}


//1.2属性注入:setter或其他方法
@Autowired
public setCompactDisc(CompactDisc cd) {
    this.cd = cd;
}

2。在JavaConfig中显式配置:

建议:JavaConfig是配置代码,它不应该包含任何业务逻辑,也不应该侵入到业务逻辑代码中。最好放到单独的包中

//1.创建配置类。需要保证该配置类本身能被@ComponentScan扫描成组件到IOC容器中
@Configuration
public class CDPlayerConfig {
  
  //2.声明一个简单的Bean。默认Bean名=方法名(可以更改方法名,或指定name属性值来指定Bean名)
  @Bean
  public CompactDisc compactDisc() {
    return new SgtPeppers();
  }
  
  @Bean
  public CDPlayer cdPlayer() {
    
    //3.1.显式装配。"形似方法调用"式构造器注入,但实际上这里compactDisc()会被Springl拦截,并不会真正的执行方法调用
    return new CDPlayer(compactDisc());
  }

}


//3.2."形参"式构造器注入。好处:compactDisc()要求当前JavaConfig中存在该方法,否则会编译报错
//这里的CompactDisc可以是隐式扫描、JavaConfig、xml等只要是容器中存在这个Bean
@Bean
public CDPlayer cdPlayer(CompactDisc compactDisc) {
    
    return new CDPlayer(compactDisc);
}

3。在xml中显式配置

正因为xml手段太古老了,因此在Spring3.0对于构造器注入、属性注入分别给出了c命名空间空间、p-命名空间对原始的改进,因此xml手段的bean注入就单独拎出来总结

<!--
1.创建xml配置规范。一个以<beans>为根的xml文件;借助IDE声明多个xml模式文件-.xsd
 -->
<?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">

  <!--
  2.声明一个简单的Bean
    默认Bean名/BeanId = 全限定类名#x,x表实例计数。这里即"soundsystem.SgtPeppers#0",建议手动指定
    Spring会调用默认构造器来创建实例。而JavaConfig需要我们手动写new,但更加灵活
    类型不安全问题:类名是以string形式配置的,这对代码重构很不友好
   --> 
  <bean id="compactDisc" class="soundsystem.SgtPeppers" />

  <!--
  3.注入
   -->

</beans>

扩展知识:注入方式

1)构造器注入

引用类型

<!-- 
<constructor-arg>标签
-->
<bean id="cdPlayer" class="soundsystem.CDPlayer">
    <constructor-arg ref="compactDisc" />
</bean>

<!-- 
c命名空间1:
  c:cd-ref
    c表示c命名空间;
    -ref表示这是一个bean的引用;
    cd是CDPlayer构造器形参名,当修改时形参名会失效
-->
<bean id="cdPlayer" class="soundsystem.CDPlayer"
    c:cd-ref="compactDisc">
</bean>
<!-- 
c命名空间2:
  _0表示第0个形参。
    这样虽然修改形参名无碍,但若有多个形参时,就不能随意更改前后位置
-->
<bean id="cdPlayer" class="soundsystem.CDPlayer"
    c:_0-ref="compactDisc">
</bean>
<!-- 
c命名空间3:只适合仅有一个形参
-->
<bean id="cdPlayer" class="soundsystem.CDPlayer"
    c:_-ref="compactDisc">
</bean>

字面量

<!-- 
<constructor-arg>标签
  使用value而不是ref
-->
<bean id="compactDisc"
        class="soundsystem.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
</bean>

<!--
c命名空间1
-->
<bean id="compactDisc"
        class="soundsystem.BlankDisc"
    c:_title="Sgt. Pepper's Lonely Hearts Club Band" 
    c:_artist="The Beatles"/>
</bean>
<!--
c命名空间2
-->
<bean id="compactDisc"
        class="soundsystem.BlankDisc"
    c:_0="Sgt. Pepper's Lonely Hearts Club Band" 
    c:_1="The Beatles"/>
</bean>
<!--
c命名空间3:
若只有一个形参
-->
<bean id="compactDisc"
        class="soundsystem.BlankDisc"
    c:_="Sgt. Pepper's Lonely Hearts Club Band"/>
</bean>

集合:c命名空间无法做到的

<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
    <constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
    <constructor-arg value="The Beatles" />
    <!-- 可以使用<null/>设置为null -->
    <constructor-arg>
      <!-- 可以<set>来使用Set而不是List -->
      <list>
        
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <value>Fixing a Hole</value>
        <value>She's Leaving Home</value>
        <value>Being for the Benefit of Mr. Kite!</value>
        <value>Within You Without You</value>
        <value>When I'm Sixty-Four</value>
        <value>Lovely Rita</value>
        <value>Good Morning Good Morning</value>
        <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
        <value>A Day in the Life</value>

        <!-- 可以用<ref>来指定引用 -->
        <ref bean="sgtPeppers" />
      </list>
    </constructor-arg>
</bean>

2)属性注入

大致思想都是一致的

引用类型

<!--
<property>标签
 -->
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer">
    <property name="compactDisc" ref="compactDisc" />
</bean>

<!--
p命名空间:
  p:compactDisc-ref
    p表示p命名空间;
    -ref表示这是个引用而不是字面量
    compactDisc表示CDPlayer构造器的形参列表中的一个形参名
 -->
<bean id="cdPlayer" class="soundsystem.properties.CDPlayer"
        p:compactDisc-ref="compactDisc" />

字面量

<!--
<property>标签
-->
<bean id="compactDisc"
        class="soundsystem.properties.BlankDisc">
    <property name="title" value="Sgt. Pepper's Lonely Hearts Club Band" />
    <property name="artist" value="The Beatles" />
</bean>

<!--
p命名空间
-->
<bean id="compactDisc"
        class="soundsystem.properties.BlankDisc"
        p:title="Sgt. Pepper's Lonely Hearts Club Band"
        p:artist="The Beatles">
</bean>

集合

p命名空间本身是不支持集合注入的,但可以利用util-命名空间来协助

<!-- 
<property>标签
BlankDisc类包含一个成员属性:private List tracks
-->
<bean id="compactDisc"
        class="soundsystem.properties.BlankDisc">
    <property name="tracks">
      <list>
        <value>Sgt. Pepper's Lonely Hearts Club Band</value>
        <value>With a Little Help from My Friends</value>
        <value>Lucy in the Sky with Diamonds</value>
        <value>Getting Better</value>
        <value>Fixing a Hole</value>
        <value>She's Leaving Home</value>
        <value>Being for the Benefit of Mr. Kite!</value>
        <value>Within You Without You</value>
        <value>When I'm Sixty-Four</value>
        <value>Lovely Rita</value>
        <value>Good Morning Good Morning</value>
        <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
        <value>A Day in the Life</value>
      </list>
    </property>
</bean>

<!--
p命名空间本身不能注入集合,但借助util命名空间可以
 -->
<util:list id="trackList">  
    <value>Sgt. Pepper's Lonely Hearts Club Band</value>
    <value>With a Little Help from My Friends</value>
    <value>Lucy in the Sky with Diamonds</value>
    <value>Getting Better</value>
    <value>Fixing a Hole</value>
    <value>She's Leaving Home</value>
    <value>Being for the Benefit of Mr. Kite!</value>
    <value>Within You Without You</value>
    <value>When I'm Sixty-Four</value>
    <value>Lovely Rita</value>
    <value>Good Morning Good Morning</value>
    <value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
    <value>A Day in the Life</value>
</util:list>
<bean id="compactDisc"
        class="soundsystem.properties.BlankDisc"
        p:tracks-ref="trackList" />

另外:util命名空间中的成员

<util:constant>
    引用某个类型的public static域,并将其暴露为bean
<util:list>
    创建一个List的bean,成员可以是value值、ref引用
<util:map>
    Map
<util:properties>
    Properties
<util:property-path>
    引用一个bean的属性或内嵌属性,并将其暴露为bean
<util:set>
    Set


问题:c命名空间可以借助util命名空间来实现集合注入吗?

2、混合的装配手段

概览

JavaConfig
    引用其他JavaConfig
    引用xml配置
xml
    引用其他xml配置
    引用JavaConfig配置

1。JavaConfig

1)引用其他JavaConfig

@Import(CDConfig.class) //@Import({CDPlayerConfig.class,CDConfig.class})

更好的做法是定义一个父级意义的是配置类,来将其他所有配置类导入

2)引用xml配置

@ImportResource("classpath:cd-config.xml")

2。xml

1)引用其他xml

<import resource="cd-config.xml"/>

2)引用JavaConfig:和普通bean没啥区别

<bean class="soundsystem.CDConfig" />

 

第4章:面向切面的Spring

1、AOP概述

日志、安全、事务管理、缓存等功能,但这些都不应该是业务对象应该主动参与的行为,AOP可以实现横切关注点与所影响的对象之间的解耦。系统的实际业务,不仅仅要看每个控制器方法接收请求直到返回响应数据这样的纵向业务线,还有横向方向的AOP也会影响着代码逻辑

而为了重用代码,可以让定义一个父类,里面定义一个方法模拟通知的作用,而所有的子类就能继承到这个方法,在方法体执行前调用该方法。但是这样设计会使得所有类已继承了一个父类,而在Java里一个类只能有一个父类,因此并非好的设计;另外一个设计是使用委托,疑问:这可能导致对委托对象进行复杂的调用?参考:Java委托机制

1。AOP中的术语

1)通知

切面的工作内容,它定义了切面是什么、要完成什么工作、何时使用。Spring支持5个时机:前置、后置、返回、异常、环绕

2)连接点

应用中可能有数以千计的位置应用通知,这些位置就成为连接点,它是在应用执行过程中能够插入切面的一个点,这个点可以是:方法调用时、抛出异常时、修改字段时

3)切点

定义了哪些连接点会得到通知。使用明确的类和方法名、正则匹配类和方法名、动态切点运行时决策是否应用通知

4)切面

切面=通知+切点。切面是什么,在何时,何处完成什么功能

5)引入

允许向现有的类添加新的方法或属性

6)织入

是把切面应用到目标对象并创建新的代理对象的过程。它可以发生在以下生命周期点:编译器期(需要特殊的编译器)、类加载期(需要特殊的类加载器)、运行期(SpringAOP支持)

2。SpringAOP

1)4种类型的AOP支持

基于代理的经典SpringAOP
纯POJO切面
@Aspect注解驱动的切面
注入式AspectJ切面

前3者是SpringAOP变体,构建于动态代理之上,因此:SpringAOP只局限于方法拦截

第4种可以实现构造器拦截、属性拦截

2)注意事项

Spring是Java编写的,IDEA通用,而Aspect需要额外学习新的工具和语法

运行时通知对象。直到应用需要被代理的bean时,才创建代理对象

只支持方法级别的连接点,因为SpringAOP是基于动态代理的。而AspectJ、JBoss提供了字段切入点(字段在创建时、修改时得到通知)、构造器接入点(对象构造期间)

2、JavaConfig使用SpringAOP

(1)定义切点与连接点

Spring借助Aspect切点表达式语言来定义Spring切面,非下列指示器时将抛出:非法参数异常

AspectJ指示器描述
arg()限定匹配参数为指定类型的执行方法
@args()限定匹配参数由指定注解标注的执行方法
excution()用于匹配是连接点的执行方法
this()限定匹配AOP代理的bean引用为指定类型的类
target限定匹配目标对象为指定类型的类
@target()限定匹配特定的执行对象,其对应的类药具有指定类型的注解
within()限定匹配指定类型
@within()限定匹配指定注解所标注的类型
@annotation限定匹配带有指定注解的连接点
bean()

限定匹配指定beanId、bean名

但,一般用方是用not来排除该bean

结论:excution用来执行匹配,而其他的是用来限定筛选的

excution(* concert.Performance.perform(...)) && within(concert.*)

返回任意类型
指定具体类,具体方法
任意参数的perform()
并且,仅限定在执行包下
&&可用and代替
    ||或or
    !或not

 

(2)定义切面

 

(3)通知与引入

3、xml使用SpringAOP

 

4、AspectJ切面

 

第2部分:Web中的Spring

概述

第5章、第7章:构建SpringWeb应用程序、使用SpringMVC的高级技术(over)

1、跟踪SpringMVC的请求

1.请求离开客户端浏览器
    所有请求会通过前端控制器DispactherServlet
2.DS查询处理器映射
3.将请求发送给选中的控制器
    请求卸下负载-用户提交的信息,等待控制返回结果
    控制器完成业务逻辑处理后,会产生信息(模型model),并以友好的方式进行格式化,一般是html
    信息需要发送给一个视图view,可能是jsp
4.控制器将模型数据打包,并给出视图名,发送给DS
    控制器不会与特定的视图相耦合
5.DS使用视图解析器-viewResolver来将逻辑视图名匹配一个特定的视图实现(可能是jsp)
6.视图实现
7.视图渲染模型数据,并传递给客户端

2、使用SpringMVC

概览

基本环境搭建
    配置DispatcherServlet,启用SpringMVC
    定义控制器并返回模型与视图
处理各种参数
    查询参数
    路径变量
    表单参数
        扩展功能:表单参数校验
    文件上传
        配置multipart解析器
        处理multipart请求
异常处理
    Spring自动异常转换为状态码
    手动转换异常为状态码
    手动处理统一异常

1。基本环境搭建

1)配置DispatcherServlet,启用SpringMVC

//1.替代web.xml的配置方式。扩展AACDSI类的任意类会自动配置DS、Spring应用上下文
//要求:Servlet3.0、Tomcat7以上的服务器
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
  //2.应用默认Servlet,处理进入应用的所有请求
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  //3.WebConfig将用于定义DS应用上下文中的bean:Web组件。控制器、视图解析器、处理器映射等
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

  //4.RootConfig用于配置ContextLoaderListener创建的应用上下文中的bean:service、dao等
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

}

AbstractAnnotationConfigDispatcherServletInitializer解析

Servlet3.0开始,容器会尝试在类路径下寻找ServletContainerInitializer实现类去初始化Servlet容器
    Spring提供该接口的实现:SpringServletContainerInitializer,该类将任务委托给WebApplicationInitializer
        Spring3.2引入AbstractAnnotationConfigDispatcherServletInitializer,是WebApplicationInitializer的便利实现

DispatcherServlet应用上下文配置类

@Configuration
//1.替代xml方式配置<mvc:annotation-driven>启用MVC注解驱动
@EnableWebMvc
//2.web控制器组件扫描。该包下的控制器添加上@Component注解,就会被扫描到容器中
//不需要在JavaConfig中显式声明控制器
@ComponentScan("spittr.web")
public class WebConfig extends WebMvcConfigurerAdapter {

  //3.配置JSP视图解析器
  //默认使用的是BeanNameViewResolver。它要求:bean名称=视图名;bean要实现View接口
  @Bean
  public ViewResolver viewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/views/");
    resolver.setSuffix(".jsp");
    return resolver;
  }
  
  //4.静态资源直接放行
  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }

}

ContextLoaderListener应用上下文配置类

@Configuration
@ComponentScan(basePackages={"spitter"}, 
    excludeFilters={
        @Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)
    })
public class RootConfig {

}

2)定义控制器并返回模型与视图

@Controller //也可以使用@Component,但表意稍差
//1.类级映射。可配置多个,如:{"/","/homepage"}
@RequestMapping("/spittles")
public class SpittleController {

  private SpittleRepository spittleRepository;
  @Autowired
  public SpittleController(SpittleRepository spittleRepository) {
    this.spittleRepository = spittleRepository;
  }
  

  //2.方法级映射。默认值="/"
  @RequestMapping(method=RequestMethod.GET)
  Model实际是个Map。这里如果换成:Map model也是会被实例化的
  public String spittles(Model model) { 
    //3.不指定key时,会根据对象类型推断。该方法返回List<Spittle>,因此推断key=spittleList
    //也可以显式指定,如:model.addAttribute("spittleList",xxx);
    model.addAttribute(spittleRepository.findSpittles(Long.MAX_VALUE,20));
    //返回视图逻辑名
    return "spittles";
  }

}


//不推荐的写法。太依赖推断了
//未显示指定设定模型。但数据会添加到模型,key根据类型推断而来。"spittleList"
//未指定逻辑视图名。根据请求路径推断得出。"spittles"
@RequestMapping(method=RequestMethod.GET)
  public String spittles() { 
    return spittleRepository.findSpittles(Long.MAX_VALUE,20);
}

spittles.jsp

<c:forEach items="${spittleList}" var="spittle" >
    <li id="spittle_<c:out value="spittle.id"/>">
        <div class="spittleMessage"><c:out value="${spittle.message}" />
        </div>
        <div>
            <span class="spittleTime">
                <c:out value="${spittle.time}" />
            </span>
            <span class="spittleLocation">
                (<c:out value="${spittle.latitude}" />,<c:out value="${spittle.longitude}"/>)
            </span>
        </div>
    </li>
</c:forEach>

2。处理各种参数

1)查询参数

@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
        //可以设置required=false表示非必须
        @RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
        @RequestParam(value="count", defaultValue="20") int count) {

    return spittleRepository.findSpittles(max, count);
}

测试:http://(ip):(port)/(packageName)/spittles?max=1000&count=30

2)路径变量

设计理念:尽管用请求参数的方式也能正常工作,但是从面向资源的角度来看,路径变量方式能标示一个资源,但请求参数方式描述的是一个带参数的操作(本质含义上是一个通过HTTP发起的RPC)

@RequestMapping(value="/{spittleId}", method=RequestMethod.GET)
public String spittle(
        //若前后端约定该变量名一致的情况下,那么就可以省略注解的参数字符串了,采用推断方式精简代码
        @PathVariable("spittleId") long spittleId, 
        Model model) {
    model.addAttribute(spittleRepository.findOne(spittleId));
    return "spittle";
}

spittle.jsp

<div class="spittleView">
    <div class="spittleMessage">
        <c:out value="${spittle.message}" />
    </div>
    <div>
        <span class="spittleTime">
            <c:out value="${spittle.time}" />
        </span>
    </div>
</div>

测试:http://(ip):(port)/(packageName)/spittles/8

3)表单参数

registerForm.jsp注册表单

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          href="<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Register</h1>

    <!--
    未设置action属性,因此推断结果:它提交的目的地址就是展现它的url路径,假设:
    这个jsp页面本身请求展现的url为:http://(ip):(port)/(packageName)/spitter/register,这当然是个Get形式的请求
    那么表单提交目标地址也为:http://(ip):(port)/(packageName)/spitter/register的POST
    -->
    <form method="POST">
      First Name: <input type="text" name="firstName" /><br/>
      Last Name: <input type="text" name="lastName" /><br/>
      Email: <input type="email" name="email" /><br/>
      Username: <input type="text" name="username" /><br/>
      Password: <input type="password" name="password" /><br/>
      <input type="submit" value="Register" />
    </form>
  </body>
</html>

请求接收

@RequestMapping(value="/register", method=POST)
//Spitter类的每个属性名,需要与页面上form表单的每个input的name属性值一一对应且一致
public String processRegistration(Spitter spitter) {
    
    spitterRepository.save(spitter);
    //InternalResolverViewResolver视图解析到"redirect:"前缀,则重定向到目标页面;而"forward:"是请求转发
    return "redirect:/spitter/" + spitter.getUsername();
}

参考:页面跳转的两种方式(转发和重定向)区别及应用场景分析

扩展功能:表单参数校验。

对于非法的表单参数值,比如:空的firstName、非法格式的邮箱,是不能被提交成功的,但是表单校验逻辑又不应该属于控制器本身的业务逻辑,更不应该让这样非法的数据进入到service服务层。Spring3.0在SpringMVC中提供了JSR303支持,只需要保证类路径下有该API的实现即可,如:Hibernate Validator(SpringBoot应用默认就集成了),主要提供了以下注解:

//javax.validation.constraint包下
@AssertFalse:必须为Boolean.FALSE
@AssertTrue:必须为Boolean.TURE
@DecimalMax:不大于
@DecimalMin:不小于
@Digits:指定位数的数字
@Future:将来的日期
@Max:不大于
@Min:不小于
@NotNull:必须不能为null
@Null:必须为null
@Past:已过去的日期
@Pattern:满足某个正则表达式
@Size:String、集合、数组的长度

使用步骤:

(1)添加到实体类的属性上

public class Spitter {

  private Long id;
  
  @NotNull
  @Size(min=5, max=16)
  private String username;

  @NotNull
  @Size(min=5, max=25)
  private String password;
  
  @NotNull
  @Size(min=2, max=30)
  private String firstName;

  @NotNull
  @Size(min=2, max=30)
  private String lastName;
  
  @NotNull
  @Email
  private String email;
}

(2)启用校验

@RequestMapping(value="/register", method=POST)
public String processRegistration(
        @Valid Spitter spitter, 
        Errors errors) { //该形参必须紧跟@Valid后面
    if (errors.hasErrors()) { //表单校验是否有错
      return "registerForm";
    }
    
    spitterRepository.save(spitter);
    return "redirect:/spitter/" + spitter.getUsername();
}

书上提供的这种方式有个缺点就是:我们需要在每个POST接收方法中调用同样的一段代码去判定是否参数校验成功,这可以利用SpringAOP去拦截这些POST方法,然后在环绕通知中获取Error对象来控制;

这两种做法也还有缺点:它要求每个POST接收方法中都需要加入一个Error的形参,这也有一定的代码倾入性。这个可以更换为使用全局异常处理来处理,具体见:SpringBoot2.2.2.RELEASE+参数校验

4)文件上传

multipart用于处理二进制数据,DispatcherServlet并未实现任何解析multipart型数据的功能,它委托给MultipartResolver处理

(1)配置multipart解析器

注意:接收multipart数据这里有3种方式,对应于3种注入DispatcherServlet方式,若采用javax.servlet.http.Part接收的话就不需要配置MultipartResolver

StandardServletMultipartResolver。要求Spring3.1、Servlet3.0以上,首先需要将注入这个解析器

@Bean
public MultipartResolver multipartResolver() throws IOException {
    return new StandardServletMultipartResolver();
}

然后需要在DispatcherServlet中指定解析器相关配置,这里给出对应的3种DS配置产生的不同方式

//1.实现WebApplicationInitializer注入DS(这里不采用书上的)
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        //使用RootConfig配置ContextLoaderListener上下文,其中的bean是service、dao层等
        AnnotationConfigWebApplicationContext rootContext =
                      new AnnotationConfigWebApplicationContext();
        rootContext.register(RootConfig.class); 
        container.addListener(new ContextLoaderListener(rootContext));
  
        //使用WebConfig配置DispatcherServlet上下文,其中的bean主要是控制器、解析器、处理器映射等
        AnnotationConfigWebApplicationContext dispatcherContext =
                      new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebConfig.class);
  

        ServletRegistration.Dynamic dispatcher =
                      container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        //multipart相关配置
        //("/tmp/spittr/uploads",2097152,4194304,0):文件大小不超过2MB,整个请求不超过4MB,所有文件都要写到磁盘中
        dispatcher.setMultipartConfig(
            new MultipartConfigElement("/tmp/spittr/uploads"); //指定上传路径(必选配置)
        );
    }
}


//2.继承AbstractAnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer方式注入DS
//这种方式下DS已被创建好了,此时只需重载一个customizeRegistration方法,使用它的形参即可
public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
    ...
    //其他代码见前面的:配置DispatcherServlet
    @Override
    public void customizeRegistration(Dynamic registration){
        registration.setMultipartConfig(
            new MultipartConfigElement("/tmp/spittr/uploads");
        );
    }
    ...
}

//3.web.xml方式注入DS
//<servlet>
//    <servlet-name>dispatcherServlet</servlet-name>
//    <servlet-class>
//        org.springframework.web.servlet.DispatcherServlet
//    </servlet-class>
//    <load-on-startup>1</load-on-startup>
//    <multipart-config>
//        <location>/tmp/spittr/uploads<location>
//        <max-file-size>2097152<max-file-size>
//        <max-request-size>4194304<max-request-size>
//    </multipart-config>
//</servlet>

CommonsMultipartResolver。注入bean

@Bean
public MultipartResolver multipartResolver() {
    //所有配置都是可选的,甚至可以直接将commonsMultipartResolver返回
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();

    multipartResolver.setUploadTempDir("/tmp/spittr/uploads"); //路径(默认为Servlet容器临时目录)
    multipartResolver.setMaxUploadSize(); //最大文件2MB
    multipartResolver.setMaxInMemorySize(0); //不管文件大小如何,所有文件都将写到磁盘

    //无法设置multipart请求整体的最大容量

    return commonsMultipartResolver;
}

(2)处理multipart请求

前端页面


<!--
1.enctype:以multipart数据的形式提交表单而非表单数据形式。每个输入域对应一个part
2.type="file":接收文件
3.accept:限定文件类型
 -->
<form method="POST" enctype="multipart/form-data">
...
    <label>picture</label>
    <input type="file" name="profilePicture" accept="image/jpeg,image/png,image/gif">    
...
</form>

3种接收方式:byte[]、MultipartFile、Part(和MultipartFile的api使用无差,但不需要配置MultipartResolver)

只需要替换类型即可

//1.byte[]数组接收。可以做的事情太少了
@RequestMapping(value="/register", method=RequestMethod.POST)
public String processUpload(
        @RequestPart("profilePicture") byte[] profilePicture
        @Valid Spitter spitter) {
    System.out.println(file.getSize());
    return "redirect:/";
}

扩展知识1:MultipartFile提供的方法

public interface MultipartFile extends InputStreamSource {

    String getName();

    //原始文件名
    @Nullable
    String getOriginalFilename();

    //内容类型
    @Nullable
    String getContentType();

    boolean isEmpty();

    //文件大小
    long getSize();

    byte[] getBytes() throws IOException;

    //用于将文件数据以流的方式读取
    @Override
    InputStream getInputStream() throws IOException;
	
    default Resource getResource() {
        return new MultipartFileResource(this);
    }

    //将文件写到本地文件系统
    void transferTo(File dest) throws IOException, IllegalStateException;

    default void transferTo(Path dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
    }

}

扩展知识2:将图保存到云端。

图片保存到本地文件系统是简单的,但需要:管理文件、确保有足够空间、确保出现故障时文件备份、集群环境下的文件同步等问题


private void saveImage(MultipartFile image) throws ImageUploadException {

    try {
        AWScredentials awsCredentials = new AWScredentials(s3AccessKey,s2SecretKey);
        S3Service s3 = new RestS2Service(awsCredentials);
        ......
    } catch (Exception e) {
        throw new ImageUploadException("保存失败",e);
    }
}

3。异常处理

原因:异常是不可避免会发生的,我们需要以优雅的、合理的处理告知前端。当异常只需简单处理-返回Http状态码,除了Spring已定义的能自动处理一些异常,剩下一些异常Spring会默认转换为500,这是不精确的,因此我们可以使用@ResponseStatus来指定为特定的状态码并提供更精确的信息;当不仅仅是简单处理时,就需要编写异常处理器来深度定制处理结果返回给前端,借助@ControllerAdvice我们可以只编写一个类、一个方法就可以捕获所有控制器方法所抛出的特定异常进行处理

不管是Spring自动转换还是我们通过@ResponseStatus去映射HTTP状态码,这是异常处理的一种简单的方式而已,简单到我们程序员不需要知道其中的具体处理流程,我们能知道的是:它被转换的结果是为对应的状态码,而通过@ExceptionHandler方式是定义一个专门处理某类型异常的处理方法,这个方法会捕获程序中所有抛出该类型的异常,因此这个方法的处理结果可以是各种各样的:返回视图、返回特定的数据、提示信息等等

1)Spring自动异常转换机制

这里的异常类型会由Spring自身抛出,作为DispatcherServlet处理过程中、执行校验时出现异常的处理的状态码结果

Spring异常	                        HTTP状态码

BindException                           400
ConversionNotSupportedException	        500
HttpMediaTypeNotAcceptableException	406
HttpMediaTypeNotSupportedException	415
HttpMessageNotReadableException	        400
HttpMessageNotWritableException	        500
HttpRequestMethodNotSupportedException	405
MethodArgumentNotValidException	        400
MissingServletRequestParameterException	400
MissingServletRequestPartException	400
NoSuchRequestHandlingMethodException	404
TypeMismatchException	                400

2)手动转换异常为特定的状态码

如果应用中其他的异常,我们只需要将它处理为HTTP状态码而不需要提供其他数据,就可以手动映射为状态码。比如当我们前台请求是获取一篇文章这样的业务需求,那么当从dao返回的数据为null时,那就需要抛出一个异常,像这样

@GetMapping("/{spittleId}")
public String spittle(
        @PathVariable("spittleId")long spittleId,
        Model model){

    Spittle spittle = spittleRepository.findOne(spittleId);
    if(spittle == null){
        throw new SpittleNotFoundException(); //非受查异常,因此spittle方法不需要声明throws
    }
    model.addAttribute(spittle);
    return "spittle";
}


//自定义非受查异常
public class SpittleNotFoundException extends RuntimeException {

    ...
}

此时会产生默认的500状态码,但是这里的业务场景是:请求资源没有找到,因此应该更改为404(比500在含义上更为精确)。在我的理解看来,500表示服务器内部错误,但具体是什么错误就不知道了,就好比Exception,只知道是个异常,但具体什么异常不知道,而404就像一个具体的异常,如ClassNotFoundException,我们可以知道更具体的异常信息(当然500和404并非继承关系)

所以,在SpittleNotFoundException上添加,那么只要程序中抛出SNFE异常,就会转换为404状态码返回给前端浏览器而非500

@ResponseStatus(
    value=HttpStatus.NOT_FOUND,
    reason="Spittle没找到")

3)手动统一处理特定的异常

将异常映射为状态码是很简单的异常处理方案,但有时需要响应中不仅是状态码,还有错误信息等,就不能视为HTTP错误了,而是需要手动处理,而手动处理异常这部分代码不应该放到具体的方法中,业务方法应该更关注于业务实现,这和前面将参数校验代码抽离出来是同样的思想。

在XxxController中添加以下异常处理器,该方法能处理XxxController控制器中所有控制器方法所抛出的异常


//处理特定异常
@ExceptionHandler(DuplicateSpittleException.class)
public String handleDuplicateSpittleException() {
    
    //返回特定的逻辑视图名,实际上在这个方法里做其他操作也是可以的
    return error/duplicate;
}

如果需要处理多个XxxController抛出相同的DuplicateSpittleException,并且,我们的处理逻辑是一样的,那总不能在每个方法里都写一段一样的代码吧,所以可以利用控制器通知来定义一个全局异常捕获器来处理所有@RequestMapping方法抛出的异常,

我们平时写代码都是Controller方法向下调用Service业务方法,再向下调用DAO层,这意味着假如我们在这几层某处中编写了try/catch捕获了某个异常,那么在调用者看来这个方法是正常返回的/异常不可知的,俗称:异常被吃了。那么控制器方法就不会抛出异常,也就不会被上层捕获了

一句话:@ControllerAdvice用于对所有控制器方法添加全局异常捕获器、全局属性、全局数据绑定器


@ControllerAdvice  //@Component子类
public class AppWideExceptionHandler {

    
    //1.全局异常捕获器
    //所有控制器方法所抛出的DuplicateSpittleException,将会在这里被捕获
    @ExceptionHandler(DuplicateSpittleException.class)
    public String handleDuplicateSpittleException() {
        return error/duplicate;
    }

    //2.全局属性
    //所有控制器方法可以通过两种方式获取
    //@ModelAttribute("globalAtt") String author。直接形参取出
    //(String) modelMap.get("globalAtt")。通过ModelMap间接取出
    @ModelAttribute
    public void addAtr(Model model){    //参数为model对象
        model.addAttribute("globalAtt", "全局属性");    //添加属性
    }
    


    //3.全局数据绑定器(用于参数绑定的组件)
    //Date类型的数据格式默认并非为"yyyy-MM-dd",因此当前端传递这样格式的数据时:
    //如:http://localhost:8080/springboot/hello?date=1992-12-18
    //控制器方法的形参列表当中的Date date就能接受数据绑定了
    @InitBinder       
    public void initBinder(WebDataBinder binder){
    
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        //定义日期编辑器,编辑器都是PropertyEditorSupport的子类
        CustomDateEditor editor=new CustomDateEditor(sdf, false);
        //注册编辑器,这里的第一个参数为要绑定的参数类型:Date
        binder.registerCustomEditor(Date.class, editor);
    }
    
}

参考:spring boot 全局异常处理的实现(@ExceptionHandler),以及@InitBinder、@ModelAttribute的作用

3、跨重定向请求传递数据

题外话:建议在处理完POST请求后,都执行重定向,这样可以防止用户点击浏览器刷新、后退时产生重复提交

为什么要?将数据发送给重定向的目标方法

@RequestMapping(value="/register",method=POST)
public String processRegistration(
        Spitter spitter,Model model) {

    spitterRepository.save(spitter);
    //1.使用URL模板传递简单数据
    model.addAttribute("username",spitter.getUsername()); 
    model.addAttribute("spitterId",spitter.getId()); //未匹配到占位符,因此以查询参数形式附加到url
    //2.使用flash属性传递对象数据
    model.addFlashAttribute("spitter",spitter); //可以省略key,会根据类型推断为spitter
    return "redirect:/spitter/{username}";  //匹配到占位符,则以路径变量形式附加在url上
}

传递对象数据有一个方案是这样的思路:将对象数据保存到session中;在第二个请求中从session获取;在session中清理这份数据,Spring认为我们不应该管理这些数据,不应该耗费过多的代码在session管理这份数据上,因此定义了flash属性

4、定制化DispatcherServlet

概览:3种DS初始化

再谈AbstractAnnotationConfigDispatcherServletInitializer
使用较原始的WebApplicationInitializer
最原始的web.xml(不推荐)

若对SpringMVC环境无高度的定制化要求时,一般像1、基本环境搭建那里AbstractAnnotationConfigDispatcherServletInitializer就可以满足需求了,但若:需要添加其他Servlet、Filter;Servlet<3.0或tomcat<7等情况下就没那么简单了

1。再谈AbstractAnnotationConfigDispatcherServletInitializer

前面1、2中完成的配置是这样的

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  
  //应用默认Servlet,处理进入应用的所有请求
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  //WebConfig将用于定义DS应用上下文中的bean:Web组件。控制器、视图解析器、处理器映射等
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

  //RootConfig用于配置ContextLoaderListener创建的应用上下文中的bean:service、dao等
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }
  
  
  @Override
  public void customizeRegistration(Dynamic registration){
    //文件上传相关配置
    registration.setMultipartConfig(
        new MultipartConfigElement("/tmp/spittr/uploads");
    );
  }

  //注册Filter,并只会映射到DS上
  @Override
  protected Filter[] getServletFilters(){
      return new Filter[] {new MyFilter()};
  }

}

这个类的继承关系

WebApplicationInitializer
    AbstractContextLoaderInitializer //ContextLoader应用上下文
        AbstractDispatcherServletInitializer //DS应用上下文
            AbstractAnnotationConfigDispatcherServletInitializer

 

2。使用较原始的WebApplicationInitializer

Spring提供WebApplicationInitializer的目的,就是为了取代web.xml

参考:Spring中WebApplicationInitializer的理解

AbstractAnnotationConfigDispatcherServletInitializer自动创建DispatcherServlet应用上下文、ContextLoader应用上下文,但是它不能注册你自定义的Servlet、Listener、特定路径的Filter。


public class MyServletInitializer implements WebApplicationInitializer {


    @Override
    public void onStartup(ServletContext servletContext){

        //添加Servlet
        Dynamic myServlet = servletContext.addServlet("myServlet",MyServlet.class);
        myServlet.addMapping("/custom/**");

        //添加Filter
        javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter",MyFilter.class);
        filter.addMappingForUrlPatterns(null,false,"/custom/*");
    }
}

扩展知识:在SpringBoot应用里,默认是以jar包方式运行的,若希望将应用打包成war包,除了修改pom.xml中<package>值为war之外,还需要添加一个类,例如:

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(YhcloudApplication.class);
    }
}

//而这个类就实现了WebApplicationInitializer
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
    ...
}

参考:springboot部署web容器SpringBootServletInitializer用途

这个类还有啥其他用途目前尚不清楚

3。最原始的web.xml(不推荐)

web.xml使用xml配置文件来初始化ContextLoader应用上下文、DS应用上下文(最最古老的方式)

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="
            http://java.sun.com/xml/ns/javaee 
            http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" >
    
    <!-- 1.初始化ContextLoader上下文 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <!-- 2.初始化DS上下文 -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servet-class>
        <!--
        默认是找/WEB-INF/(servlet名)-context.xml,这里即appServlet-context.xml
        这里使用init-param可以指定
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                /WEB-INF/spring/servlet-context.xml
            </param-value>
        <init-param>
        -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

web.xml使用JavaConfig来初始化CL、DS(将xml配置文件换成了JavaConfig,算是过渡期)

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="
            http://java.sun.com/xml/ns/javaee 
            http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" >
    
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.AnnotationFonfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- 1.初始化CL上下文 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>(全路径)RootConfig</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <!-- 2.初始化DS上下文 -->
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.AnnotationFonfigWebApplicationContext
            </param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                (全路径)WebConfig
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

 

第6章:渲染Web视图

1、视图解析

SpringMVC的一个重要策略:将控制器中请求处理逻辑和视图渲染二者解耦,控制器并不直接负责产生html,这样可以在不影响请求处理逻辑的前提下,维护和更新视图。控制器只通过逻辑视图名来了解视图,具体采用什么视图实现来渲染模型是由ViewResolver决定的。

public interface ViewResolver {

    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;

}

视图解析器接收一个viewName-逻辑视图名、Locale-物理位置,返回一个View视图实例,而View接口就是接收:模型、request、response对象,将输出结果渲染到response中

public interface View {

    String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
    String PATH_VARIABLES = View.class.getName() + ".pathVariables";
    String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

    @Nullable
    default String getContentType() {
        return null;
    }

	
    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

}

Spring提供了这些视图、视图解析器的实现能满足大多数情况

BeanNameVR               将视图解析为Spring应用上下文中的Bean,BeanId=视图名
ContentNegotiatingVR     通过考虑客户端需要的内容来解析视图,委托给另外一个能产生对应类型内容的视图解析器
FreeMarkerVR             解析为FreeMarker模板
InternalResourceVR       解析为Web应用的内部资源(一般为JSP)
JasperReportsVR          解析为JasperReports定义
TilesVR                  解析为ApacheTile定义,其中TileId=视图名(还有个Tiles3.0)
UrlBasedVR               直接根据视图的名称解析视图,视图名会匹配一个物理视图定义
VelocityLayoutVR         解析为Velocity布局,从不同的Velocity模板中组合页面
VelocityViewVR           解析为Velocity模板
XmlVR                    解析为特定xml文件中的bean定义,类似BeanNameVR               
XsltVR                   解析为XSLT转换后的结果

2、JSP

1.配置解析器

2.定义视图

3.Spring的JSP库

3、Apache Tiles

1.配置解析器

2.定义Tiles

4、Thymeleaf

1.配置解析器

2.定义模板

1)普通模板

2)表单模板

第8章:使用SpringWebFlow

第9章、第14章:保护Web应用、保护方法应用

1、

 

第3部分:后端中的Spring

概述

本部分主要是持久化数据技术。首先是SpringJDBC来简化原生JDBC操作,但实际应用中一般不是使用jdbc而是使用各种ORM框架,这里介绍了SpringData对关系数据库、非关系MongDB、图数据库Neo4j、缓存Redis的支持

第10章:通过Spring和JDBC征服数据库(over)

1、Spring对于数据访问的3个设计思想

概览

设计哲学
异常体系
    原生jdbc、ORM框架中的异常
    Spring提供与平台无关的持久化异常
模板化

1.设计哲学

服务通过接口来访问Repository,更易于被测试,它不再与特定的数据访问实现耦合,将持久层隐藏于接口之后。另外,由于这里将Repository都有Repository接口,因此我们在服务中注入Repository时,可以选择接口注入的方式而不是构造器注入、属性注入,但Spring并不强制你这样做

2.异常体系

1)原生jdbc、ORM框架中的异常

原生jdbc的异常:SQLException,例如

应用程序无法连接数据库
要执行的语句存在语法错误
查询中所使用的表、列不存在
试图插入或更新的数据违反了数据库约束

Hibernate的异常体系:提供了20个左右的异常类,分别特定的问题,这样程序员就能针对想处理的异常编写catch。因此这样的ORM框架相对于JDBC本身提供的SQLException,至少是有异常分类

总结:SQLException往往是无法通过在catch中编写代码来解决的致命性问题,因此不应该设计为受查异常,但从异常本身概念上去设计的话,它应该设计为非受查异常,因为这一类异常就类似File资源访问一样,这并非程序代码本身问题;Hibernate设计了自己的异常体系,那么如果使用这样的异常体系的话,那么对Hibernate的使用会渗透到整个
系统各个地方,有朝一日若更换ORM框架,那带来的修改成本将是巨大的

2)Spring提供与平台无关的持久化异常

Spring将抛出一致的异常,与所选择持久化机制与数据访问层隔离开来;并且这些异常都继承DataAccessException非受查异常,可以不用写try/catch语句(受查异常必须有try/catch,否则编译期报错,而非受查异常是运行时报错),当然你也可以在一个方法中写try/catch,那么说明是由你自己来处理这可能的异常,对于调用该方法的外部来说,这个方法是正常返回的,它根本不知道这个方法里发生了异常

3.模板化

Spring使用模板方法模式,将数据访问过程中的过程划分为:模板和回调。其中模板管理着过程中固定的部分:事务控制、管理资源、异常处理,而回调用于处理数据访问语句、绑定参数、整理结果集。例如以下模板

CciTemplate
    JCA CCI连接
JdbcTemplate
    JDBC连接
NamedParameterJdbcTemplate
    支持命名参数的JDBC连接
SimpleJdbcTemplate
    jdk5简化后的jdbc连接,Spring3.1废弃
HibernateTemplate
    Hibernate3.x以上的Session
SqlMapClientTemplate
    iBATIS SqlMap客户端
JdoTemplate
    Java数据对象-JDO实现
JpaTemplate
    Java持久化API的实体管理器

2、使用SpringJDBC

概览

配置数据源
    JNDI数据源 (推荐)
    数据源连接池
    基于JDBC驱动的数据源
    嵌入式数据源
    profile选择数据源 
SpringJDBC
    原生JDBC
    Spring的JDBC模板
        JdbcTemplate
        NamedParameterJdbcTemplate

1.配置数据源

生产环境中建议使用数据源连接池方式

1)JNDI数据源

如果应用是部署在JavaEE服务器中,如:WebSphere、JBoss、Tomcat等这样的Web容器,它们允许通过JNDI获取数据源。数据源是完全独立于应用程序之外进行管理,具有更好的性能并支持热切换

参考:JNDI_百度百科 读完这个我懂了JNDI

xml

<jee:jndi-lookup id="dataSource" jndi-name="/jdbc/SpitterDS" resource-ref="true" />

JavaConfig

@Bean
public JndiObjectFactoryBean dataSource() {
    
    JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean ();
    jndiObjectFB.setJndiName("jdbc/SpitterDS");
    jndiObjectFB.setResourceRef(true);
    jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
    return jndiObjectFB;
}

2)数据源连接池

3)基于JDBC驱动的数据源

4)嵌入式数据源

5)profile选择数据源

2.SpringJDBC

1)原生JDBC

优点:
    建议在SQL之上
    可以更好地进行数据访问性能调优
    在更低的层次上处理数据,完全控制如何读取和管理数据,甚至访问单独的列
缺点:
    需要负责处理与数据库访问相关的所有事情,其中包括:资源管理、异常处理
    大量的JDBC代码都是用于创建连接、异常处理、清理资源

2)Spring的JDBC模板

Spring承担了资源管理、异常处理工作

JdbcTemplate

NamedParameterTemplate

 

 

 

第11章:使用对象-关系映射持久化数据

 

 

第12章:使用NoSQL数据库

第13章:缓存数据

1、配置缓存管理器

Spring3.1内置了多个个缓存管理器实现

//Spring3.1提供
SimpleCM
NoOpCM
ConcurrentMapCM
CompositeCM
EhCM

//SpringData提供
RedisCM
GemfireCM

概览

ConcurrentMapCacheManager
EhcacheCacheManager
RedisCacheManager
使用多个CM

1)ConcurrentMapCM

以ConcurrentHashMap作为缓存存储,简单高效,对开发、测试友好,但它是基于内存的,与应用同生共死

JavaConfig方式

@Configuration
@EnableCaching //启用对注解驱动缓存的支持
public class CachingConfig {

  @Bean
  public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager(cm);
  }

}

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"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/cache
           http://www.springframework.org/schema/cache/spring-cache.xsd">

    <cache:annotation-driven/>

    <bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
    </bean>

</beans>

2)EhcacheCM

@Configuration
@EnableCaching
public class CachingConfig {

  @Bean
  public EhCacheCacheManager cacheManager(CacheManager cm) {
    return new EhCacheCacheManager(cm); //EhCache的CacheManager实例,被注入到Spring的EhCacheCacheManager中
  }

  //EhCacheManagerFactoryBean实现了Spring的FactoryBean接口
  //因此该方法注册到Spring上下文中的实例并非EhCacheManagerFactoryBean,而是一个CacheManager实例,这个实例将传给上个方法的形参
  @Bean
  public EhCacheManagerFactoryBean ehcache() {
    EhCacheManagerFactoryBean ehCacheFactoryBean = 
        new EhCacheManagerFactoryBean();
    ehCacheFactoryBean.setConfigLocation(
        new ClassPathResource("spittr/cache/ehcache.xml")); //读取xml文件进行EhCache自身的配置
    return ehCacheFactoryBean;
  }
  
}

ehcache.xml配置文件,参考:

Ehcache 中ehcache.xml 配置详解和示例

EhCache官网

<ehcache>
  <cache name="spittleCache"
          maxBytesLocalHeap="50m"
          timeToLiveSeconds="100">
  </cache>
</ehcache>

3)RedisCM

@Configuration
@EnableCaching
public class CachingConfig {

  @Bean
  public CacheManager cacheManager(RedisTemplate redisTemplate) {
    return new RedisCacheManager(redisTemplate);
  }

  @Bean
  public JedisConnectionFactory redisConnectionFactory() {
    JedisConnectionFactory jedisConnectionFactory = 
        new JedisConnectionFactory();
    jedisConnectionFactory.afterPropertiesSet();
    return jedisConnectionFactory;
  }

  @Bean
  public RedisTemplate<String,String> redisTemplate(
            RedisConnectionFactory redisCF)
  
}

2、支持缓存

1)为方法添加注解

2)使用xml声明缓存

 

 

第4部分:Spring集成

概述

系统需要通过远程调用服务来与其他系统进行交互。Spring使得我们使用远程服务时就像使用JavaBean一样简单。在客户端,Spring提供了代理工厂bean,无论是哪种远程调用模型都可以很方便的装配进应用中;并且Spring捕获了所有的RemoteExption-受查异常,并重新抛出RemoteAccessException-非受查异常。

但在分布式应用中,远程服务可能有性能瓶颈,因此还有另外一种选择:暴露RESTFul资源(下一章)

第15章:使用远程服务(over)

1、远程调用与Spring设计

远程调用是客户端与服务端之间的会话,当客户端需要一些功能并不在其实现范围之内,而需要调用是其他系统暴露的远程服务来完成。从表面上,RPC类似于调用一个本地对象的方法调用,二者都是同步操作,都会阻塞当前线程直到过程完毕。二者仅仅是距离问题,RPC调用就是执行流从一个应用传递给另一个应用。

而Spring的支持设计如下

客户端:Spring为多种远程调用模型提供风格一致的支持,它通过一个代理工厂Bean来将服务作为Spring所管理的Bean来配置到应用中,使得应用就像使用本地对象一样去使用。代理负责处理连接的细节并向远程服务发起调用;另外,远程调用异常RemoteException就异常本身而言是设计成受查异常,Spring代理会捕获它并抛出非受查异常RemoteAccessException(这和Spring设计数据访问异常体系是一致的思想)

服务端:利用远程导出器,支持多种远程调用模型将Spring管理的Bean发布为远程服务

 

2、常用的4种远程调用模型

概览

RMI
Hessian或Burlap
HttpInvoker
JAX-RPC和JAX-WS

1.RMI

由于RMI使用任意端口来交互,因此很难穿越防火墙,企业内部尚可,但互联网上运行(╥╯^╰╥);RMI是基于Java的,因此两端的应用必须是Java开发;RMI使用了Java的序列化机制,因此serialVersionUID必须一致

(1)导出服务

使用RmiServiceExporter将服务SpitterServiceImpl发布一个RMI服务

@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService){

    RmiServiceExporter rmiExporter = new RmiServiceExporter();
    rmiExporter.setService(spitterService); //服务实例
    rmiExporter.setServiceName("spitterService"); //服务名
    rmiExporter.setServiceInterface(SpitterService.class); //指定该服务所实现的接口

    //默认情况下,RSE会尝试绑定到本地机器1099端口上的RMI服务注册表
    //rmiExporter.setRegistryHost("rmi.spitter.com");
    //rmiExporter.setRegistryPort(1199);
    return rmiExporter;
}

(2)访问服务

使用RmiProxyFactoryBean为RMI服务创建代理

@Bean
public RmiProxyFactoryBean spitterService(){

    RmiProxyFactoryBean rmiProxy = new RmiProxyFactoryBean();
    rmiProxy.setServiceUrl('rmi://localhost/SpitterService');
    rmiProxy.setServiceInterface(SpitterService.class);
    return rmiProxy;
}

使用

@Autowired
private SpitterService spitterService;

客户端甚至不需要知道所处理的是一个RMI服务,它还是通过注入机制接受了一个service对象;代理捕获了该方法所有可能抛出的RemoteException受查型异常,并包装为非受查异常(即运行时异常RuntimeException)抛出,因此可以不编写try/catch。

2.Hessian或Burlap

二者相对于RMI来说,它们可以移植到其他非Java的应用中:PHP、Python、C++、C#等;解决了RMI中的防火墙渗透问题。但由于Hessian、Burlap采用私有的序列化机制,当数据模型非常复杂时,可能无法胜任

Hessian和Burlap对比

Hessian
    二进制消息 
    节约带宽   
Burlap
    基于xml消息,因此支持能解析xml的语言
    更具可读性

(1)导出服务

Hessian本身使用也不复杂,但是Spring可以提供基于AOP的系统级服务,如:声明式事务

//Hessian
@Bean
public HessianServiceExporter hessianExportedSpitterService(SpitterService service){

    HessianServiceExporter exporter = new HessianServiceExporter();
    exporter.setService(service); //服务实例
    //Hessian没有服务注册表,因此不需要为Hessian服务命名
    exporter.setServiceInterface(SpitterService.class); //指定该服务所实现的接口
    return exporter;
}

//Burlap
@Bean
public BurlapServiceExporter burlapExportedSpitterService(SpitterService service){

    BurlapServiceExporter exporter = new BurlapServiceExporter();
    exporter.setService(service); 
    exporter.setServiceInterface(SpitterService.class);
    return exporter;
}


二者的配置方式一模一样

除此之外,由于Hessian是基于HTTP,因此HessianServiceExporter实现为一个SpringMVC控制器。

配置DispatcherServlet拦截到.service请求

3种方式启动web应用,具体见第5章SpringMVC
    1.web.xml方式启动的web应用
        为DispatcherServlet添加一个Servlet映射
            <servlet-mapping>
                <servlet-name>spitter</servlet-name>
                <url-pattern>*.service</url-pattern>
            </servlet-mapping>

    2.实现WebApplicationInitializer方式启动的web应用
        将url模式作为映射,添加到ServletRegistration.Dynamic中
            ServletRegistration.Dynamic dispatcher = container.addServlet(
                    "appServlet",new DispatcherServlet(dispatcherServletContext));
            dispatcher.setLoadOnStartup(1);
            dispatcher.addMapping("/");
            dispatcher.addMaping("*.service");

    3.扩展AbstractDispatcherServletInitializer或AnnotationCinfogDispatcherServletInitializer
        重载getServletMappings()时,需要包含该映射
            @Override
            protected String[] getServletMappings(){
                
                return new String[] {"/","*.service"};
            }

交给hessianExportedSpitterService/burlapExportedSpitterService处理。

@Bean
public HandlerMapping hessianMapping(){

    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    Properties mappings = new Properties();
    mappings.setProperty("/spitter.service","hessianExportedSpitterService");
    mapping.setMappings(mappings);
    return mapping;
}

(2)访问服务

//在客户端这边,RMI、Hessian、Burlap三者的代码几乎一模一样

@Bean
public HessianProxyFactoryBean spitterService(){

    HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
    proxy.setServiceUrl('http://localhost:8080/Spitter/spitter.service');
    proxy.setServiceInterface(SpitterService.class);
    return proxy;
}

3.HttpInvoker

既解决了RMI问题,能够执行基于Http的远程调用并很好的穿透防火墙,也解决了H/B的问题,使用Java的序列化机制。但是它要求:C/S两房应用必须是Spring应用;必须是Java应用;serialVersionUID必须一致

(1)导出服务

示意图、代码与H/B思路完全相同,只是用HttpInvokerServiceExporter代替HessianServiceExporter,也需配置SpringMVC拦截

@Bean
public HttpInvokerServiceExporter httpInvokerExportedSpitterService(SpitterService service){

    HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
    exporter.setService(service); //服务实例
    //Hessian没有服务注册表,因此不需要为Hessian服务命名
    exporter.setServiceInterface(SpitterService.class); //指定该服务所实现的接口
    return exporter;
}

(2)访问服务

与前面三个的思路完全相同,只是用HttpInvokerProxyFactoryBean代替

@Bean
public HttpInvokerProxyFactoryBean spitterService(){

    HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
    proxy.setServiceUrl('http://localhost:8080/Spitter/spitter.service');
    proxy.setServiceInterface(SpitterService.class);
    return proxy;
}

4.JAX-RPC和JAX-WS

SOA:面向服务的架构。核心理念:应用程序应当被设计成依赖一组公共的核心服务,而不是每个应用都重新实现相同的功能。参考:SOA面向服务架构

Spring确实提供了一个SimpleJaxWsServiceExporter,这样就能像前面一样的方式来导出服务了,但这个JAX-WS(Java API for XML Web Service)导出器要求:JAX-WS运行时支持将端点发送到指定是地址上。目前只有SunJDK1.6自带的JAX-WS满足,其他实现并不能满足这个要求。因此,当使用其他JAX-WS实现时,就不能用这里的SimpleJaxWsServiceExporter

(1)导出服务

当对象的生命周期并非由Spring管理,而该对象的属性又需要注入Spring所管理的Bean时,就用SpringBeanAutowiringSupport。这里SpitterServiceEndpoint并非由Spring管理,它并未注入到Spring容器中,但是它需要一个SpitterService注入

编写自实现的JAX-WS端点并装配到容器中,该端点委托注入的spittleService来完成实际的工作

@WebSerive(serviceName="SpitterService")
public class SpitterServiceEndponit extends SpringBeanAutoWiringSupport {


    @Autowired
    private SpitterService spitterService;

    @WebMethod
    public void addSpittle(Spittle spittle){
        spitterService.addSpittle(spittleId);
    }

    @WebMethod
    public void deleteSpittle(long spittleId){
        spitterService.deleteSpittle(spittleId);
    }

    @WebMethod
    public List<Spittle> getRecentSpittles(int spittleCount){
        return spitterService.getRecentSpittles(spittleCount);
    }

    @WebMethod
    public List<Spittle> getSpittlesForSpitter(Spitter spitter){
        return spitterService.getSpittlesForSpitter(spitter);
    }
}

注意:当然,我们也可以在SpitterSerivceEndpoint类上添加@Component使它成为Spring组件,这样就不需要extends SpringBeanAutoWiringSupport类了

导出独立的JAX-WS端点

@Bean
public SimpleJaxWsServiceExporter jaxWsExporter(){
    SimpleJaxWsServiceExporter exporter = new SimpleJaxWsServiceExporter();
    
    //默认服务基本地址为http://localhost:8080/
    //因此SpitterServiceEndpoint的服务地址为:http://localhost:8080/SpitterService
    //可以调整服务基本地址
    //exporter.setBaseAddress("http://localhost:8888/services/");
    return exporter;
}

(2)访问服务

@Bean
public JaxWsPortProxyFactoryBean spitterService(){

    JaxWsPortProxyFactoryBean proxy = new JaxWsPortProxyFactoryBean();
    proxy.setWsdlDocument("http://localhost:8080/services/SpitterService?wsdl");
    proxy.setServiceName("spitterService");
    proxy.setPortName("spitterServiceHttpPort");
    proxy.setServiceInterface(SpitterService.class);
    proxy.setNamespaceUri("http://spitter.com"); //指定服务的命名空间
    return proxy;
}

其中与之前不太相同的有:通过WSDL来为服务创建代理。疑问:WSDL是什么

参考:WSDL详解

第16章:使用SpringMVC创建REST API

第17章:Spring消息

第18章:使用WebSocket和STOMP实现消息功能

第19章:使用Spring发送Email

第20章:使用JMX管理Spring Bean

第21章:借助SpringBoot简化Spring开发

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值