2. Spring 框架基础

1.Spring框架

Spring 框架概述

Spring 框架模块概述

Module Artifact 描述
AOP spring-aop Spring基于代理的AOP框架
Aspects spring-aspects 用于 Spring 的基于 AspectJ 的切面
Beans spring-beans Spring 的核心 bean 工厂支持
Context spring-context 应用程序上下文运行时实现;还包含调度和远程支持类
Context spring-context-indexer 支持提供应用程序中使用的bean的一个静态索引;提高启动性能
Context spring-context-support 用于将第三方库与 Spring 集成的支持类
Core spring-core 核心实用程序
Expression Language spring-expression Spring 表达式语言 (SpEL) 的类
Instrumentation spring-instrument 与一个 Java 代理一起使用的检测类
JCL spring-jcl 用于公共日志记录的 Spring 特定替换
JDBC spring-jdbc JDBC 支持包,包括数据源设置类和 JDBC 访问支持
JMS spring-jms JMS 支持包,包括同步 JMS 访问和消息侦听器容器
ORM spring-orm ORM 支持包,包括对 Hibernate 5+ 和 JPA 的支持
Messaging spring-messaging Spring消息传递抽象;JMS 和 WebSocket 使用
OXM spring-oxm XML 支持包,包括对对象到 XML 映射的支持;还包括对 JAXB、JiBX、XStream 和 Castor 的支持
Test spring-test 测试支持类
Transactions spring-tx 交易基础设施类;包括 JC one 集成和 DAO 支持类
Web spring-web 适用于任何网络环境的核心网络包
WebFlux spring-webflux Spring WebFlux 支持包包括对 Netty 和 Undertow 等多个反应式运行时的支持
Servlet spring-webmvc 在一个 Servlet 环境中使用的 Spring MVC 支持包包括对常见视图技术的支持
WebSocket spring-websocket Spring WebSocket 支持包包括对通过 WebSocket 协议进行通信的支持

Spring Framework 模块依赖项

2.依赖注入

有七种方法可以解耦这种依赖构建逻辑。

  • 工厂模式
  • 服务定位器模式
  • 依赖注入
    • 基于构造函数
    • 基于二传手(Setter based)
    • 基于现场
  • 上下文查找

1.假设:使用来自 JNDI(Java 命名和目录接口)的上下文查找。构造函数代码需要知道如何进行查找和处理异常。

MoneyTransferService Implementation with Contextualized LookupMoneyTransferService Implementation with Contextualized Lookup

package com.xxx.prospringmvc.moneytransfer.jndi;
import javax.naming.InitialContext;
import javax.naming.NamingException;
//other import statements omitted.
public class JndiMoneyTransferServiceImpl implements MoneyTransferService {
  private AccountRepository accountRepository;
  private TransactionRepository transactionRepository;
  public JndiMoneyTransferServiceImpl() {
    try {
      InitialContext context = new InitialContext();
      this.accountRepository = (AccountRepository) context.lookup("accountRepository");
      this.transactionRepository = (TransactionRepository) context.lookup("transactionRepository");
    } catch (NamingException e) {
      throw new IllegalStateException(e);
    }
  }
//transfer method omitted, same as listing 2-1
}
复制代码

*结论:*前面的代码不是特别干净;例如,想象来自不同上下文的多个依赖项。代码很快就会变得混乱,并且越来越难以进行单元测试。

2.解决上述的问题:

为了解决对象构造函数中的构造/查找逻辑,我们可以使用依赖注入。我们只是将对象传递给它完成工作所需的依赖项。这使我们的代码干净、解耦且易于测试。依赖注入是一个对象指定它们所使用的依赖的过程。IoC 容器使用该规范;当它构造一个对象时,它也会注入它的依赖项。这样,我们的代码更干净,我们不再用构造逻辑给类增加负担。更容易维护和更容易进行单元和/或集成测试。测试更容易,因为我们可以注入一个存根或模拟对象来验证我们对象的行为。

A MoneyTransferService Implementation with Constructor-Based Dependency Injection one MoneyTransferService Implementation with Constructor-Based Dependency Injection

