为 Spring Framework 和 Spring Boot 构建代码结构

工程 | Josh Long | 2021 年 8 月 23 日 | ...

或者:为 Spring Framework 构建心智模型

编辑:下面有一个很棒的评论提到,原来的标题Structuring Spring Boot Applications(《Spring Boot 应用结构设计》)有点误导,因为这篇博客并不打算讨论如何组织类型和包,而是思考 Spring 如何与代码中的对象进行交互。下一段试图澄清这一点,但显然做得不够。总之,我已经更改了标题,这很不巧地会破坏到之前的链接。对于标题不佳以及由此导致的链接失效两天,我深感抱歉。我希望它能对大家有所帮助,即使我显然可以在标题上做得更好……

思考如何设计应用结构是一件困难的事情。在更高层次上有很多需要考虑的地方——它是一个批处理作业、一个 Web 应用、一个消息应用等等。相应的框架——Spring Batch、Spring Webflux、Spring Integration——将指导这些决策。除此之外,还有许多其他框架,它们都是为帮助您为特定的业务领域构建生产级别的产品而量身定制的。我们在这篇文章中不会探讨这些。相反,我想回答这个问题:我们应该如何设计我们的配置

我无法给出一个有主见的答案并希望在一篇文章中就讲完,但我们可以讨论 2021 年 Spring Boot 应用中配置的技术维度。更容易谈论的是如何设计 Java 对象以便在控制反转(IoC)容器中良好地工作。请记住,归根结底,Spring 就是一大堆对象。它需要知道您希望如何排列您的对象——它们如何连接起来以及它们如何相互关联——才能为它们提供服务。例如,它可以在方法开始和停止时开始和提交事务。当请求到达时,它可以创建调用您的 Spring 控制器处理方法的 HTTP 端点。它可以响应来自 Apache Kafka 代理或 AWS SQS 或 RabbitMQ 或任何其他消息代理的新消息,来调用您的消息监听器对象。Spring 能做的事情还有很多,但这一切都假定您已经将对象注册到了 Spring 中。

Spring 有一个对象的元模型——有点像 Java 的反射 API。它知道哪些类带有注解。它知道哪些对象有构造函数。它知道某个对象依赖于哪些依赖项、哪些 bean 以及哪种类型。您的工作是帮助它构建这个元模型,以便为您管理所有对象。例如,如果它能控制您对象的创建,那么它也能在您的对象被创建之前改变它们的创建方式。

只有当 Spring 知道对象是如何连接在一起的时候,它才能为您提供所有这些服务。所以,核心思想是您提供普通的 Java 对象(POJO),Spring 会检测到它们身上的注解,并利用这些注解来连接您的服务的行为。但是,当然,除非它能控制您 Java 对象的创建,否则它做不到这一点。

在幕后,它通过创建 Java InvocationHandler(JDK 代理),更常见的是,使用 CGLIB 之类的工具来创建一个扩展您的 Java 类的新类来实现这一点。这个类是您类的子类。所以,假设您有一个像这样的类


class CustomerService  {

	private final JdbcTemplate template; 

	CustomerService (JdbcTemplate jt) {
		this.JdbcTemplate = jt;
	}

    @Transactional 
	public void updateCustomer ( long customerId, String name){
       // .. .
	}
}

您希望 Spring 在每次调用该方法时自动开始和停止一个事务。为了实现这一点,Spring 需要在调用您的方法之前和之后插入自己。在幕后,它会做类似这样的事情

class SpringEnhancedCustomerService extends CustomerService {

    // Spring provides a reference from the applicationContext of type JdbcTemplate
	SpringEnhancedCustomerService (JdbcTemplate jt) {
		 super(JdbcTemplate ) ;
	}


	@Override 
	public void updateCustomer (long customerId, String name) {
		// call Java code to start a JDBC transaction 
		super.updateCustomer(customerId, name);
		// call Java code to stop a JDBC transaction
	}
}

在您的代码中,您可以注入对 CustomerService 的引用。您仍然会得到一个实例,但不是您创建的那个。相反,您将获得该子类。正是这种魔术般的技巧——您要一个帽子,却得到一个里面装着兔子的帽子——使得 Spring 如此强大。

