vlambda博客
学习文章列表

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

第 1 章 Spring Framework 5.0 和设计模式入门

本章将帮助您更好地理解带有模块的 Spring 框架,并使用负责 Spring 成功的设计模式。本章将涵盖 Spring 框架的每个主要模块。我们首先介绍 Spring 框架。我们将了解 Spring 5 中引入的新特性和增强功能。我们还将了解 Spring Framework 的主要模块中使用的设计模式。

在本章结束时,您将了解 Spring 的工作原理,以及 Spring 如何通过使用设计模式解决企业应用程序设计级别的常见问题。您将了解如何改善应用程序组件之间的松散耦合,以及如何通过使用带有设计模式的 Spring 来简化应用程序开发。

本章将涵盖以下主题:

  • Introduction of the Spring Framework
  • Simplifying application development using Spring and its pattern
    • Using the power of the POJO pattern
    • Injecting dependencies
    • Applying aspects to address cross-cutting concerns
    • Applying a template pattern to eliminate boilerplate code
  • Creating a Spring container for containing beans using the Factory pattern
    • Creating a container with an application context
    • The life of a bean in the container
  • Spring modules
  • New features in Spring Framework 5.0

介绍 Spring 框架


在 Java 的早期,有许多用于企业应用程序的较重企业 Java 技术,它们为程序员提供企业解决方案。然而,维护应用程序并不容易,因为它与框架紧密耦合。几年前,除了 Spring,所有的 Java 技术都比较重,比如 EJB。当时,Spring 作为一种替代技术被引入,专门为 EJB 设计,因为与其他现有 Java 技术相比,Spring 提供了一种非常简单、更精简和更轻量的编程模型。 Spring 通过使用许多可用的设计模式使这成为可能,但它专注于 Plain Old Java Object (POJO) 编程模型。该模型为 Spring 框架提供了简单性。它还支持诸如 依赖注入 (DI) 之类的想法模式和 Aspect-Oriented Programming (AOP) Proxy 模式和装饰器模式。

Spring Framework 是一个开源应用程序框架和基于 Java 的平台,为开发企业 Java 应用程序提供全面的基础架构支持。所以开发者不需要关心应用的基础设施;他们应该专注于应用程序的业务逻辑,而不是处理应用程序的配置。所有基础设施、配置和元配置文件,无论是基于 Java 的配置还是基于 XML 的配置,都由 Spring Framework 处理。所以这个框架让你在使用 POJO 编程模型而不是非侵入式编程模型构建应用程序时更加灵活。

Spring Inversion of Control (IoC) 容器是< span>整个框架的核心。它有助于将应用程序的不同部分粘合在一起,从而形成一个连贯的架构。 Spring MVC 组件可用于构建非常灵活的 Web 层。 IOC 容器使用 POJO 简化了业务层的开发。

Spring 简化了应用程序开发并消除了对其他 API 的大量依赖。让我们看看作为应用程序开发人员如何从 Spring 平台中受益的一些示例:

  • All application classes are simple POJO classes--Spring is not invasive. It does not require you to extend framework classes or implement framework interfaces for most use cases.
  • Spring applications do not require a Java EE application server, but they can be deployed on one.
  • You can execute a method in a database transaction by using transaction management in Spring Framework without having any third-party transactional API.
  • Using Spring, you can use a Java method as a request handler method or remote method, like a service() method of a servlet API, but without dealing with the servlet API of the servlet container.
  • Spring enables you to use a local java method as a message handler method without using a Java Message Service (JMS) API in the application.
  • Spring also enables you to use the local java method as a management operation without using a Java Management Extensions (JMX) API in the application.
  • Spring serves as a container for your application objects. Your objects do not have to worry about finding and establishing connections with each other.
  • Spring instantiates the beans and injects the dependencies of your objects into the application--it serves as a life cycle manager of the beans.

使用 Spring 及其模式简化应用程序开发


使用传统 Java 平台开发企业 应用程序 在将基本构建块组织为单个组件以在应用程序中重用时有很多限制。为基本和通用功能创建可重用组件是最佳设计实践,因此您不能忽视它。为了解决应用程序中的可重用性问题,您可以使用各种设计模式,如工厂模式、抽象工厂模式、构建器模式、装饰器模式和服务定位器模式,将基本构建块组合成一个连贯的整体,例如类和对象实例,以促进组件的可重用性。这些模式解决了常见的递归应用程序问题。 Spring Framework 只是在内部实现了这些模式,为您提供了一个以正式方式使用的基础设施。

企业应用程序开发有很多复杂性,但 Spring 就是为了解决这些问题而创建的,并且可以为开发人员简化流程。 Spring 不仅限于服务器端开发——它还有助于简化有关构建项目、可测试性和松散耦合的事情。 Spring 遵循 POJO 模式,即一个 Spring 组件可以是任何类型的 POJO。组件是一段独立的代码,理想情况下可以在多个应用程序中重用。

由于本书重点介绍 Spring 框架为简化 Java 开发而采用的所有设计模式,我们需要讨论 或者至少提供一些设计模式的基本实现和考虑,以及为企业应用程序开发设计基础架构的最佳实践。 Spring 使用以下策略使 Java 开发变得简单和可测试:

  • Spring uses the power of the POJO pattern for lightweight and minimally invasive development of enterprise applications
  • It uses the power of the dependency injection pattern (DI pattern) for loose coupling and makes a system interface oriented
  • It uses the power of the Decorator and Proxy design pattern for declarative programming through aspects and common conventions
  • It uses the power of the Template Design pattern for eliminating boilerplate code with aspects and templates

在本章中,我将解释这些想法,并展示 Spring 如何简化 Java 开发的具体示例。让我们开始探索 Spring 如何通过使用 POJO 模式鼓励面向 POJO 的开发来保持微创。