package com.xxx.prospringmvc.moneytransfer.constructor;
// import statements ommitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
  private final AccountRepository accountRepository;
  private final TransactionRepository transactionRepository;
  public MoneyTransferServiceImpl(AccountRepository accountRepo,
                                  TransactionRepository transactionRepo) {
    this.accountRepository = accountRepo;
    this.transactionRepository = transactionRepo;
  }
//transfer method omitted, same as listing 2-1
}
复制代码

*总结:*顾名思义,基于构造函数的依赖注入使用构造函数来注入对象中的依赖项。使用了基于构造函数的依赖注入。它有一个构造函数,它接受两个对象作为参数,当我们构造com.xxx.prospringmvc. moneytransfer. constructor. MoneyTransferServiceImpl的实例时,我们需要将所需的依赖项交给它。

3.补充setter

基于 setter 的依赖注入使用setter方法来注入依赖。JavaBeans 规范定义了 setter 和 getter 方法。如果我们有一个名为的方法setAccountService,我们设置了一个名称为的属性 accountService. 属性名称是使用方法名称创建的,减去“set”并且第一个字母小写(完整规范在 JavaBeans 规范中。展示了一个基于 setter 的依赖注入的例子。属性的 getter 和 setter 都不是强制性的。属性可以是只读的(仅定义了getter方法)或只写的(仅定义了setter方法)。显示了 setter 方法,因为我们只需要编写属性;在内部,我们可以直接引用该字段。

A MoneyTransferService Implementation with Setter-Based Dependency Injection one MoneyTransferService Implementation with Setter-Based Dependency Injection

package com.xxx.prospringmvc.moneytransfer.setter;
// imports ommitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
  private AccountRepository accountRepository;
  private TransactionRepository transactionRepository;
  public void setAccountRepository(AccountRepository accountRepository) {
    this.accountRepository = accountRepository;
  }
  public void setTransactionRepository(TransactionRepository transactionRepo) {
    this.transactionRepository = transactionRepository;
  }
//transfer method omitted, same as listing 2-1
}

复制代码

4.最终:使用注解

最后,还有使用注解的基于字段的依赖注入。我们不需要指定构造函数参数或 setter 方法来设置依赖项以使其工作。我们首先定义一个可以保存依赖项的类级字段。接下来,我们在该字段上添加注释以表达我们将该依赖项注入到我们的对象中的意图。Spring 接受几种不同的注解:@Autowired, @Resource, 和 @Inject. 所有这些注释或多或少都以相同的方式工作。

} A MoneyTransferService Implementation with Field-Based Dependency Injection one MoneyTransferService Implementation with Field-Based Dependency Injection

package com.xxx.prospringmvc.moneytransfer.annotation;
import org.springframework.beans.factory.annotation.Autowired;
//other imports omitted
public class MoneyTransferServiceImpl implements MoneyTransferService {
  @Autowired
  private AccountRepository accountRepository;
  @Autowired
  private TransactionRepository transactionRepository;
//transfer method omitted, same as listing 2.1
}
复制代码

总结:@Autowired 和 @Inject 可以放在方法和构造函数上来表达依赖注入配置,即使有多个参数!当对象只有一个构造函数时,可以省略注释。

3.应用上下文

应用程序上下文的作用

应用程序上下文负责管理其中定义的 bean。

它还支持更复杂的操作,例如将 AOP 应用于其中定义的 bean。

Spring 提供了几种不同的 ApplicationContext 实现。这些实现中的每一个都提供相同的功能,但在加载应用程序上下文配置的方式上有所不同。

各种ApplicationContext实现(简化)

应用上下文概述

Implementation Location 文件类型
ClassPathXmlApplicationContext Classpath XML
FileSystemXmlApplicationContext File system XML
AnnotationConfigApplicationContext Classpath Java
XmlWebApplicationContext Web Application Root XML
AnnotationConfigWebApplicationContext Web Application Classpath Java

**举个例子:**让我们看一个基于 Java 的配置文件com.xxx.prospringmvc.moneytransfer.annotation.ApplicationContextConfiguration类

类中使用了两个注释:org.springframework.context.annotation.Configuration和org.springframework.context.annotation.Bean。

第一个构造型将我们的类定义为一个配置文件.

第二个表示该方法的结果用作一个工厂来创建一个 bean。bean 的名称默认为方法名称。

The ApplicationContextConfiguration Configuration File

