Spring 5 设计模式 (chapter 1)

开始使用Spring Framework 5.0和设计模式

介绍Spring框架

Spring简化了应用程序开发,并消除了许多依赖项其他api。让我们来看一些例子,作为应用程序开发人员,可以从Spring平台获益:

  • 所有的应用程序类都是简单的POJO类——Spring不是入侵性的。它不需要您扩展框架类或为大多数用例实现框架接口。
  • Spring应用程序不需要Java EE应用服务器,但是它们可以部署在任何一个Java EE应用服务器上。
  • 您可以使用Spring框架中的事务管理来执行数据库事务中的方法,而不需要任何第三方事务API。(Spring内部封装好了)
  • 使用Spring,您可以使用Java方法作为请求处理程序方法或远程方法,就像servlet API的service()方法,但不需要处理servlet容器的servlet API。
  • Spring允许您在应用程序中使用本地java方法作为消息处理程序方法,而无需使用java消息服务(JMS)API。(封装了JMS)
  • Spring还允许您使用本地java方法作为管理操作,而不用在应用程序中使用java管理扩展(JMX)API。(封装了JMX)
  • Spring作为应用程序对象的容器。您的对象不必担心查找和建立彼此的连接。(IOC容器来管理对象的生命周期)
  • Spring实例化bean并将对象的依赖项注入
    应用程序——它作为bean的生命周期管理器。

利用Spring简化应用程序开发

我们需要讨论或至少提供一些基本的实现和对设计模式的考虑,以及为企业应用程序开发设计基础设施的最佳实践。Spring使用以下策略使java开发变得容易和可测试:

  • Spring使用POJO模式的力量来进行轻量级的、最小侵入式的企业应用开发
  • 它使用依赖注入模式(DI模式)的力量来实现松散耦合,并使系统面向接口
  • 它使用了修饰符和代理设计模式的力量,通过切面和公共约定来实现声明性编程
  • 它使用切面和模板设计模式的力量来消除样板代码。

在本章中,我将解释这些概念,并展示了Spring如何简化Java开发的具体例子。让我们从探索如何通过使用POJO模式来鼓励基于POJO的开发来探索Spring如何保持最小的入侵性。

使用POJO模式的力量

有许多的Java开发框架,它们通过强制你继承或者实现它们已经存在的类和接口来开发,Struts,Tapestry和早期版本的EJB采用了这种方法。这些框架的编程模型是基于入侵式的编程模型。这使得代码很难在系统中发现bug,有时会使代码变得难以理解。
但是,如果你使用Spring框架,你不需要继承或者实现任何类和接口,只是简单的基于POJO的实现,这是一种非侵入式的编程模型,它可以使得代码很容易的发现bug,也可以让我们的代码更好的理解。
Spring允许您使用非常简单的非Spring类进行编程,这意味着不需要实现特定于Spring的类或接口,因此基于Spring的应用程序中的所有类都是POJO。这意味着您可以在不依赖Spring库的情况下编译和运行这些文件;您甚至不能意识到Spring框架正在使用这些类。在基于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模式实现。 它使事情变得容易理解,松散耦合,并可跨应用程序进行测试。假设我们有一个简单的程序,每个类都在共同完成一些业务任务,并帮助建立业务需求和期望。这意味着应用程序中的每个类都与其他协作对象(其依赖项)一起对业务任务负有责任。 我们来看下面的图片。 对象之间的依赖关系可以在依赖对象之间创建复杂性和紧密耦合:

TransferService组件依赖于其他两个组件: TransferRepository和AccountRepository

一个典型的应用系统由几个部分组成,以执行一个用例。例如,考虑下传输服务类。
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()方法,那么AccountRepository类的transfer()方法在本次测试中也被认为是不可能的。 因为开发人员并不知道AccountRepository对TransferService类的依赖关系; 至少,开发人员无法使用单元测试来测试TransferService类的transferMoney()方法。

在企业应用程序中,耦合是非常危险的,它把你推向了一个在将来无法对应用程序进行任何改进的情况下,如果此类应用程序的进一步更改可能会产生大量的错误,并且修复这些错误可能会产生新的错误。