使用 POJO 模式的强大功能

还有许多其他用于 Java 开发的 框架,它们通过强制您扩展或实现它们现有的类或接口之一来锁定您; Struts、Tapestry 和早期版本的 EJB 都有这种方法。这些框架的编程模型基于侵入模型。这使您的代码更难找到系统中的错误,有时它会使您的代码难以理解。但是,如果您使用 Spring Framework,则不需要实现或扩展其现有的类和接口,因此这只是基于 POJO 的实现,遵循非侵入式编程模型。它使您的代码更容易找到系统中的错误,并使代码易于理解。

Spring 允许您使用非常简单的非 Spring 类进行编程,这意味着不需要实现特定于 Spring 的类或接口,因此基于 Spring 的应用程序中的所有类都只是 POJO。这意味着您可以在不依赖 Spring 库的情况下编译和运行这些文件;您甚至无法识别 Spring Framework 正在使用这些类。在基于 Java 的配置中,您将使用 Spring 注解,这是基于 Spring 的应用程序的最坏情况。

让我们借助以下示例来看看这个:

    package com.packt.chapter1.spring; 
    public class HelloWorld { 
      public String hello() { 
        return "Hello World"; 
      } 
    } 

前面的类是一个简单的 POJO 类,没有与框架相关的特殊指示或实现,使其成为 Spring 组件。因此,此类在 Spring 应用程序中的功能与在非 Spring 应用程序中的功能相同。这就是 Spring 的非侵入式编程模型的美妙之处。 Spring 支持 POJO 的另一种方式是使用 DI 模式与其他 POJO 协作。让我们看看 DI 如何帮助解耦组件。

在 POJO 之间注入依赖关系

依赖注入这个术语并不新鲜——它被 PicoContainer 使用。依赖注入是一种设计模式,它促进了 Spring 组件之间的松散耦合——即不同的协作 POJO 之间。因此,通过将 DI 应用于您的复杂编程,您的代码将变得更简单、更易于理解和更易于测试。

在您的应用程序中,许多对象根据您的要求为特定功能协同工作。对象之间的这种协作实际上称为依赖注入。在工作组件之间注入依赖关系有助于您对应用程序中的每个组件进行单元测试,而无需紧密耦合。

在一个工作的应用程序中,最终用户想要的是看到输出。为了创建输出,应用程序中的一些对象一起工作,有时是耦合的。所以在编写这些复杂的应用类时,要考虑这些类的可重用性,并使这些类尽可能独立。这是编码的最佳实践,将帮助您独立地对这些类进行单元测试。

DI 如何工作并使开发和测试变得容易

让我们看看您的应用程序中的 DI 模式 implementation。它使事情易于理解、松散耦合并且可在整个应用程序中测试。假设我们有一个简单的应用程序(比您可能在大学课程中制作的 Hello World 示例更复杂)。每个班级都在共同努力执行一些业务任务并帮助建立业务需求和期望。这意味着应用程序中的每个类都对业务任务以及其他协作对象(其依赖项)负责衡量。让我们看看下面的图像。对象之间的这种依赖关系会在依赖对象之间产生复杂性和紧密耦合:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

TransferService 组件传统上依赖于另外两个组件:TransferRepository 和 AccountRepository

一个典型的应用系统由几个部分组成,这些部分协同工作以执行一个用例。例如,考虑 TransferService 类,如下所示。

TransferService 使用直接实例化:

    package com.packt.chapter1.bankapp.transfer; 
    public class TransferService { 
      private AccountRepository accountRepository; 
      public TransferService () { 
        this.accountRepository = new AccountRepository(); 
      } 
      public void transferMoney(Account a, Account b) { 
        accountRepository.transfer(a, b); 
      } 
    } 

TransferService 对象需要一个 AccountRepository 对象才能从帐户 a 进行资金转账记入 b。因此,它直接创建 AccountRepository 对象的实例并使用它。但是直接实例化会增加耦合并分散应用程序中的对象创建代码,从而难以维护并且难以为 TransferService 编写单元测试,因为在这种情况下,每当您想使用 assert TransferService 类的 transferMoney() 方法code> 进行单元测试,则该测试不太可能调用 AccountRepository 类的 transfer() 方法。但是开发者不知道 AccountRepositoryTransferService 类的依赖;至少,开发人员无法使用单元测试来测试 TransferService 类的 transferMoney() 方法。

在企业应用程序中,耦合是非常危险的,它会将您推到将来您将无法对应用程序进行任何增强的情况,在这种应用程序中任何进一步的更改都会产生很多错误,并且在哪里修复这些错误可能会产生新的错误。紧密耦合的组件是这些应用程序中出现主要问题的原因之一。不必要的紧耦合代码使您的应用程序无法维护,并且随着时间的推移,它的代码将不会被重用,因为其他开发人员无法理解它。但有时企业应用程序需要一定程度的耦合,因为在现实世界中不可能实现完全解耦的组件。应用程序中的每个组件对角色和业务需求都有一定的责任,以至于应用程序中的所有组件都必须了解其他组件的责任。这意味着有时耦合是必要的,但我们必须非常小心地管理所需组件之间的耦合。

对依赖组件使用工厂助手模式

让我们尝试使用工厂模式的依赖对象的 another 方法。该设计模式基于 GOF 工厂设计模式,使用工厂方法创建对象实例。所以这个方法实际上是集中了new操作符的使用。它根据客户端代码提供的信息创建对象实例。这种模式在依赖注入策略中被广泛使用。

TransferService 使用工厂助手:

    package com.packt.chapter1.bankapp.transfer; 
    public class TransferService { 
      private AccountRepository accountRepository; 
      public TransferService() { 
        this.accountRepository = 
          AccountRepositoryFactory.getInstance("jdbc"); 
      } 
      public void transferMoney(Account a, Account b) { 
        accountRepository.transfer(a, b); 
      } 
    } 