package com.xxx.prospringmvc.moneytransfer.annotation;
import com.xxx.prospringmvc.moneytransfer.repository.AccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedAccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedTransactionRepository;
import com.xxx.prospringmvc.moneytransfer.repository.TransactionRepository;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationContextConfiguration {
  @Bean
  public AccountRepository accountRepository() {
    return new MapBasedAccountRepository();
  }
  @Bean
  public TransactionRepository transactionRepository() {
    return new MapBasedTransactionRepository();
  }
  @Bean
  public MoneyTransferService moneyTransferService() {
    return new MoneyTransferServiceImpl();
  }
}
复制代码

注意点:

1.配置类可以是抽象的;但是,它们不能是final。为了解析类,Spring 可能会创建配置类的一个动态子类。

2.有一个班级只有 @Configuration注释是不够的。我们还需要一些东西来引导我们的应用程序上下文。我们使用它来启动我们的应用程序。

package com.xxx.prospringmvc.moneytransfer.annotation;
import com.xxx.prospringmvc.ApplicationContextLogger;
import com.xxx.prospringmvc.moneytransfer.domain.Transaction;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.math.BigDecimal;
public class MoneyTransferSpring {
  private static final Logger logger =
    LoggerFactory.getLogger(MoneyTransferSpring.class);
  /**
   * @param args
   */
  public static void main(String[] args) {
    ApplicationContext ctx =
      new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
    transfer(ctx);
    ApplicationContextLogger.log(ctx);
  }
  private static void transfer(ApplicationContext ctx) {
    MoneyTransferService service =
      ctx.getBean("moneyTransferService", MoneyTransferService.class);
    Transaction transaction = service.transfer("123456", "654321", new BigDecimal("250.00"));
    logger.info("Money Transfered: {}", transaction);
  }
}
复制代码

这个类通过创建一个实例来引导我们的配置org.springframework.context.annotation.AnnotationConfigApplicationContext并将包含我们的配置的类传递给它

*总结:*最后,请注意应用程序上下文可以在层次结构中。我们可以有一个应用程序上下文作为另一个上下文的父级,一个应用程序上下文只能有一个父级,但它可以有多个子级。子上下文可以访问父上下文中定义的 bean;但是,父 bean 不能访问子上下文中的 bean。

这个特性允许我们将我们的应用程序 bean(例如,服务、存储库和基础设施)与我们的 Web bean(例如,请求处理程序和视图)分开。进行这种分离会很有用。例如,假设多个 servlet 需要重用相同的应用程序 bean。我们可以简单地重用已经存在的实例,而不是为每个 servlet 重新创建它们。这可能是当有一个 servlet 处理 Web UI 和另一个处理 Web 服务时。

ApplicationContext的层次

3.1.资源加载

不同的ApplicationContext实现和默认资源加载机制。但是,这并不意味着您只能从默认位置加载资源。您还可以通过包含适当的前缀从特定位置加载资源

前缀概述

Prefix Location
classpath: The root of the classpath
file: File system
http: Web application root

Ant 风格的正则表达式

Expression Description
classpath:/META-INF/spring/*.xml 从 META-INF/spring 目录中的类路径加载所有具有 XML 文件扩展名的文件
file:/var/conf/*/.properties 从 /var/conf 目录和所有子目录加载具有属性文件扩展名的所有文件

3.2.组件扫描

