领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多编辑:下面有一条很好的评论提到,原始标题“构建 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 将自动创建服务,它将提供所需的依赖项。它还会对类进行子类化以提供这些服务。
我们正在取得进展,消除了越来越多的样板代码。但是 DataSource
和 JdbcTemplate
呢?您需要它们,但肯定不应该每次都重新创建它们?这是 Spring Boot 的见解。它使用 @Condition
注解来装饰用 @Component
或 @Configuration
注解的类,以便在创建类或调用 @Bean
方法之前评估测试。这些测试可以在环境中寻找线索。例如,假设您在类路径上有 H2——一个嵌入式 SQL 数据库。并且您在类路径上拥有包含 JdbcTemplate
类的 spring-jdbc
库。它可以使用测试来测试类路径上是否存在这些类,并推断您想要一个嵌入式 SQL DataSource
,并且您想要一个用新创建的 DataSource
连接的 JdbcTemplate
实例。它有自己的配置来为您提供这些 bean。现在,您可以完全删除 @Configuration
类了!Spring Boot 提供了两个 bean,并根据原型注解暗示了另一个 bean。
我们已经了解了 Spring IoC 容器的基本动机,并且我们已经了解了 IoC 容器如何工作以帮助实现框架提出的承诺。
我们确实可以更进一步,探索面向方面编程 (AOP)、自动配置等等,但这旨在提供一个思维框架,以便了解何时应用哪种类型的配置,以便您可以专注于重要工作,即安全快速地将工作软件投入生产。