在前面的代码中,我们使用工厂模式创建了一个AccountRepository的对象。在软件工程中,应用程序设计和开发的最佳实践之一是 program-to-interface (P2I)。根据这种做法,具体类必须实现在调用者的客户端代码中使用的接口,而不是使用具体类。通过使用 P2I,您可以改进上述代码。因此,我们可以轻松地将其替换为对客户端代码影响很小的接口的不同实现。所以编程到接口为我们提供了一种涉及低耦合的方法。换句话说,没有直接依赖于导致低耦合的具体实现。让我们看看下面的代码。这里,AccountRepository 是一个接口而不是一个类:

    public interface AccountRepository{ 
      void transfer(); 
      //other methods 
    } 

所以我们可以根据我们的要求来实现它,它依赖于客户的基础设施。假设我们在使用 JDBC API 的开发阶段想要一个 AccountRepository。我们可以提供 AccountRepositry 接口的 JdbcAccountRepositry 具体实现,如下所示:

    public class JdbcAccountRepositry implements AccountRepositry{ 
      //...implementation of methods defined in AccountRepositry 
      // ...implementation of other methods 
    } 

在这种模式中,对象由工厂类创建,以便于维护,这避免了将创建对象的代码分散到其他业务组件中。使用工厂助手,还可以使对象创建可配置。这种技术提供了一种紧密耦合的解决方案,但我们仍在为业务组件添加工厂类以获取协作组件。那么让我们在下一节看看DI模式,看看如何解决这个问题。

对依赖组件使用 DI 模式

根据 DI 模式,依赖的 objects 在某些工厂或第三方创建对象时被赋予它们的依赖关系。这个工厂协调系统中的每个对象,使得每个依赖对象都不希望创建它们的依赖关系。这意味着我们必须专注于定义依赖关系,而不是解决企业应用程序中协作对象的依赖关系。让我们看看下面的图像。您将了解到依赖项被注入到需要它们的对象中:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

应用程序中不同协作组件之间的依赖注入

为了说明这一点,让我们看看下一节中的 TransferService -- TransferServiceAccountRepositoryTransferRepository。在这里,TransferService 能够通过 TransferRepository 的任何类型实现来转账,也就是说,我们可以使用 JdbcTransferRepositoryJpaTransferRepository,具体取决于部署环境。

TransferServiceImpl 足够灵活,可以处理它给出的任何 TransferRepository

    package com.packt.chapter1.bankapp; 
    public class TransferServiceImpl implements TransferService { 
      private TransferRepository transferRepository; 
      private AccountRepository  accountRepository; 
      public TransferServiceImpl(TransferRepository transferRepository,
       AccountRepository  accountRepository) { 
         this.transferRepository =
          transferRepository;//TransferRepository is injected 
         this.accountRepository  = accountRepository; 
         //AccountRepository is injected 
       } 
       public void transferMoney(Long a, Long b, Amount amount) { 
         Account accountA = accountRepository.findByAccountId(a); 
         Account accountB = accountRepository.findByAccountId(b); 
         transferRepository.transfer(accountA, accountB, amount); 
       } 
    } 

在这里您可以看到 TransferServiceImpl 没有创建自己的存储库实现。相反,我们在构造时将存储库的实现作为构造函数参数给出。这是一种称为 构造函数注入 的 DI。在这里,我们将存储库接口类型作为构造函数的参数传递。现在 TransferServiceImpl 可以使用存储库的任何实现,无论是 JDBC、JPA 还是模拟对象。关键是 TransferServiceImpl 没有耦合到任何特定的存储库实现。使用哪种存储库将金额从一个帐户转移到另一个帐户并不重要,只要它实现了存储库接口即可。如果您使用 Spring Framework 的 DI 模式,松散耦合是主要优势之一。 DI 模式总是促进 P2I,因此每个对象通过其关联接口而不是关联实现来了解其依赖关系,因此可以轻松地将依赖关系与该接口的另一个实现交换,而不是更改为其依赖的类实现。

Spring 为从它的各个部分组装这样一个应用程序系统提供了支持:

  • Parts do not worry about finding each other
  • Any part can easily be swapped out

通过创建应用程序部件或组件之间的关联来组装应用程序系统的方法称为wiring。在 Spring 中,有 许多 方法将协作组件连接在一起以构成应用程序系统。例如,我们可以使用 XML 配置文件或 Java 配置文件。

现在让我们看看如何将TransferRepositoryAccountRepository的依赖注入到一个TransferService 与弹簧:

    <?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="transferService" class="com.packt.chapter1.bankapp.service.TransferServiceImpl"> 
         <constructor-arg ref="accountRepository"/> 
         <constructor-arg ref="transferRepository"/> 
    </bean> 
    <bean id="accountRepository" class="com. packt.chapter1.bankapp.repository.JdbcAccountRepository"/> 
    <bean id="transferRepository" class="com. packt.chapter1.bankapp.repository.JdbcTransferRepository"/>     
    
    </beans> 

这里,TransferServiceImplJdbcAccountRepositoryJdbcTransferRepository在Spring中被声明为bean .在 TransferServiceImpl bean 的情况下,它被构造,传递对 AccountRepository 的引用TransferRepository bean 作为构造函数参数。您可能想知道 Spring 还允许您使用 Java 表达相同的配置。

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

    package com.packt.chapter1.bankapp.config; 
 
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.Configuration; 
 
    import com.packt.chapter1.bankapp.repository.AccountRepository; 
    import com.packt.chapter1.bankapp.repository.TransferRepository; 
    import 
     com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository; 
    import 
     com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository; 
    import com.packt.chapter1.bankapp.service.TransferService; 
    import com.packt.chapter1.bankapp.service.TransferServiceImpl; 
 
    @Configuration 
    public class AppConfig { 
    
     @Bean 
     public TransferService transferService(){ 
       return new TransferServiceImpl(accountRepository(),
       transferRepository()); 
     } 
     @Bean 
     public AccountRepository accountRepository() { 
       return new JdbcAccountRepository(); 
     } 
     @Bean 
     public TransferRepository transferRepository() { 
       return new JdbcTransferRepository(); 
     } 
    } 