此功能使 Spring 能够扫描您的类路径以查找注释为org.springframework.stereotype.Component (或专门的注释之一.

如 @Service, @Repository, @Controller, 或者org.springframework.context.annotation.Configuration

如果我们想启用组件扫描,org.springframework.context.annotation.ComponentScan注释使我们能够做到这一点。这个注解需要放在我们的配置类上才能启用组件扫描。

package com.xxx.prospringmvc.moneytransfer.scanning;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @author Marten Deinum
 */
@Configuration
@ComponentScan(basePackages = {
    "com.xxx.prospringmvc.moneytransfer.scanning",
    "com.xxx.prospringmvc.moneytransfer.repository" })
public class ApplicationContextConfiguration {}
复制代码

*总结:*通过不指定一个包或使用太广泛的一个包(如com.xxx)来扫描整个类路径被认为是一种不好的做法。这可能会导致扫描大部分或所有类,这会严重影响应用程序的启动时间。

3.3.范围

默认情况下,一个 Spring 应用程序上下文中的所有 bean 都是单例。一个 bean 只有一个实例,它用于整个应用程序。

范围概述

Prefix 描述
singleton 默认范围。在整个应用程序中创建和共享一个 bean 的一个实例。
prototype 每次需要一个特定的 bean 时,都会返回一个新的 bean 实例。
thread bean 在需要时创建并绑定到当前正在执行的线程。如果线程死了,bean 就会被销毁。
request bean 在需要时创建并绑定到传入的javax.servlet.ServletRequest的生命周期。如果请求结束,则销毁 bean 实例。
session bean 在需要时创建并存储在javax.servlet.HttpSession 中。当会话被销毁时,bean 实例也会被销毁。
globalSession bean 在需要时创建并存储在全局可用的会话中(在 Portlet 环境中可用)。如果没有这样的会话可用,范围将恢复为会话范围功能。
application 这个范围与单例范围非常相似;但是,具有此范围的 bean 也在javax.servlet.ServletContext 中注册。

3.4.配置文件

配置文件可以轻松地为不同环境创建应用程序的不同配置。例如,我们可以为本地环境、测试和部署到 CloudFoundry 创建单独的配置文件。这些环境中的每一个都需要一些特定于环境的配置或 bean。您可以考虑数据库配置、消息传递解决方案和测试环境,以及某些 bean 的存根。

我们在开发Spring Boot应用时,通常同一套程序会被安装到不同环境,比如:开发、测试、生产等。其中数据库地址、服务器端口等等配置都不同,如果每次打包时,都要修改配置文件,那么非常麻烦。profile功能就是来进行动态配置切换的。 1)profile配置方式

​ ·多profile文件方式

​ ·yml多文档方式 2)profile激活方式 ​ ·配置文件

​ ·虚拟机参数

​ ·命令行参数

image-20211007100009851

package com.xxx.prospringmvc.moneytransfer.annotation.profiles;
import com.xxx.prospringmvc.moneytransfer.annotation.MoneyTransferServiceImpl;
import com.xxx.prospringmvc.moneytransfer.repository.AccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedAccountRepository;
import com.xxx.prospringmvc.moneytransfer.repository.MapBasedTransactionRepository;
import com.xxx.prospringmvc.moneytransfer.repository.TransactionRepository;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class ApplicationContextConfiguration {
  @Bean
  public AccountRepository accountRepository() {
    return new MapBasedAccountRepository();
  }
  @Bean
  public MoneyTransferService moneyTransferService() {
    return new MoneyTransferServiceImpl();
  }
  @Configuration
  @Profile(value = "test")
  public static class TestContextConfiguration {
    @Bean
    public TransactionRepository transactionRepository() {
      return new StubTransactionRepository();
    }
  }
  @Configuration
  @Profile(value = "local")
  public static class LocalContextConfiguration {
    @Bean
    public TransactionRepository transactionRepository() {
      return new MapBasedTransactionRepository();
    }
  }
}
复制代码

要启用配置文件,我们需要告诉应用程序上下文哪些配置文件处于活动状态。要激活某些配置文件,我们需要设置一个名为spring.profiles.active(在 Web 环境中,这可以是 servlet 初始化参数或 Web 上下文参数)。这是一个逗号分隔的字符串,包含活动配置文件的名称。如果我们现在添加一些(在本例中为静态内部)类org.springframework.context.annotation.Configuration 和 org.springframework.context.annotation.Profile注释,则仅处理与活动配置文件之一匹配的类。所有其他类都将被忽略。

下面的代码显示了一些示例引导程序代码。通常,我们不会从引导程序代码中设置活动配置文件。相反,我们使用系统变量的组合来设置我们的环境。这使我们能够让我们的应用程序保持不变,但仍然可以灵活地更改我们的运行时配置。

使用java 代码来进行配置

package com.xxx.prospringmvc.moneytransfer.annotation.profiles;
import com.xxx.prospringmvc.ApplicationContextLogger;
import com.xxx.prospringmvc.moneytransfer.domain.Transaction;
import com.xxx.prospringmvc.moneytransfer.service.MoneyTransferService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.math.BigDecimal;
/**
 * @author Marten Deinum
 */