所以,Spring 必须知道您的对象。有很多方法可以做到这一点。

一种方法是您可以非常明确。在 Spring Boot 之前,您有两种标准选项:XML 和 Java 配置。不过,那是 2013 年及以前的情况。如今,我们不鼓励使用 XML,所以您只剩下 Java 配置。这是一个例子

@Configuration 
class ServiceConfiguration {

 @Bean DataSource h2DataSource (){
 	return ... ;
 }

 @Bean JdbcTemplate JdbcTemplate (DataSource ds) {
 	return new JdbcTemplate(ds);
 }

  @Bean CustomerService customerService (JdbcTemplate jdbcTemplate) {
  	return new CustomerService (jdbcTemplate);
  }
}


在这里,您创建了三个对象并将它们显式地连接起来。当 Spring 启动时,它会找到 @Configuration 类,调用所有带有 @Bean 注解的方法,将所有返回值存储在应用程序上下文中,并使它们可供注入。如果一个方法似乎需要参数,它会查找任何返回该类型值的其他方法并首先调用它。然后,该值将被注入到方法中作为参数。如果它已经为其他注入调用了该方法,它会直接重用已创建的实例。

这种方法的好处在于它的明确性——所有关于您的对象如何连接在一起的信息都集中在一个地方——配置类。但是,对于您创建的类,您需要在两个不同的位置拥有知识:类本身和配置类。

因此,还有另一种更隐式的方法可以使用:组件扫描。在这种方法中,Spring 会在类路径中查找带有构造型注解的类,例如 @Component@Controller。所有构造型注解最终都带有 @Component 注解。@Component 是最底层、最不区分注解。如果您查看 @Controller,它带有 @Component 注解。如果您查看 @RestController,它带有 @Controller 注解。有三层间接,但带有 @RestController 注解的类仍然至少被视为一个带有 @Component 注解的类。专门的注解添加了专门的处理,但它们仍然是 @Component 的特例,而不是替代品。

所以,我们可能会觉得在配置类中定义 CustomerService 并对其进行配置很烦人。毕竟,如果 Spring 只知道这个类,它总能自己弄清楚其余的关系,对吧?它可以查看构造函数,发现要构造一个 CustomerService 实例,它需要对 JdbcTemplate 的引用,而 JdbcTemplate 已经在别处定义好了。

所以,这就是组件扫描所做的。您可以将 @Service(另一个构造型注解,它带有 @Component 注解)添加到类中,然后从配置类中删除 @Bean 方法。Spring 将自动创建服务,它将提供所需的依赖项。它还将扩展该类以提供这些服务。

我们正在取得进展,消除了越来越多的样板代码。但是 DataSourceJdbcTemplate 呢?您需要它们,但肯定不应该每次都重新创建它们?这就是 Spring Boot 的洞察力。它使用 @Condition 注解来装饰带有 @Component@Configuration 注解的类,以便在创建类或调用 @Bean 方法之前评估一个测试。这些测试可以查找环境中的线索。例如,假设您在类路径中有一个 H2——一个嵌入式 SQL 数据库。并且您在类路径中有一个 spring-jdbc 库,其中包含 JdbcTemplate 类。它可以使用一个测试来检查这些类在类路径中的存在情况,并推断您想要一个嵌入式 SQL DataSource,并且您想要一个与新创建的 DataSource 连接的 JdbcTemplate 实例。它有自己的配置来为您提供这些 bean。现在,您可以完全删除 @Configuration 类了!Spring Boot 提供了两个 bean,并基于构造型注解推断出另一个 bean。

我们已经了解了 Spring IoC 容器的基本动机,以及 IoC 容器如何工作以帮助实现框架所提供的承诺。

实际上,我们可以走得更远,探索面向切面编程(AOP)、自动配置等等,但本文旨在提供一个心智模型,用于理解何时应用哪种配置,以便您可以专注于将工作软件安全快速地投入生产这一重要工作。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有