无论您使用的是基于 XML 的配置还是基于 Java 的配置,依赖注入模式的好处都是相同的:

  • Dependency injection promotes loose coupling. You can remove hard-coded dependencies with best practice P2I, and you could provide dependencies from outside the application by using the Factory pattern and its built-in swappable and pluggable implementation
  • The DI pattern promotes the composition design of object-oriented programming rather than inheritance programming

虽然 TransferService 依赖于一个 AccountRepositoryTransferRepository,但它并不关心关于应用程序中使用的 AccountRepositoryTransferRepository 的实现类型(JDBC 或 JPA)。只有 Spring 通过其配置(基于 XML 或 Java)知道所有组件如何组合在一起并使用 DI 模式使用它们所需的依赖项进行实例化。 DI 可以在不更改依赖类的情况下更改这些依赖项——也就是说,我们可以使用 JDBC 实现或 JPA 实现而不更改 AccountService 的实现。

在 Spring 应用程序中,应用程序上下文的实现(Spring 为基于 Java 的实现提供 AnnotationConfigApplicationContext,为基于 XML 的实现提供 ClassPathXmlApplicationContext ) 加载 bean 定义并将它们连接到 Spring 容器中。 Spring 中的应用程序上下文在启动时为应用程序创建并连接 Spring bean。使用基于 Java 的配置查看 Spring 应用程序上下文的实现——它加载 Spring 配置文件(Java 的 AppConfig.javaSprig .xml for XML)位于应用程序的类路径中。在以下代码中,TransferMain 类的 main() 方法使用了 AnnotationConfigApplicationContext< /code> 类加载配置类 AppConfig.java 并获取 AccountService 类的对象。

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

    package com.packt.chapter1.bankapp; 
 
    import org.springframework.context.ConfigurableApplicationContext; 
    import 
     org.springframework.context.annotation
     .AnnotationConfigApplicationContext; 
 
    import com.packt.chapter1.bankapp.config.AppConfig; 
    import com.packt.chapter1.bankapp.model.Amount; 
    import com.packt.chapter1.bankapp.service.TransferService; 
 
    public class TransferMain { 
 
      public static void main(String[] args) { 
        //Load Spring context 
        ConfigurableApplicationContext applicationContext = 
          new AnnotationConfigApplicationContext(AppConfig.class); 
         //Get TransferService bean 
         TransferService transferService = 
          applicationContext.getBean(TransferService.class); 
           //Use transfer method 
         transferService.transferAmmount(100l, 200l,
          new Amount(2000.0)); 
         applicationContext.close(); 
      } 
 
    }    

这里我们快速介绍一下依赖注入模式。在本书的后续章节中,您将了解更多关于 DI 模式的知识。现在让我们看一下使用 Spring 的声明式编程模型通过切面和代理模式来简化 Java 开发的另一种方法。

为横切关注点应用方面

在 Spring 应用程序中,DI 模式为我们提供了协作软件组件之间的松散耦合,但 Spring 中的 Aspect-Oriented Programming (Spring AOP) 使您能够捕获在整个应用程序中重复的常见功能。所以我们可以说 Spring AOP 提倡松散耦合并允许横切关注点(如下所列)以最优雅的方式分离。它允许通过声明透明地应用这些服务。使用 Spring AOP,可以编写 自定义方面并以声明方式配置它们。

应用程序中 许多 位置所需的通用功能是:

  • Logging and tracing
  • Transaction management
  • Security
  • Caching
  • Error handling
  • Performance monitoring
  • Custom business rules

此处列出的组件不是您的核心应用程序的一部分,但这些组件具有一些额外的职责,通常称为横切关注点,因为它们倾向于跨越系统中的多个组件,超出其核心职责。如果你把这些组件和你的核心功能放在一起,从而在没有模块化的情况下实现横切关注点,它将有两个主要问题:

  • Code tangling: A coupling of concerns means that a cross-cutting concern code, such as a security concern, a transaction concern, and a logging concern, is coupled with the code for business objects in your application.
  • Code scattering: Code scattering refers to the same concern being spread across modules. This means that your concern code of security, transaction, and logging is spread across all modules of the system. In other words, you can say there is a duplicity of the same concern code across the system.

下图说明了这种复杂性。业务对象与横切关注点过于密切相关。每个对象不仅知道它正在被记录、保护并参与事务上下文,而且每个对象还负责执行仅分配给它的那些服务:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

横切关注点,例如日志记录、安全性和事务处理,通常分散在模块中,而这些任务不是它们的主要关注点

Spring AOP 实现了横切关注点的模块化,以避免缠结和分散。您可以将这些模块化的关注点以声明的方式应用到应用程序的核心业务组件上,而不会影响上述组件。这些方面确保 POJO 保持清晰。 Spring AOP 通过使用代理设计模式使这个魔法成为可能。我们将在本书后面的章节中更多地讨论代理设计模式。

Spring AOP 的工作原理