紧耦合元件是这些应用中主要问题的原因之一。 不必要的紧密耦合的代码使您的应用程序变得不可维护,随着时间的推移,代码将不会被重用,因为其他开发人员无法理解。 但是,有时企业应用程序需要一定程度的耦合,因为在现实世界中,完全不耦合的组件是不可能的。 应用程序中的每个组件对于角色和业务需求都有一定的责任,所有组件都必须知道其他组件的责任。 这意味着有时需要耦合,因此我们必须非常仔细地管理所需组件之间的耦合。

使用依赖组件的工厂帮助模式(Using factory helper pattern for dependent components)

让我们使用工厂模式来尝试另一个管理依赖对象的方法。此设计模式基于GOF工厂设计模式,通过使用工厂方法创建对象实例。 所以这个方法实际上集中了新操作符的使用。 它根据客户端代码提供的信息创建对象实例。 这种模式在依赖注入策略中被广泛使用。
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模式,在某些工厂或第三方创建对象时,依赖对象会得到它们的依赖项。该工厂以这样一种方式协调系统中的每个对象,而每个依赖对象并不期望创建它们的依赖项。这意味着我们应该要专注于定义依赖项,而不是解决企业应用程序中协作对象的依赖关系。让我们看一下下面的图像。我们可以看到依赖项被注入到需要它们的对象中:

Dependency injection between the different collaborating components in the application

为了展示这一点,我们将在下一节看到TransferService,一个 TransferService依赖于AccountRepositoryTransferRepository,在这里,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并没有创建他自己的依赖实现,在构建时,我们已经将repository的实现作为构造函数的参数。这是一个称为构造函数注入的DI类型。
这里我们已经传递了repository接口类型作为构造函数的参数。现在TransferServiceImpl可以使用任何repositorie接口的实现,比如JDBC,JPA,或者mock对象,关键点是TransferServiceImpl并没有和特定的repositorie实现进行耦合,无论哪种repository用于将数量从一个帐户转移到另一个帐户,只要它实现了repository相应的接口就行。

如果您正在使用Spring框架的DI模式,那么松耦合是关键的优点之一。DI模式总是促进P2I(面向接口编程),因此每个对象都知道其与相关接口的依赖关系,而不是相关的实现,因此依赖项可以很容易地与该接口的另一个实现交换,而不是更改到依赖的类实现。

通过创建关联来组装应用系统的方法
应用部分或组件被称为wiring

在Spring中,有很多方法可以将协作组件连接到一起,从而形成一个应用程序系统。例如,我们可以使用XML配置文件或Java配置文件。
现在,让我们看看如何将TransferRepositoryAccountRepository的依赖关系注入到Spring的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的例子中,它是构造的,它传递一个引用到AccountRepositoryTransferRepositorybean作为构造函数的参数。您可能想知道,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的配置的依赖注入模式,它们好处都是一样的:

  • 依赖注入促进松耦合。您可以使用最佳实践P2I删除硬编码的依赖项,您可以通过使用工厂模式及其内置的可切换实现和可插入实现来从应用程序外部提供依赖关系
  • DI模式促进了面向对象编程的组合设计,而不是继承编程

尽管TransferService依赖于一个AccountRepositoryTransferRepository,但它并不关心应用程序中使用AccountRepositoryTransferRepository的什么类型(JDBC或JPA)。只有Spring通过它的配置(XML或基于java)知道所有组件是如何组合在一起的,并使用DI模式来实例化它们所需要的依赖关系。DI使那些依赖类的依赖关系不再变化——也就是说,我们可以使用JDBC实现或JPA实现,而不需要更改AccountService的实现。