public class MoneyTransferSpring {
  private static final Logger logger = LoggerFactory.getLogger(MoneyTransferSpring.class);
  /**
   * @param args
   */
  public static void main(String[] args) {
    System.setProperty("spring.profiles.active", "test");
    AnnotationConfigApplicationContext ctx1 =
      new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
    transfer(ctx1);
    ApplicationContextLogger.log(ctx1);
    System.setProperty("spring.profiles.active", "local");
    AnnotationConfigApplicationContext ctx2 =
      new AnnotationConfigApplicationContext(ApplicationContextConfiguration.class);
    transfer(ctx2);
    ApplicationContextLogger.log(ctx2);
  }
  private static void transfer(ApplicationContext ctx) {
    MoneyTransferService service = ctx.getBean("moneyTransferService", MoneyTransferService.class);
    Transaction transaction = service.transfer("123456", "654321", new BigDecimal("250.00"));
    logger.info("Money Transfered: {}", transaction);
  }
}
复制代码

我们也可以通过

application.yml 来进行配置

image-20211007100130004

image-20211007100155025

这时候我们会发现他就改变了

image-20211007100246541

3.5.启用功能

Spring 框架提供了比依赖注入更多的灵活性;它还提供了许多我们可以启用的不同功能。我们可以使用注解来启用这些功能。

org.springframework.web.servlet.config.annotation.EnableWebMvc和org.springframework.web.reactive.config.EnableWebFlux注释提供的特性。

Spring Boot 会自动启用其中一些功能;这取决于在类路径上检测到的类。

注释启用的功能概述

Annotation 描述 Spring Boot 启动检测
org.springframework.context.annotation.EnableAspectJAutoProxy 支持处理构造型为 org.aspectj.lang.annotation.Aspect 的 bean。 Yes
org.springframework.scheduling.annotation.EnableAsync 启用对使用org.springframework.scheduling.annotation.Async或javax.ejb.Asynchronous注释处理 bean 方法的支持。 No
org.springframework.cache.annotation.EnableCaching 使用 org.springframework.cache.annotation.Cacheable 注释启用对 bean 方法的支持。 Yes
org.springframework.context.annotation.EnableLoadTimeWeaving 启用对加载时编织的支持。默认情况下,Spring 使用一种基于代理的 AOP 方法;然而,这个注释使我们能够切换到加载时编织。一些 JP one 提供商需要它。 No
org.springframework.scheduling.annotation.EnableScheduling 启用对注释驱动调度的支持,让我们解析使用 org.springframework.scheduling.annotation.Scheduled 注释注释的 bean 方法。 No
org.springframework.beans.factory.aspectj.EnableSpringConfigured 支持对非 Spring 管理的 bean 应用依赖注入。通常,此类 bean 使用org.springframework.beans.factory.annotation.Configurable注释进行注释。此功能需要加载时或编译时编织,因为它需要修改类文件。 No
org.springframework.transaction.annotation.EnableTransactionManagement 启用注释驱动的事务支持,使用org.springframework.transaction.annotation.Transactional或javax.ejb.TransactionAttribute来驱动事务。 Yes
org.springframework.web.servlet.config.annotation.EnableWebMvc 支持具有请求处理方法的强大且灵活的注释驱动控制器。此功能使用org.springframework.stereotype.Controller注释检测 bean,并将方法与org.springframework.web.bind.annotation.RequestMapping注释绑定到 URL。 Yes
org.springframework.web.reactive.config.EnableWebFlux 使用 Spring Web MVC 中的知名概念支持强大且灵活的反应式 Web 实现,并在可能的情况下对其进行扩展。 Yes

"../images/300017_2_En_2_Chapter/300017_2_En_2_Fige_HTML.jpg" 有关这些功能的更多信息,我们建议您查看不同注释的 Java 文档和专门的参考指南章节。

3.6.面向切面编程

核心AOP 概念

Concept 描述
Aspect 一个横切关注点的模块化。通常,这是一个带有org.aspectj.lang.annotation.Aspect注释的Java 类。
Join Point 一个程序执行过程中的一点。这可以是执行一种方法、分配一个字段或处理异常。在 Spring 中,一个连接点总是执行一种方法!
Advice 方面在一个特定连接点采取的特定操作。有几种类型的通知:beforeafterafter throwingafter returnaround。在 Spring 中,通知被称为**拦截器,**因为我们正在拦截方法调用。
Pointcut 一个匹配连接点的谓词。该通知与一个切入点表达式相关联,并在与该切入点匹配的任何连接点处运行。Spring 默认使用 AspectJ 表达式语言。连接点可以使用org.aspectj.lang.annotation.Pointcut注释编写。