以下几点描述了 Spring AOP 的工作:

  • Implement your mainline application logic: Focusing on the core problem means that, when you are writing the application business logic, you don't need to worry about adding additional functionalities, such as logging, security, and transaction, between the business codes-Spring AOP takes care of it.
  • Write aspects to implement your cross-cutting concerns: Spring provides many aspects out of the box, which means you can write additional functionalities in the form of the aspect as independent units in Spring AOP. These aspects have additional responsibilities as cross-cutting concerns beyond the application logic codes.
  • Weave the aspects into your application: Adding the cross-cutting behaviors to the right places, that is, after writing the aspects for additional responsibilities, you could declaratively inject them into the right places in the application logic codes.

我们来看一个 Spring 中 AOP 的图解:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

基于 AOP 的系统演化——这让应用程序组件专注于其特定的业务功能

在上图中,Spring AOP 将横切关注点(例如安全、事务和日志记录)从业务模块中分离出来,即 BankServiceCustomerServiceReportingService。这些横切关注点在应用程序运行时应用于业务模块的预定义点(上图中的条纹)。

假设您要使用 TransferServicetransferAmmount() 方法之前和之后记录消息class="literal">LoggingAspect。以下清单显示了您可能使用的 LoggingAspect 类。

LoggingAspect 调用用于为 TransferService 记录系统:

    package com.packt.chapter1.bankapp.aspect; 
 
    import org.aspectj.lang.annotation.After; 
    import org.aspectj.lang.annotation.Aspect; 
    import org.aspectj.lang.annotation.Before; 
 
    @Aspect 
    public class LoggingAspect { 
    
     @Before("execution(* *.transferAmount(..))") 
     public void logBeforeTransfer(){ 
       System.out.println("####LoggingAspect.logBeforeTransfer() 
       method called before transfer amount####"); 
     } 
    
     @After("execution(* *.transferAmount(..))") 
     public void logAfterTransfer(){ 
       System.out.println("####LoggingAspect.logAfterTransfer() method
       called after transfer amount####"); 
     } 
    } 

要将 LoggingAspect 转换为切面 bean,您只需在 Spring 配置文件中将其声明为切面 bean。此外,要使其成为一个方面,您必须将 @Aspect 注释添加到此类。这是更新后的 AppConfig.java 文件,修改后将 LoggingAspect 声明为方面。

LoggingAspect 声明为切面并启用 Spring AOP 的 Apsect 代理功能:

    package com.packt.chapter1.bankapp.config; 
 
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.Configuration; 
    import
     org.springframework.context.annotation.EnableAspectJAutoProxy; 
 
    import com.packt.chapter1.bankapp.aspect.LoggingAspect; 
    import com.packt.chapter1.bankapp.repository.AccountRepository; 
    import com.packt.chapter1.bankapp.repository.TransferRepository; 
    import
     com.packt.chapter1.bankapp.repository.jdbc.JdbcAccountRepository; 
    import
     com.packt.chapter1.bankapp.repository.jdbc.JdbcTransferRepository; 
    import com.packt.chapter1.bankapp.service.TransferService; 
    import com.packt.chapter1.bankapp.service.TransferServiceImpl; 
 
    @Configuration 
    @EnableAspectJAutoProxy 
    public class AppConfig { 
    
      @Bean 
      public TransferService transferService(){ 
        return new TransferServiceImpl(accountRepository(),
        transferRepository()); 
      } 
      @Bean 
      public AccountRepository accountRepository() { 
        return new JdbcAccountRepository(); 
      } 
      @Bean 
      public TransferRepository transferRepository() { 
        return new JdbcTransferRepository(); 
      } 
      @Bean 
      public LoggingAspect loggingAspect() { 
        return new LoggingAspect(); 
      } 
    } 

在这里,我们使用 Spring 的基于 Java 的 AOP 配置将 LoggingAspect bean 声明为切面。首先,我们将 LoggingAspect 声明为 bean。然后我们用 @Aspect 注释对该 bean 进行注释。

我们用 @Before 注释对 LoggingAspectlogBeforeTransfer() 进行注释,以便在执行 transferAmount() 之前调用此方法。这称为建议之前。然后,我们用 @After 注解对 LoggingAspectanother 方法进行注解,以声明 logAfterTransfer() 方法应该transferAmount() 执行后被调用。这称为建议后

@EnableAspectJAutoProxy 用于在应用程序中启用 Spring AOP 功能。这个注解实际上强制你对spring配置文件中定义的一些组件应用代理。稍后我们将在 第 6 章中详细讨论 Spring AOP,使用代理和装饰器模式的Spring Aspect Oriented Programming。现在,知道你已经要求 Spring 调用 logBeforeTransfer()logAferTransfer() 就足够了TransferService 类的 transferAmount() 方法前后的="literal">LoggingAspect。现在,从这个例子中可以看出两个重要的点:

  • LoggingAspect is still a POJO (if you ignore the @Aspect annotation or are using XML-based configuration)--nothing about it indicates that it's to be used as an aspect.
  • It is important to remember that LoggingAspect can be applied to TransferService without TransferService needing to explicitly call it. In fact, TransferService remains completely unaware of the existence of LoggingAspect.

让我们转向另一种 Spring 简化 Java 开发的方式。

应用模板模式消除样板代码

在企业应用程序的某一时刻,我们看到了一些看起来像我们之前在同一个应用程序中编写过的代码。这实际上是样板代码。我们经常不得不在同一个应用程序中一次又一次地编写代码,以完成应用程序不同部分的共同需求。不幸的是,有很多地方 Java API 涉及到一堆样板代码。当使用 JDBC 从数据库中查询数据时,可以看到样板代码的一个常见示例。如果您曾经使用过 JDBC,那么您可能已经在代码中编写了一些处理以下内容的内容:

  • Retrieving a connection from the connection pool
  • Creating a PreparedStatement object
  • Binding SQL parameters
  • Executing the PreparedStatement object
  • Retrieving data from the ResultSet object and populating data container objects
  • Releasing all database resources

让我们看看下面的代码,它包含 Java 的 JDBC API 的样板代码:

    public Account getAccountById(long id) { 
      Connection conn = null; 
      PreparedStatement stmt = null; 
      ResultSet rs = null; 
      try { 
        conn = dataSource.getConnection(); 
        stmt = conn.prepareStatement( 
          "select id, name, amount from " + 
          "account where id=?"); 
        stmt.setLong(1, id); 
        rs = stmt.executeQuery(); 
        Account account = null; 
        if (rs.next()) { 
          account = new Account(); 
          account.setId(rs.getLong("id")); 
          account.setName(rs.getString("name")); 
          account.setAmount(rs.getString("amount")); 
        } 
        return account; 
      } catch (SQLException e) { 
      } finally { 
          if(rs != null) { 
            try { 
              rs.close(); 
            } catch(SQLException e) {} 
          } 
          if(stmt != null) { 
            try { 
              stmt.close(); 
            } catch(SQLException e) {} 
          } 
          if(conn != null) { 
            try { 
              conn.close(); 
            } catch(SQLException e) {} 
          } 
        } 
      return null; 
    } 

在上述代码中,我们可以看到 JDBC 代码向数据库查询帐户名称和金额。对于这个简单的任务,我们必须创建一个连接,然后创建一个语句,最后查询结果。我们还必须捕获 SQLException,这是一个检查异常,即使如果它被抛出,您也无能为力。最后,我们必须清理混乱,关闭连接语句和结果集。这也可能迫使它处理 JDBC 的异常,因此您也必须在此处捕获 SQLException。这种样板代码严重损害了可重用性。

Spring JDBC 使用模板设计模式解决了样板代码的问题,并通过删除模板中的通用代码使生活变得非常简单。这使得数据访问代码非常干净,并防止了诸如连接泄漏等烦人的问题,因为 Spring 框架确保所有数据库资源都被正确释放。

Spring中的模板设计模式

让我们看看如何在春季使用 Template 设计模式:

  • Define the outline or skeleton of an algorithm
  1. Leave the details for specific implementations until later.
  2. Hide away large amounts of boilerplate code.
  • Spring provides many template classes:
  • JdbcTemplate
  • JmsTemplate
  • RestTemplate
  • WebServiceTemplate
  • Most hide low-level resource management

让我们看看我们之前在 Spring 的 JdbcTemplate 中使用的相同代码,以及它如何删除样板代码。

使用 JdbcTemplates 让您的代码专注于任务:

    public Account getAccountById(long id) { 
      return jdbcTemplate.queryForObject( 
        "select id, name, amoount" + 
        "from account where id=?", 
         new RowMapper<Account>() { 
           public Account mapRow(ResultSet rs, 
            int rowNum) throws SQLException { 
              account = new Account(); 
              account.setId(rs.getLong("id")); 
              account.setName(rs.getString("name")); 
              account.setAmount(rs.getString("amount")); 
              return account; 
            } 
         }, 
      id); 
    } 

正如你在前面的代码中看到的,这个新版本的 getAccountById() 与样板代码相比要简单得多,这里的方法主要是从数据库而不是创建数据库连接,创建语句,执行查询,处理 SQL 异常,最后关闭连接。使用模板,您必须提供 SQL 查询和一个 RowMapper 用于将结果集数据映射到模板的 queryForObject( ) 方法。模板负责为这个操作做所有事情,比如数据库连接等等。它还在框架后面隐藏了很多样板代码。

在本节中,我们已经看到 Spring 如何利用面向 POJO 的开发和模式(例如 DI 模式、使用 Aspect-using Proxy 模式和模板方法设计模式)的力量来攻击 Java 开发的复杂性。

在下一节中,我们将了解如何使用 Spring 容器在应用程序中创建和管理 Spring bean。

使用 Spring 容器通过工厂模式管理 bean


Spring 为我们提供了一个容器,我们的应用程序对象就存在于这个 Spring 容器中。如下图所示,这个容器负责创建和管理对象:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

在 Spring 应用程序中,我们的应用程序对象存在于这个 Spring 容器中

Spring Container 也将许多 Object 根据其配置。它配置了一些初始化参数,并从头到尾管理它们的完整生命周期。

基本上,有 两种 不同类型的 Spring 容器:

  • Bean factory
  • Application contexts

豆厂

在 Spring Framework 中,org.springframework.beans.factory.BeanFactory 接口提供了 bean factory,它是一个 Spring IoC 容器。 XmlBeanFactory 是这个接口的一个实现类。此容器从 XML 文件中读取配置元数据。它基于 GOF 工厂方法设计模式——它以复杂的方式创建、管理、缓存和连接应用程序对象。 bean 工厂只是一个对象池,对象由配置创建和管理。对于小型应用来说,这已经足够了,但是企业应用的需求更多,所以spring提供了另外一个版本的spring容器,功能更多。

在下一节中,我们将了解应用程序上下文以及 Spring 如何在应用程序中创建它。

应用程序上下文

在 Spring Framework 中,org.springframework.context.ApplicationContext 接口也提供了 Spring 的 IoC 容器。它只是 bean 工厂的一个包装器,提供一些额外的应用程序上下文服务,例如对 AOP 的支持,因此也提供声明性事务、安全性和仪表支持,例如对国际化所需的消息资源的支持,以及发布应用程序的能力事件到感兴趣的事件侦听器。

创建具有应用程序上下文的容器

Spring 提供了几种风格的应用程序上下文作为 bean 容器。 ApplicationContext 接口有多个核心实现,如下所示:

  • FileSystemXmlApplicationContext: This class is an implementation of ApplicationContext that loads application context bean definitions from the configuration files (XML) located in the file system.
  • ClassPathXmlApplicationContext: This class is an implementation of ApplicationContext that loads application context bean definitions from the configuration files (XML) located in the classpath of the application.
  • AnnotationConfigApplicationContext: This class is an implementation of ApplicationContext that loads application context bean definitions from the configuration classes (Java based) from the class path of the application.

Spring 为您提供了 ApplicationContext 接口的 Web 感知实现,如下所示:

  • XmlWebApplicationContext: This class is a web-aware implementation of ApplicationContext that loads application context bean definitions from the configuration files (XML) contained in a web application.
  • AnnotationConfigWebApplicationContext: This class is a web-aware implementation of ApplicationContext that loads Spring web application context bean definitions from one or more Java-based configuration classes.

我们可以使用这些实现中的任何一种将 bean 加载到 bean 工厂中。这取决于我们的应用程序配置文件位置。例如,如果您想从特定位置的文件系统中加载配置文件 spring.xml,Spring 为您提供了一个 FileSystemXmlApplicationContext< /code>,在文件系统的特定位置查找配置文件 spring.xml 的类:

    ApplicationContext context = new
     FileSystemXmlApplicationContext("d:/spring.xml"); 

同样,您还可以使用 ClassPathXmlApplicationContext 从应用程序的类路径中加载应用程序配置文件 spring.xml Spring 提供的类。它在类路径(包括 JAR 文件)的任何位置查找配置文件 spring.xml

    ApplicationContext context = new 
     ClassPathXmlApplicationContext("spring.xml"); 

如果您使用的是 Java 配置而不是 XML 配置,则可以使用 AnnotationConfigApplicationContext

    ApplicationContext context = new 
     AnnotationConfigApplicationContext(AppConfig.class); 

加载配置文件并获取应用上下文后,我们可以通过调用应用上下文的 getBean() 方法从 Spring 容器中获取 bean:

    TransferService transferService = 
     context.getBean(TransferService.class); 

在下一节中,我们将了解 Spring bean 的生命周期,以及 Spring 容器如何响应 Spring bean 来创建和管理它。

豆子在容器中的寿命


Spring 应用程序上下文使用 Factory 方法设计模式根据给定的配置以正确的顺序在容器中创建 Spring bean。所以Spring容器有责任管理bean从创建到销毁的生命周期。在普通的 java 应用程序中,使用 Java 的 new 关键字来实例化 bean,然后就可以使用了。一旦不再使用 bean,它就有资格进行垃圾收集。但是在 Spring 容器中,bean 的生命周期更加精细。下图展示了一个典型 Spring bean 的生命周期:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

Spring容器中一个Spring bean的生命周期如下:

  1. Load all bean definitions, creating an ordered graph.
  2. Instantiate and run BeanFactoryPostProcessors (you can update bean definitions here).
  3. Instantiate each bean.
  1. Spring injects the values and bean references into the beans' properties.
  2. Spring passes the ID of the bean to the setBeanName() method of the BeanNameAware interface if any bean implements it.
  3. Spring passes the reference of the bean factory itself to the setBeanFactory() method of BeanFactoryAware if any bean implements it.
  4. Spring passes the reference of the application context itself to the setApplicationContext() method of ApplicationContextAware if any bean implements it.
  5. BeanPostProcessor is an interface, and Spring allows you to implement it with your bean, and modifies the instance of the bean before the initializer is invoked in the Spring bean container by calling its postProcessBeforeInitialization().
  6. If your bean implements the InitializingBean interface, Spring calls its afterPropertiesSet() method to initialize any process or loading resource for your application. It's dependent on your specified initialization method. There are other methods to achieve this step, for example, you can use the init-method of the <bean> tag, the initMethod attribute of the @Bean annotation, and JSR 250's @PostConstruct annotation.
  7. BeanPostProcessor is an interface, and spring allows you to implement it with your bean. It modifies the instance of the bean after the initializer is invoked in the spring bean container by calling its postProcessAfterInitialization().
  8. Now your bean is ready to use in the step, and your application can access this bean by using the getBean() method of the application context. Your beans remain live in the application context until it is closed by calling the close() method of the application context.
  9. If your bean implements the DisposibleBean interface, Spring calls its destroy() method to destroy any process or clean up the resources of your application. There are other methods to achieve this step-for example, you can use the destroy-method of the <bean> tag, the destroyMethod attribute of the @Bean annotation, and JSR 250's @PreDestroy annotation.
  10. These steps show the life cycle of Spring beans in the container.
  11. The next section describes the modules that are provided by the Spring Framework.

弹簧模块


Spring 框架有几个不同的模块用于一组特定的功能,它们或多或少独立于其他模块工作。该系统非常灵活,因此开发人员只能选择企业应用程序所需的那些。例如,开发人员可以只使用 Spring DI 模块并使用非 Spring 组件构建应用程序的其余部分。因此,Spring 提供了与其他框架和 API 一起工作的集成点——例如,您只能将 Spring Core DI 模式与 Struts 应用程序一起使用。如果开发团队更熟练地使用 Struts,则可以使用它来代替 Spring MVC,而应用程序的其余部分则使用 Spring 组件和特性,例如 JDBC 和事务。因此,虽然开发人员需要使用 Struts 应用程序部署所需的依赖项,但无需添加整个 Spring Framework。

以下是整个模块结构的概述:

读书笔记《spring-5-design-patterns》第 1 章 Spring Framework 5.0 和设计模式入门

Spring框架的各个模块

让我们看看 Spring 的每个模块,看看每个模块如何适应更大的图景。

核心弹簧容器

Spring框架的这个模块使用了很多设计模式,如工厂方法设计模式、DI模式、抽象工厂设计模式、单例设计模式、原型设计模式等。所有其他 Spring 模块都依赖于该模块。当您配置应用程序时,您将隐式使用这些类。它也称为 IoC 容器,是 Spring 支持依赖注入的核心,它管理 Spring 应用程序中的 bean 如何创建、配置和管理。您可以使用 BeanFactory 的实现或 ApplicationContext 的实现来创建 Spring 容器。该模块包含 Spring bean factory,它是提供 DI 的 Spring 部分。

Spring的AOP模块

Spring AOP 是一个基于 Java 的 AOP 框架,集成了 AspectJ。它使用动态代理进行切面编织,专注于使用 AOP 解决企业问题。该模块基于代理和装饰器设计模式。该模块实现了横切关注点的模块化,以避免缠结并消除分散。与 DI 一样,它支持核心业务服务和横切关注点之间的松散耦合。您可以实现自定义方面并在应用程序中以声明方式配置它们,而不会影响业务对象的代码。它在代码中提供了很大的灵活性;您可以在不触及业务对象代码的情况下删除或更改方面逻辑。这是spring框架中非常重要的一个模块,所以我会在第6章中详细讨论,本书的Spring Aspect Oriented Programming with Proxy and Decorator Pattern

Spring DAO - 数据访问和集成

Spring DAO 和 Spring JDBC 通过使用模板删除公共代码,让生活变得非常轻松。模板实现了 GOF 模板方法设计模式,并提供了合适的扩展点来插入自定义代码。如果您使用的是传统的 JDBC 应用程序,则必须编写大量样板代码,例如创建数据库连接、创建语句、查找结果集、处理 SQLException,最后关闭连接。如果您正在使用带有 DAO 层的 Spring JDBC 框架,那么您不必编写样板代码,这与传统的 JDBC 应用程序不同。这意味着 Spring 允许您保持应用程序代码简洁明了。

春天的 ORM

Spring 还提供了对 ORM 解决方案的支持,它提供了与 ORM 工具的集成,以便在关系数据库中轻松持久化 POJO 对象。这个模块实际上提供了对 Spring DAO 模块的扩展。与基于 JDBC 的模板一样,Spring 提供 ORM 模板以与领先的 ORM 产品一起使用,例如 Hibernate、JPA、OpenJPA、TopLink、iBATIS 等。

Spring Web MVC

Spring 为企业 Web 应用程序提供了一个 Web 和远程模块。该模块有助于构建高度灵活的 Web 应用程序,利用 Spring IOC 容器的全部优势。 Spring的这个模块使用了MVC架构模式、前端控制器模式、DispatcherServlet模式等模式,并与servlet API无缝集成。 Spring Web 模块非常可插拔且灵活。我们可以添加任何视图技术,例如 JSP、FreeMarker、Velocity 等。我们还可以使用 Spring IOC 和 DI 将其与其他框架集成,例如 Struts、Webwork 和 JSF。

Spring Framework 5.0 中的新特性


Spring 5.0 是可用的 Spring 的最新版本。 Spring 5.0 中有许多令人兴奋的新特性,包括:

  • Support for JDK 8 + 9 and Java EE 7 Baseline:

Spring 5 支持 Java 8 作为最低要求,因为整个框架代码库都基于 Java 8。

Spring Framework 至少需要 Java EE 7 才能运行 Spring Framework 5.0 应用程序。这意味着它需要 Servlet 3.1、JMS 2.0、JPA 2.1。

  • Deprecated and removed packages, classes, and methods:

在 Spring 5.0 中,某些软件包已被删除或弃用。它从 spring-aspects 模块中删除了一个名为 mock.static 的包,因此不支持 AnnotationDrivenStaticEntityMockingControl

自 Spring 5.0 起,web.view.tiles2orm.hibernate3/hibernate4 等软件包也已被删除。现在,在最新的 spring 框架中,正在使用 Tiles 3 和 Hibernate 5。

Spring 5.0 框架不再支持 Portlet、Velocity、JasperReports、XMLBeans、JDO、Guava(等等)。

自 Spring 5.0 起,Spring 早期版本的一些已弃用的类和方法已被删除。

  • Adding the new reactive programming model:

这种编程模型已在 Spring 5.0 框架中引入。让我们看看下面列出的关于反应式编程模型的要点。

Spring 5 在响应式编程模型中引入了 Spring 核心模块 DataBuffer 和具有非阻塞语义的编码器/解码器抽象。

使用响应式模型,Spring 5.0 通过 JSON (Jackson) 和 XML (JAXB< /span>) 支持。

Spring 响应式编程模型添加了一个新的 spring-web-reactive 模块,该模块具有对 @Controller 编程模型的响应式支持,适应响应式流到 Servlet 3.1 容器以及非 Servlet 运行时,例如 Netty 和 Undertow。

Spring 5.0 还引入了一个新的 WebClient,在客户端提供响应式支持以访问服务。

正如这里列出的那样,您可以看到 Spring Framework 5 中有许多令人兴奋的新特性和增强功能。因此,在本书中,我们将通过示例及其采用的设计模式来研究其中的许多新特性。

概括


阅读本章后,您现在应该对 Spring 框架及其最常用的设计模式有一个很好的了解。我强调了 J2EE 传统应用程序的问题,以及 Spring 如何解决这些问题并通过使用大量设计模式和良好实践来创建应用程序来简化 Java 开发。 Spring 旨在使企业 Java 开发更容易,并促进松散耦合的代码。我们还讨论了用于横切关注点的 Spring AOP 以及用于松散耦合和可插拔 Spring 组件的 DI 模式,这样对象就不需要知道它们的依赖关系来自哪里或它们是如何实现的。 Spring Framework 是最佳实践和有效对象设计的推动者。 Spring Framework 有两个重要特性——首先它有一个 Spring 容器来创建和管理 bean 的生命周期,其次它提供对多个模块和集成的支持以帮助简化 Java 开发。