在Spring应用程序中,一个实现的应用程序上下文(Spring提供了基于java的AnnotationConfigApplicationContext和基于xmlClassPathXmlApplicationContext实现)加载bean定义并将它们绑在一起成一个Spring容器。Spring的应用程序上下文在启动时为应用程序创建和连接Spring bean。使用基于java的配置来查看Spring应用程序上下文的实现——它加载Spring配置文件(AppConfig) 或者 Sprig.xml)位于应用程序的类路径中。在以下代码中,TransferMain类的main()方法使用AppConfig所类加载配置类。获取一个AccountService类的对象。

Spring提供了基于java配置的方式来代替XML:


package com.packt.patterninspring.chapter1.bankapp;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.packt.patterninspring.chapter1.bankapp.config.AppConfig;
import com.packt.patterninspring.chapter1.bankapp.model.Amount;
import com.packt.patterninspring.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 AOP的面向方面编程使您能够捕获在整个应用程序中重复的常见功能。因此,我们可以说,Spring AOP促进了松散耦合,并允许以最优雅的方式分离的横切关注点。它允许通过声明透明地应用这些服务。在Spring AOP中,可以编写自定义方面并以声明的方式配置它们。

在您的应用程序中许多地方需要的通用功能是:

  • 日志和跟踪
  • 事务管理
  • 安全
  • 缓存
  • 错误处理
  • 性能监控
    -自定义业务规则

这里列出的组件并不是您的核心应用程序的一部分,但是这些组件有一些额外的职责,通常称为交叉关注点,因为它们在系统中跨多个组件,超出了它们的核心职责。如果您将这些组件与您的核心功能相结合,从而实现了不模块化的横切关注点,那么它将有两个主要问题:

  • Code tangling:关注点的耦合意味着交叉的关注点代码,例如安全问题、事务关注和日志关注,与应用程序中的业务对象的代码相结合。
  • Code scattering: 代码散射指的是跨模块分布的相同关注点。这意味着您关心的安全性、事务和日志记录都分布在系统的所有模块中。换句话说,您可以说整个系统中都存在相同的问题代码。

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


Cross-cutting concerns, such as logging, security and transaction, are often scattered about in modules where those tasks are not their primary concern

Spring AOP使交叉关注点的模块化避免了缠结和散射。您可以对应用程序的核心业务组件进行这些模块化的关注,而不会影响上述组件。这些方面确保了pojo仍然是简单的。Spring AOP通过使用代理设计模式使这种魔力成为可能。我们将在本书的后续章节中进一步讨论代理设计模式。

Spring AOP 是怎么工作的

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

  • 实现你的主要的程序逻辑 : 专注于核心问题意味着,当您在编写应用程序业务逻辑时,您不需要担心添加额外的功能,例如日志、安全性和事务,在业务代码与spring AOP之间进行处理。
  • 编写一些切面来实现您的横切关注点: Spring提供了许多切面,这意味着您可以在Spring AOP中以独立单元的形式编写额外的功能。这些方面在应用程序逻辑代码之外有更多的跨切关注点。
  • ** 将各个切面织入到应用程序中去:将交叉横切的行为添加到正确的位置,也就是说,在写了其他的方面之后职责,您可以在应用程序逻辑代码中以声明的方式将它们注入到正确的位置。

让我们来看看Spring AOP 的说明:

基于aop的系统进化——这使得应用程序组件专注于它们的特定 业务功能

在前面的图中,Spring AOP将横切关注点分离开来,例如,安全、事务和日志,来自业务模块,即BankServiceCustomerServiceReportingService。这些横切关注点应用于应用程序运行时的业务模块的预定义点(前面图中的条纹)。假设您希望在调用transferAmmount()传输服务方法之前和之后使用LoggingAspect的服务来记录消息。下面的清单显示了您可能使用的LoggingAspect类。


package com.packt.patterninspring.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(* *.transferAmmount(..))")
    public void logBeforeTransfer(){
        System.out.println("####LoggingAspect.logBeforeTransfer() method called before transfer amount####");
    }
    
    @After("execution(* *.transferAmmount(..))")
    public void logAfterTransfer(){
        System.out.println("####LoggingAspect.logAfterTransfer() method called after transfer amount####");
    }
}