现在让我们看看事务管理以及 Spring 如何使用 AOP 围绕方法应用事务。

事务通知或拦截器是org.springframework.transaction.interceptor.TransactionInterceptor. 这个建议是围绕方法放置的org.springframework.transaction.annotation.Transactional注解。

为此,Spring 在实际对象周围创建了一个包装器,称为代理。代理就像一个封闭的对象,但它允许添加(动态)行为(在这种情况下,是方法的事务性)。

一种代理方法调用

所述org.springframework.transaction.annotation.EnableTransactionManagement注释寄存器含有切入点(作用于豆org.springframework.transaction.annotation.Transactional注释)。至此,拦截器就可以使用了。用于启用功能的其他注释的工作方式类似;他们注册 bean 以启用所需的功能,包括大多数功能的 AOP(以及代理创建)。

4.网络应用

那么所有这些技术如何应用于一个Web 应用程序 呢?例如,应用程序上下文如何发挥一种作用?那么提到的所有其他事情呢?

在开发一个 Web 应用程序时,有实际的业务逻辑(例如,服务、存储库和基础设施信息)和基于 Web 的 bean。这些东西应该是分开的,所以我们需要有多个应用上下文和关系。

我们还需要引导我们的应用程序的代码,否则什么也不会发生。在本示例中,我们使用一个MoneyTransferSpring类和一个 main 方法来启动应用程序上下文。这不是我们在一个网络环境中可以做到的。Spring 附带两个可以引导应用程序的组件:org.springframework.web.servlet.DispatcherServlet和org.springframework.web.context.ContextLoaderListener。两个组件都引导并配置应用程序上下文。

我们看一下配置的类 DispatcherServlet. 这是com.xxx.prospringmvc.bookstore.web.BookstoreWebApplicationInitializer类.我们的 Servlet 3.0+ 容器检测到这个类,并初始化我们的应用程序。我们创造了DispatcherServlet 并通过它 org.springframework.web.context.support.AnnotationConfigWebApplicationContext. 接下来,我们将 servlet 映射到所有内容(“/”)并告诉它在启动时加载。

package com.xxx.prospringmvc.bookstore.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import com.xxx.prospringmvc.bookstore.web.config.WebMvcContextConfiguration;
public class BookstoreWebApplicationInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException {
        registerDispatcherServlet(servletContext);
    }
    private void registerDispatcherServlet(final ServletContext servletContext) {
        WebApplicationContext dispatcherContext =
          createContext(WebMvcContextConfiguration.class);
        DispatcherServlet dispatcherServlet =
          new DispatcherServlet(dispatcherContext);
        ServletRegistration.Dynamic dispatcher =
          servletContext.addServlet("dispatcher", dispatcherServlet);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("*.htm");
    }
    private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
        AnnotationConfigWebApplicationContext context =
          new AnnotationConfigWebApplicationContext();
        context.register(annotatedClasses);
        return context;
    }
}
复制代码

让我们通过添加一个让事情变得更有趣 ContextLoaderListener类,以便我们可以拥有父上下文和子上下文。新注册的监听器使用com.xxx.prospringmvc.bookstore.config.InfrastructureContextConfiguration来确定要加载哪些 bean。已经配置好的DispatcherServlet 自动检测由加载的应用程序上下文 ContextLoaderListener.

package com.xxx.prospringmvc.bookstore.web;
import org.springframework.web.context.ContextLoaderListener;
import com.xxx.prospringmvc.bookstore.config.InfrastructureContextConfiguration;
// other imports omitted, see listing 2-11
public class BookstoreWebApplicationInitializer implements WebApplicationInitializer {
  @Override
  public void onStartup(final ServletContext servletContext) throws ServletException {
    registerListener(servletContext);
    registerDispatcherServlet(servletContext);
  }
// registerDispatcherServlet method ommitted see Listing 2-11
// createContext method omitted see Listing 2-11
  private void registerListener(final ServletContext servletContext) {
    AnnotationConfigWebApplicationContext rootContext =
      createContext(InfrastructureContextConfiguration.class);
      servletContext.addListener(new ContextLoaderListener(rootContext));
  }
}
复制代码

