《Spring实战 第4版》
《Spring实战 第4版》
《Spring 实战 第 4 版》
第一章 Spring 之旅
依赖注入和 AOP
Spring 致力于简化企业级 Java 开发,促进代码的松耦合。成功的关键在于依赖注入和 AOP。
依赖注入:依赖注入现在已经演变成一项复杂的编程技巧或设计模式理念,但事实上并不是十分复杂。任何一个有实际意义的应用,都会由多个类相互之间进行写作来完成特定的业务逻辑。传统情况下,每个对象负责管理与自己相互协作的对象(它所依赖的对象)的引用,这带来的问题就是高耦合以及难以测试。通过 DI,对象的依赖关系将由系统中负责协调的第三方组件进行设定,对象无需自行创建或管理他们的依赖关系,依赖关系将被自动注入到它们需要的对象中。依赖注入的方式之一是在构造时候将任务作为构造器参数传入,即构造器传入。对象会在运行期间赋予他们它们所依赖的对象,依赖对象通常会通过接口了解所注入的对象,确保低耦合。
AOP,面向切面编程:DI 能够让相互协作的软件组件保持松散耦合,而 AOP 允许把遍布应用各处的功能分离出来形成可重用的组件。面向切面编程往往被定义为促使软件系统实现关注点分离的一项技术。系统由许多不同的组件组成,每一个组件各负责一块特定的功能。除了实现自身核心的功能之外,还经常承担着额外的职责,例如日志、事务管理和安全这样的系统服务经常融入到别的组件中去。他们会跨越系统的多个组件,所以这些系统服务通常被称为横切关注点。如果将这些关注点散布在各个模块中,组件会因为这些与自身核心业务无关的代码而变得混乱。借助 AOP,每一个核心应用都应该更加关注自身的业务,而不需要去了解涉及系统服务所带来的复杂问题,将安全、事务和日志关注点与核心业务逻辑相分离,抽象为切面。当 Spring 装配 bean 的时候,这些切面能够在运行期编织起来,非常有效地赋予 bean 新的行为。
第四章 面向切面的 Spring
面向切面编程
从接触 Spring 的第一天就接触到了面向切面编程(AOP)这个概念,可是究竟什么才是面向切面编程?它存在的意义是什么?
之前提到过,在软件开发中有一些功能被散布在应用中多处,这些功能从概念上是与应用的业务逻辑相分离(但往往会直接嵌入到应用的业务逻辑中),被称为横切关注点,例如日志,安全和事务等辅助功能。把这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的问题。
使用依赖注入(DI)可以管理和配置我们的应用对象,有助于应用对象之间的解耦,而 AOP 可以实现横切关注点与他们所影响的对象之间的解耦。切面能实现了横切关注点(跨多个应用对象的逻辑)的模块化,对于重用通用功能,它提供了一种取代继承和委托的更优方案。使用面向切面编程时,横切关注点可以被模块化为特殊的类,这些类被称为切面,面向切面编程本质上就是面向这些特殊的类。
描述 AOP 的常用术语有通知,切点和连接点等。通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)。切面是通知和切点的结合,通知和切点共同定义了切面的全部内容——它是什么,在何时何处完成功能。
第五章 构建 Spring Web 应用程序
重温 Spring MVC
Spring MVC 基于模型-视图-控制器(Model-View-Controller, MVC)模式实现,它能构建像 Spring 框架那样灵活和松耦合的 Web 应用程序。Spring 将请求在调度 Servlet、处理器映射、控制器以及视图解析器之间移动,每一个 Spring MVC 中的组件都有特定的目的,并没有看起来那么复杂。
请求从离开浏览器到获取响应返回,要经历很多站,在每一站都会留下一些信息的同时也会带上其他信息。在请求离开浏览器时,会带着信息来到 DispatcherServlet,这是一个前端控制器,它的任务是将请求发给 Spring MVC 控制器(Controller),控制器是一个用于处理请求的 Spring 组件。在成熟的程序中都会有多个控制器,所以 DispatcherServlet 需要知道应该将请求发给哪个控制器,因此 DispatcherServlet 会查询一个或多个处理器映射来确定前往位置。
一旦确定合适的控制器,DispatcherServlet 会将请求发送给选中的控制器,到了控制器,请求会卸下负载(就是携带的信息,例如用户提交的表单信息),然后等待控制器处理这些信息。事实上控制器本身很少甚至不处理,而是将业务逻辑委托给服务对象进行处理。在控制器完成逻辑处理之后,通常会产生一些信息,这些信息需要返回给用户并在浏览器上显示,这些信息被称为模型(model),不过一般信息需要发送给一个视图(view),以更好的方式返回。
控制器做的最后一件事就是将模型数据打包,将请求连同模型和视图名返回给 DispatcherServlet,DispatcherServlet 会使用视图解析器去匹配一个特定的视图实现,视图将模型数据渲染输出,通过响应对象返回客户端。
第六章 渲染 Web 视图
JSP 和 Thymeleaf
JSP 已经存在了很长的时间,在 Java Web 服务器中无所不在,但是它存在一些缺陷。JSP 最明显的问题在于它看起来像 HTML 或 XML,但其实并不是。大多数 JSP 模板都是采用 HTML 的形式,但掺杂了各种 JSP 标签库的标签,使其变得很混乱。虽然这些标签库能够以很便利的方式为 JSP 带来动态渲染的强大功能,但是它也摧毁了我们想维持一个格式良好的文档的可能性。
标签库和 JSP 缺乏良好格式的一个副作用就是它很少能够与其产生的 HTML 类似,在 Web 浏览器或 HTML 编辑其中查看未经渲染的 JSP 模板会令人非常困惑,这个结果是不完整的。同时 JSP 规范是与 Servlet 规范紧密耦合的,这就意味着 JSP 只能用在基于 Servlet 的 Web 应用之中。
Thymeleaf 是一项很有吸引力的技术,因为它能创建原始的模板,这些模板是纯 HTML,能够像静态的 HTML 那样以原始的方式编写和预览,并且能够在运行时渲染动态模型数据。除此之外,Thymeleaf 是与 Servlet 没有耦合关系的,这样它就能够用在 JSP 不能使用的领域。
第七章 Spring MVC 的高级技术
跨重定向请求传递数据
在提交表单处理完 POST 请求后,通常来讲一个最佳的实践就是执行一下重定向。除了其他的一些因素外,这样做能够防止用户点击浏览器的刷新按钮或后退箭头时,客户端重新执行危险的 POST 请求。
在控制器方法返回的视图名称中,可以借助"redirect:"。当控制器方法返回的 String 值以"redirect:"开头的话,那么这个 String 不是用来查找视图的,而是用来指导浏览器进行重定向的路径。
正在发起重定向的方法该如何发送数据给重定向的目标方法呢?一般来讲,当一个处理器方法完成之后,该方法所指定的模型数据将会复制到请求中,并作为请求中的属性,请求会转发(forword)到视图上进行渲染。因为控制器方法和视图处理的是同一个请求,所以在转发过程中,请求属性可以保存。但是当控制器的结果是重定向时,原始的请求就结束了,并且会发起一个新的 GET 请求。原始请求中饭所带有的模型数据也随着请求一起消亡,在新的请求属性中没有任何的模型数据。
也就是说,模型的属性是以请求属性的形式存放在请求中的,在重定向后无法存活。因此,对于重定向来说,模型并不能用来传递数据,这里有两种方案能够从发起重定向的方法传递数据给处理重定向方法中:
- 使用 URL 模板以路径变量和(或)查询参数的形式传递数据以路径变量的形式和参数传递数据: return "redirect: /test/{name}"; 按照这个写法就能正常运行,只不过稍微有些不妥,因为 name 的值是直接连接到重定向 String 上的。新的写法: // more code model.addAttribute( "name",user.getName() ); return "redirect: /test/{name}"; 这样将变量作为占位符填充到 URL 模板中,而不是直接连接到重定向 String 中,变量中所有不安全字符都会进行转义,这样更加安全。
- 通过 flash 属性发送数据通过上面的方法只能用来发送一些简单的值,如 String 和数字的值,如果想要发送更为复杂的值,就需要 flash 属性的帮助了。以发送 user 对象为例,使用方法: // more code model.addFlashAttribute( "user",user ); return " redirect: /test "; 在重定向执行之前,所有的 flash 属性都会复制到会话当中。在重定向之后,存在会话中的 flash 属性会被取出,并从会话转移到模型之中,处理重定向的方法就能从模型中访问 user 对象了。
第九章 保护 Web 应用
Spring Security
Spring Security 是一种基于 Spring AOP 和 Servlet 规范中的 Filter 实现的安全框架,为基于 Spring 的应用程序提供声明式安全保护,它能在 Web 请求级别和方法调用级别处理身份认证和授权。因为基于 Spring 框架,所以 Spring Security 充分利用了依赖注入和面向切面的技术。Spring Security 从两个角度来解决安全性问题,它使用 Servlet 规范中的 Filter 保护 Web 请求并限制 URL 级别的访问,还能够使用 Spring AOP 保护方法调用——借助于对象代理和试用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。
通过 HTTP 发送的数据没有经过加密,敏感信息要通过 HTTPS 来加密发送
跨站请求伪造,一个站点欺骗用户提交请求到其他服务器,就会发生 CSRF 攻击。Spring Security 通过一个同步 token 的方式来实现 CSRF,它将会拦截状态变化的请求并检查 CSRF token。
第十章 通过 Spring 和 JDBC 征服数据库
Spring 的数据访问
Spring 的目标之一就是允许我们在开发应用程序时,能够遵循面向对象原则中的“针对接口编程”,对数据访问的支持也不例外。为了避免持久化的逻辑分散到应用的各个组件中,最好将数据访问的功能放到一个或多个专注于此项任务的组件中,这样的组件通常称为数据访问对象(data access object, DAO)或 Repository。
为了避免应用与特定的数据访问策略耦合在一起,编写良好的 DAO 应该以接口的方式暴露功能。设计数据访问层的合理方式是:服务对象本身并不会处理数据访问,而是将数据访问委托给 DAO,DAO 接口确保其与服务对象的松耦合。这样的好处有:第一,使得服务对象易于测试;第二,数据访问层是以持久化技术无关的方式来进行访问的,切换持久化框架对应用程序其他部分带来的影响最小。
在 Java 中,JDBC 是与关系型数据库交互的最基本方式。JDBC 抛出 SQLException 的常见问题包括:应用程序无法连接数据库;要执行的查询存在语法错误;查询中所使用的表和/或列不存在;试图插入或更新的数据违反了数据库约束。与其他技术相比,使用 JDBC 能够很好地对数据访问的性能进行调优,JDBC 允许使用数据库的所有特性,而这是其他框架不支持甚至禁止的。相对于持久层框架,JDBC 能够让我们在更低的层次上处理数据,我们可以完全控制应用程序如何读取和管理数据,包括访问和管理数据库中单独的列。这种细粒度的数据访问方式在很多应用程序中是很方便的。
第十一章 使用对象-关系映射持久化数据
对象-关系映射持久化数据
随着应用程序变得越来越复杂,对持久化的需求也变得更复杂。这里介绍三种复杂的特性:
- 延迟加载:随着对象关系变得越来越复杂,有时候并不希望立即获取完整的对象间关系,延迟加载允许我们只在需要的时候获取数据。
- 预先抓取:与延迟加载相对,借助于预先抓取,可以使用一个查询获取完整的关联对象。预先查询的功能可以在一个操作中将他们全部从数据库中取出来,节省了多次查询的成本。
- 级联:更改数据库中的表时会同时修改其他表。
一些可用的框架提供了这样的服务,这些服务的通用名称是对象/关系映射(ORM)。在持久层使用 ORM 工具,可以节省数千行的代码和大量的开发时间。Spring 与最常用的两种 ORM 方案集成:Hibernate 和 JPA。
Hibernate 是一个开源持久化框架,他不仅提供了基本的对象关系映射,还提供了 ORM 工具所应具有的所有复杂功能,比如缓存、延迟加载、预先抓取以及分布式缓存。JPA 是基于 POJO 的持久化机制,它从 Hibernate 和 JDO 上借鉴了很多理念。在 Spring 中使用 JPA 需要现在 Spring 应用上下文中将实体管理器工厂按照 bean 的形式进行配置,然后编写基于 JPA 的 Repository。
第十二章 使用 NoSQL 数据库
常见的 NoSQl 数据库
关系型数据库是主流的数据存储形式,曾作为数据持久化领域的唯一可选方案,但是现在有多种不同的数据库,每一种都代表了不同形式的数据,并提供了适应多种领域模型的功能。
- MongoDB:最流行的开源文档数据库之一有一些数据的最佳表现形式是文档,也就是说,不要把这些数据分散到多个表、节点或实体中,将这些信息收集到一个非规范化(也就是文档)的结构中会更有意义。尽管文档之间可能彼此有关联,但是通常来讲,文档是独立的实体。能够按照这种方式优化并处理文档的数据库称之为文档数据库。文档数据库不是通用的数据库,它们所擅长的是一个很小的问题集。有些数据具有明显的关联关系,文档数据库并没有针对存储这样的数据进行优化。在订单数据应用上,传统的关系型数据库会将订单中的某些条目保存在另外一个数据库表中,通过外键进行应用,而在文档数据库中,这些条目只是同一个订单文档中内嵌的一部分,没有必要将这种关联关系持久化为文档。
- Neo4j:世界领先的开源图形数据库文档型数据库会将数据存储到粗粒度的文档中,而图数据库会将数据存储到多个细粒度的节点中,这些节点之间通过关系建立关联。图数据库中的一个节点通常会对应数据库中的一个概念(concept),它会具备描述节点状态的属性,连接两个节点的关联关系可能也会带有属性。按照最简单的形式,图数据库比文档数据库更加通用,有可能会成为关系型数据库的无模式替代方案。因为数据的结构是图,所以可以遍历关联关系以查找数据中你所关心的内容,这在其他数据库中是很难甚至无法实现的。
- Redis:Key-Value 存储 Redis 是一种特殊类型的数据库,它被称为 key-value 存储,即键值对存储,与 hash Map 有很大的相似性。
第十三章 缓存数据
Spring 对缓存的支持
缓存可以存储经常用到的信息,这样每次需要的时候,这些信息都是立即可用的。尽管 Spring 自身并没有实现缓存解决方案,但是它对缓存功能提供了声明式的支持,能够与多种流行的缓存实现进行集成,Spring 对缓存的支持有两种方式:注解驱动的缓存;XML 声明的缓存。
在本质上,使用 @EnableCaching 启用注解驱动缓存和使用 <cache: annotation-driven> 启用缓存的工作方式是相相同的,它们都会创建一个切面并触发 Spring 缓存注解的切点。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。
第十四章 保护方法应用
Spring Security 实现方法级保护
在 Spring Security 中实现方法级安全性的最常见办法是使用特定的注解,将这些注解应用到需要保护的方法上。Spring Security 提供了三种不同的安全注解:
- Spring Security 自带的 @Secured 注解
- JSR-250 的 @RolesAllowed 注解
- 表达式驱动的注解,包括 @PreAuthorize、@PostAuthorize、@PreFilter 和 @PostFilter
@Secured 和 @RolesAllowed 方案类似,能够基于用户所授予的权限限制对方法的访问。@PreAuthorize 和 @PostAuthorize 能够基于表达式的计算结果来限制方法的访问。有时候,需要保护的并不是对方法的调用,而是传入方法的数据和方法返回的数据,@PostFilter 可以在事后对方法的返回值进行过滤,而 @PreFilte 事先对方法的参数进行过滤
第十六章 使用 Spring MVC 创建 REST API
REST
REST(Representational State Transfer)——以信息为中心的表述性状态转移。REST 已成为替换传统 SOAP Web 服务的流行方案,SOAP 一般会关注行为和处理,而 REST 关注的是要处理的数据。对于 REST,有一种常见的错误就是将其视为“基于 URL 的 Web 服务”——将 REST 作为另一种类型的远程调用机制,就像 SOAP 一样,只不过是通过简单的 HTTP URL 来触发。事实上,REST 与 RPC 几乎没有任何关系。RPC 是面向服务的,并关注行为和动作;而 REST 是面向资源的,强调描述应用程序的事物和名词。
我们可以根据 REST 的构成部分来进行理解:
- 表述性(Representational):REST 资源实际上可以用各种形式来进行表述,包括 XML、JSON,甚至是 HTML
- 状态(State):当使用 REST 时,我们更关注的是资源的状态,并不是对资源采取的行为
- 转移(Transfer):REST 涉及到转移数据,以某种形式从一个应用转移到另一个应用
简单来说,REST 就是将资源的状态以最适合的形式从服务器端转移到客户端(或者反过来)。
在 REST 中,资源能够通过 URL 进行识别和定位。至于 RESTful URL 的结构并没有严格的规则,但是 URL 应该能够识别资源,而不是简单的发一条命令到服务器。