要将LoggingAspect转换为aspect bean,您需要做的就是将其声明为Spring配置文件中的一个Bean。另外,为了使它成为一个切面,您必须在这个类中添加@ aspect注释。这是更新后的AppConfig。java文件,修改为将LoggingAspect声明为一个切面。


package com.packt.patterninspring.chapter1.bankapp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import com.packt.patterninspring.chapter1.bankapp.aspect.LoggingAspect;
import com.packt.patterninspring.chapter1.bankapp.repository.AccountRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.TransferRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.jdbc.JdbcAccountRepository;
import com.packt.patterninspring.chapter1.bankapp.repository.jdbc.JdbcTransferRepository;
import com.packt.patterninspring.chapter1.bankapp.service.TransferService;
import com.packt.patterninspring.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();
    }
}

在这里,我们使用了基于Java的Spring AOP配置来声明LoggingAspectbean来作为一个切面,首先,我们要定义LoggingAspect为一个Bean,然后我们用@Aspect注解来注释这个Bean。

我们使用@ before注释来注释LoggingAspect的logBeforeTransfer(),以便在执行transferAmount()之前调用该方法。这是在建议之前。然后,我们使用@ after注释来标注另一个LoggingAspect的方法,声明logAfterTransfer()方法应该在transferAmount()执行之后调用。这就是所谓的“advice(通知)”。
@ enableaspectjautoproxy用于在应用程序中启用Spring AOP特性。这个注释实际上强制您将代理应用于spring配置文件中定义的某些组件。稍后我们将讨论更多关于Spring AOP的内容,在C - hapter 6中,使用代理和装饰器模式的Spring面向方面编程。就目前而言,您已经要求Spring在调用TransferService类的transferAmount()之前来调用logBeforeTransfer()和logAferTransfer()方法,这就足够了。现在,从这个例子中有两点需要注意:

  • LoggingAspect仍然是一个POJO(如果忽略@ aspect注释或者使用基于xml的配置)——它没有说明它将被用作一个方面。
  • 重要的一方面是,LoggingAspect可以应用于TransferService,而不需要将其显式地调用。事实上,TransferService仍然完全不知道LoggingAspect的存在。

让我们换一种方式来简化Java开发。

Applying the template pattern to eliminate boilerplate code(应用模板模式消除样板代码)

在企业应用程序的某一点上,我们看到了一些代码,这些代码看起来就像我们之前在同一个应用程序中编写的代码。这就是样板代码。我们经常需要在同一个应用程序中反复编写代码,以实现应用程序不同部分的共同需求。不幸的是,有很多地方的Java api涉及一堆样板代码。一个常见的例子是 ,当我们使用JDBC从数据库查询数据的时候,就会看到很多样板代码,如果你从来没有用过JDBC,你可能要写下面的代码来解决问题:

  • 从连接池获取一个数据库连接
  • 创建一个PreparedStatement对象
  • 绑定SQL参数
  • 执行PreparedStatement对象
  • 从ResultSet对象中检索数据并填充数据容器对象
  • 释放所有的数据库资源

让我们看看下面的代码,它包含了与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中的模板设计模式

让我们来看看怎么在Spring中使用模板模式:

  • 定义算法的概要或框架
  1. 将细节留给特定的实现,以后的子类来实现。
  2. 隐藏大量的样板代码。
  • Spring提供了很多样板类
  • JdbcTemplate
  • JmsTemplate
  • RestTemplate
  • WebServiceTemplate
  • 大多数隐藏了低级的资源管理

让我们看一下前面的代码,这此我们使用Spring JdbcTemplate,来看看它是如何使用的
消除样板代码。

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


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模式、使用代理模式和模板方法设计模式)的力量来简化Java开发的复杂性。

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

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

Spring提供给我们一个容器,我们的应用程序对象都生活在这个Spring容器中,在下面的这个图中,容器的职责就是创建和管理对象。


In a Spring application, our application objects live in this Spring container

Spring容器还根据其配置将许多对象连接在一起。它配置了一些初始化的参数,并从头到尾管理其完整的生命周期。

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

  • Bean factory
  • Application contexts