下面的代码是我们的主要应用程序上下文。它包含我们的服务和存储库的配置。此清单还显示了我们的 JPA 实体管理器,包括其基于注释的事务支持。

package com.xxx.prospringmvc.bookstore.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {
    "com.xxx.prospringmvc.bookstore.service",
    "com.xxx.prospringmvc.bookstore.repository"})
public class InfrastructureContextConfiguration {
  @Bean
  public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
    LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
    emfb.setDataSource(dataSource);
    emfb.setJpaVendorAdapter(jpaVendorAdapter());
    return emfb;
  }
  @Bean
  public JpaVendorAdapter jpaVendorAdapter() {
    return new HibernateJpaVendorAdapter();
  }
  @Bean
  public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
    return new JpaTransactionManager(emf);
  }
  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
  }
}
复制代码

关于WebApplicationInitializer和DispatcherServlet 不懂的请查看tomcat如何配置控制器的请求路径

要配置核心分发控制器Servlet,即DispatcherServlet,传统做法是直接使用xml进行配置,如下代码所示:

<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>
    org.springframework.web.servlet.DispatcherServlet
  </servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
复制代码

本质上 就是将xml 变成了javabean

5.Spring Boot

前面提到的所有内容也适用于 Spring Boot。Spring Boot 构建并扩展了 Spring Framework 的功能。然而,它确实让事情变得容易多了。默认情况下,Spring Boot 会自动配置它在类路径上找到的功能。当 Spring Boot 检测到 Spring MVC 类时,它会启动 Spring MVC。当它找到一个DataSource实现时,它会引导它。

可以通过向application.properties或application.yml文件添加属性来完成自定义。您可以通过它配置数据源、视图处理和服务器端口等。

另一种选择是手动配置事物,就像在一个常规 Spring 应用程序中所做的那样。当 Spring Boot 检测到一项功能的预配置部分时,它通常会避免自动配置该功能。

package com.xxx.prospringmvc.bookstore;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
@SpringBootApplication
public class BookstoreApplication extends SpringBootServletInitializer  {
  public static void main(String[] args) {
    SpringApplication.run(BookstoreApplication.class);
  }
  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    return builder.sources(BookstoreApplication.class);
  }
}
复制代码

6.总结

涵盖了 Spring Core 的基本知识。我们回顾了依赖注入并简要介绍了依赖注入的三个不同版本。我们还介绍了基于构造函数、基于 setter 和基于注解的依赖注入。

接下来,我们进入 Spring 世界并检查org.springframework.context.ApplicationContext,包括它们在我们的应用程序中扮演的角色。我们还解释了不同的应用程序上下文(例如,基于 XML 或 Java 的)以及它们中的每一个的资源加载。在我们的 Web 环境中,我们在org.springframework.web.context.WebApplicationContext接口的实现中使用应用程序上下文的一个专门版本。我们还介绍了默认情况下应用程序上下文中的 bean 是如何单例作用域的。幸运的是,Spring 为我们提供了额外的作用域,例如request、session、globalSession、prototype、application和线程。

为了在不同的环境中使用不同的配置,Spring 还包含配置文件。我们简要解释了如何启用配置文件以及如何使用它们。当我们测试它并将其部署到 Cloud Foundry 时,我们会在我们的示例应用程序中使用配置文件。

我们还深入研究了 Spring 需要几个启用注释来启用某些功能的方式。这些注释在应用程序上下文中注册了额外的 bean,以启用所需的功能。大多数这些特性依赖于 AOP 的启用(例如,声明性事务管理)。Spring 创建代理以将 AOP 应用于在我们的应用程序上下文中注册的 bean。

最后,我们快速浏览了 Spring Boot 以及它如何让我们作为软件开发人员的生活变得轻松。Spring Boot 使用自动配置来配置在类路径上检测到的功能。它在需要的地方建立并扩展了 Spring 框架。

下一章着眼于 MVC Web 应用程序的架构、不同的层以及它们在我们的应用程序中的作用。

猜你喜欢

转载自juejin.im/post/7016151591552024583