Bean factory

在Spring框架中,org.springframework.beans.factory.BeanFactory这个接口提供了bean factory,这就是Spring IoC容器。XmlBeanFactory是这个接口的一个实现。这个容器从一个XML文件读取配置元数据。它基于GOF工厂方法设计模式——它以复杂的方式创建、管理、缓存和连接应用程序对象。bean工厂仅仅是一个对象池,对象是由配置创建和管理的。对于小型应用程序,这是足够的,但是企业应用程序需要更多,所以spring提供了另一个具有更多特性的spring容器。

在下一节中,我们将学习应用程序上下文和Spring
怎么在应用程序中创建它。

Application contexts

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

在application context中创建一个容器

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

  • FileSystemXmlApplicationContext: 这个类是ApplicationContext的实现,它从位于文件系统的配置文件(XML)中加载应用程序上下文bean定义。
  • ClassPathXmlApplicationContext: 这个类是ApplicationContext的实现,它从位于应用程序类路径的配置文件(XML)中加载应用程序上下文bean定义。
  • AnnotationConfigApplicationContext: 这个类是ApplicationContext的实现,它从应用程序的类路径加载来自配置类(基于Java)的应用程序上下文bean定义。

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

  • XmlWebApplicationContext: 这个类是ApplicationContext的web感知实现,它从web应用程序中包含的配置文件(XML)中加载应用程序上下文bean定义。
  • AnnotationConfigWebApplicationContext:这个类是一个web感知的ApplicationContext实现,它从一个或多个基于java的配置类中加载Spring web应用程序上下文bean定义。

我们可以使用其中任何一个实现将bean加载到一个bean工厂中。这取决于我们的应用程序配置文件位置。例如,如果您想加载配置文件spring。xml文件系统的一个特定的位置,春天为你提供了一个FileSystemXmlApplicationContext,类,寻找春天的配置文件。xml在文件系统中的特定位置:

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

同样,您也可以加载应用程序配置文件spring。xml应用程序的类路径使用ClassPathXmlApplicationContext类,它查找配置文件spring.xml。包括类路径中的xml(包括JAR文件):

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

如果你使用的是Java配置,我们可以这么做:

ApplicationContext context = new
AnnotationConfigApplicationContext(AppConfig.class);

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

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

容器中的bean的生命周期

Spring应用程序上下文使用工厂方法设计模式按照给定配置在容器中创建Spring bean。因此,Spring容器有责任管理从创建到销毁的bean的生命周期。在普通java应用程序中,java的new关键字用于实例化bean,实例化之后它已经可以使用了。一旦bean不再使用,它就有资格进行垃圾收集。但是在Spring容器中,bean的生命周期更详细。下图显示了典型的Spring bean的生命周期:

The life cycle of a Spring bean in the Spring container is as follows:

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

  1. 加载所有bean定义,创建有序图。
  2. 实例化和运行BeanFactoryPostProcessors(你可以在这里更新bean定义)
  3. 实例化每一个Bean
  4. Spring将值和bean引用注入到bean的属性中。
  5. 如果任何bean实现它,Spring将bean的ID传递给BeanNameAware接口的setBeanName()方法。
  6. Spring将bean工厂自身的引用传递给BeanFactoryAwaresetBeanFactory()方法,如果任意bean实现了它。
  7. Spring将应用程序上下文本身的引用传递给应用程序的setApplicationContext()方法,如果有任何bean实现它的话。
  8. BeanPostProcessor是一个接口,Spring允许您实现它
    您的bean,并在调用初始化器之前,在Spring bean容器通过调用 postProcessBeforeInitialization()来修改bean的实例。
  9. 如果您的bean实现了InitializingBean接口,Spring调用它的afterPropertiesSet()方法来初始化应用程序的任何进程或加载资源。它依赖于指定的初始化方法。还有其他方法可以实现这一步骤,例如,您可以使用< bean >标记的init方法、@ bean注释的initMethod属性和JSR 250的@ postconstruct注释。
  10. 现在您的bean已经可以在步骤中使用了,并且您的应用程序可以使用应用程序上下文的getBean()方法来访问这个bean。您的bean在应用程序上下文中仍然存在,直到它通过调用应用程序上下文的close()方法关闭为止。
  11. 如果您的bean实现了处理bean接口,Spring调用它的destroy()方法来销毁任何进程或清除应用程序的资源。还有其他方法可以实现这一步骤,例如,您可以使用< bean >标记的破坏方法、@ bean注释的destroy方法属性和JSR 250的@ predestroy注释。
  12. 这些步骤显示了容器中的Spring bean的生命周期。
  13. 下一节将描述Spring提供的模块

Spring modules

Spring框架对于一组特定的功能有几个不同的模块,
他们互不依赖。这个系统非常灵活,所以在企业应用开发中,开发者可以只选择他们需要的模块。比如,例如,开发人员可以只使用Spring DI模块,并使用非Spring组件构建应用程序的其余部分。因此,Spring提供了与其他框架和api一起工作的集成点——例如,您可以只使用Struts应用程序来使用Spring Core DI模式。如果开发团队更精通使用Struts,那么可以使用它而不是Spring MVC,而其他应用程序则使用Spring组件和特性,比如JDBC和事务。因此,开发人员需要使用Struts应用程序来部署所需的依赖关系,不需要添加整个Spring框架。

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

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来解决企业问题。此模块基于代理和装饰器设计模式。该模块实现了横切关注点的模块化,避免了(avoid tangling and eliminate scattering.)纠缠和消除了散射。

与DI一样,它支持核心业务服务和横切关注点之间的松散耦合。您可以实现自定义切面,并在应用程序中以声明方式对它们进行配置,而不会影响业务对象的代码。它在代码中提供了很大的灵活性;您可以删除或更改方面逻辑,而无需涉及业务对象的代码。这是spring框架的一个非常重要的模块,所以我将在第6章中详细讨论它,这是本书的代理和装饰模式。

Spring DAO -数据访问和集成

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

Sping 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 5.0 的新特性

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

  • 支持JDK 8 + 9和Java EE 7基线:
    • Spring 5支持Java 8作为最低要求,因为整个框架代码库是基于Java 8的。
    • Spring框架至少需要Java EE 7来运行Spring Framework 5.0应用程序。这意味着它需要Servlet 3.1、JMS 2.0、JPA 2.1。
  • 弃用和删除包、类和方法:
    • 在Spring 5.0中,一些包已被删除或弃用。它有一个叫做mock的包。静态从spring-aspects删除模块,因此没有对AnnotationDrivenStaticEntityMockingControl的支持。
    • web.view等包。tiles2和orm。在Spring 5.0中也删除了hibernate3 / hibernate4。现在,在最新的spring框架中,tile 3和Hibernate 5正在被使用。
    • pring 5.0框架不支持Portlet、Velocity、JasperReports、XMLBeans、JDO、Guava(等等)。
    • 在Spring 5.0中删除了一些早期版本的弃用类和方法。
  • 添加了新的响应式编程模型:
    • 在Spring 5.0框架中引入了这种编程模型。让我们看看下面列出的关于反应性编程模型的列表。
    • Spring 5引入了Spring - core模块DataBuffer和encoder / decoder抽象,将非阻塞语义引入到响应性编程模型中。
    • 使用反应模型,Spring 5.0提供了使用JSON(Jackson)和XML(JAXB)支持的HTTP消息codec实现的Spring - web模块。
    • Spring反应式编程模型为@ controller编程模型添加了一个新的Spring -web -reactive,将响应流调整为Servlet 3.1容器,以及非Servlet运行时,如Netty和Undertow。
    • Spring 5.0还引入了一个新的WebClient,它在客户端提供了响应性支持来访问服务。

如这里所列,您可以看到Spring Framework 5中有许多令人兴奋的新特性和增强。所以在这本书中,我们将通过例子和他们所采用的设计模式来研究这些新特性。

总结

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

推荐阅读更多精彩内容