领先一步
VMware提供培训和认证,以加速您的进步。
了解更多Spring 框架在 2003 年成为事实上的标准,从那时起,它一直在帮助人们构建更大、更好的应用程序,并编写更简洁的代码。在这篇文章中,我们将讨论使用 Spring 组件模型配置应用程序的可用选项。我们将从最简单的形式开始构建一个简单的应用程序,并对其进行修改,以利用 Spring 框架中许多简化功能,这些功能使其成为当今应用程序的事实上的标准,并且也将继续如此。
现代企业 Java 应用程序包含许多协作对象,它们协同工作以实现某个目标,通常是具有内在业务价值的目标。即使在简单的情况下,此对象图也很深。例如,一个简单的服务(也许是支持处理客户数据的服务?)想要与数据库进行通信。此类服务需要一个数据源,并且可以选择一些便利库来促进数据库访问,无论是通过 JDBC、JPA、JDO、NoSQL 选项等。在这种图中,很容易在使用它们的地方根据需要创建对象。在这种系统中,对象构造或获取的知识散布在使用它们的 bean 中。如果 - 就像数据库javax.sql.DataSource
一样 - 该对象在多个地方需要,那么在一个地方设置所有对象,然后共享新创建的实例会更简洁。这样做的好处是将容易出错且易变的配置信息集中在一个位置,以便于更改(例如,当数据库凭据在开发和生产环境之间有所不同时)。
这是人们使用 Spring 的主要原因之一 - 因为 Spring 使人们能够集中描述这些协作对象。从 Spring 的早期版本开始,就有一个 XML 文件用于描述对象图。在早期(大约 2003 年),此文件使用 DTD,但现在使用 XML 模式。以下是我们用 Spring 的 XML 格式描述的服务示例。随着我们的进度,我们将删除越来越多的 XML 配置。每个bean
元素描述一个将被创建并赋予id
的对象。每个property
元素描述对象上的 setter 方法以及应赋予它的值。这些 setter 会由 Spring 应用程序容器为您调用。
<?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 class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="ds">
<property name="driverClassName" value="org.h2.Driver"/>
<property name="url" value="jdbc:h2:~/cs"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<constructor-arg ref="ds"/>
</bean>
<bean class="org.springframework.samples.DatabaseCustomerService" id="databaseCustomerService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean class="org.springframework.samples.CustomerClient" id="client">
<property name="customerService" ref="databaseCustomerService"/>
</bean>
</beans>
很简单,对吧?好的部分是,我们可以针对接口编写代码,而无需任何特定的实现知识。在上面的示例中,我们实例化了一个DriverManagerDataSource
并通过使用ref
属性将其传递给JdbcTemplate
实例的构造函数。ref
告诉 Spring 框架您希望传递对在同一容器中配置的另一个 bean 的引用。同样,在我们的示例中,我们传递了对CustomerClient
实例的引用,但在使用 Java 代码中,我们针对CustomerService
接口进行编码,而不是特定类型DatabaseCustomerService
。
这个简单的设置为我们提供了很多间接性和灵活性。现在对象创建和构造已移至 Spring 配置中,我们可以将非常复杂的设置隐藏在 Spring 配置中,而代码对此一无所知。隐藏复杂构造逻辑的一种常见方法是通过工厂模式。如果您正在构造许多需要协同工作的对象,或者在创建对象时想要考虑许多不同的因素,则工厂模式特别有用。本质上,您正在做的是提供一种比任何一个类的构造函数自然可以做到的更强大的方式来描述对象创建。Spring 明确支持此模式。如果配置的 bean 实现了org.springframework.beans.factory.FactoryBean
接口,则将调用接口上的getObject()
方法,结果将是在 Spring 上下文中可用的对象。此做法在 Spring 框架本身中被广泛使用,以提供方便的方式以可重用的方式构建复杂的对象图。
在我们的示例中,我们使用了一个名为 H2 的嵌入式数据库,它是一个功能强大的内存 Java 数据库。嵌入式数据库通常用于开发环境。一种常见的做法是在开发中使用嵌入式数据库来快速测试和重置数据集。通常,这也需要从 SQL 脚本加载数据以引导嵌入式数据库。Spring 框架为配置嵌入式数据源然后针对javax.sql.DataSource
评估脚本提供了明确的支持。
javax.sql.DataSource
就是这样一种情况。当然,使用 Spring 3.0 或更早版本还可以通过其他方法实现相同的灵活性,包括FactoryBean
和PropertyPlaceHolderConfigurer
。重新审视我们之前的示例,我们可以像这样声明名为ds
的javax.sql.DataSource
<bean id="ds" class="org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean">
<property name="databaseType" value="H2"/>
<property name="databasePopulator">
<bean class="org.springframework.jdbc.datasource.init.ResourceDatabasePopulator">
<property name="scripts" value="setup.sql"/>
</bean>
</property>
</bean>
此FactoryBean
负责读取任何 SQL 脚本(我在这里只指定了一个名为setup.sql
的文件,尽管您可以根据需要指定任意多个文件,用逗号分隔)并将它们加载到数据库中,然后以简单、方便的方式返回javax.sql.DataSource
实例。
我们已经看到了FactoryBean
可以提供的强大功能。精心设计的FactoryBean
将显示最有可能在设置对象时有用的选项,并且在运行时,它还将提供有关各种选项的无效配置的反馈。但是,XML 可以通过 XML 模式提供的验证在设计时提供更多指导。这就是为什么 Spring 框架长期以来一直支持使用基于 XML 模式的命名空间来描述,从而提供更多反馈和简化。让我们重新审视嵌入式数据库示例。Spring 框架提供了一个命名空间来配置嵌入式数据源。要在 Spring 中使用命名空间,您只需限定命名空间并添加对schemaLocation
元素的引用。当修改为支持 JDBC XML 命名空间时,之前的 Spring 配置文件如下所示
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
<!-- ... same as before ... -->
</beans>
此声明将启用命名空间中限定的元素在任何现代 IDE 的 XML 自动完成功能中被提议。安装此功能后,我们现在可以使用更简洁的内容替换之前的声明。
<jdbc:embedded-database id="ds" type="H2">
<jdbc:script location="classpath:setup.sql"/>
</jdbc:embedded-database>
这在功能上等同于前面的示例:它创建了一个嵌入式数据库,并在启动嵌入式数据库时评估脚本的内容。它创建了一个类型为javax.sql.DataSource
的对象,就像之前一样。我们巧妙地将嵌入式数据库简化为其本质。看起来我们的工作已经完成了,我们可以继续了,对吧?嗯,不完全是。我们仍然可以做很多事情来进一步删除配置。此处显示的一些代码是我们编写的自定义代码。如果我们愿意注释代码,那么我们可以让 Spring 帮我们找出正确的事情,而不是必须明确地说明它。为此,我们需要将上下文命名空间添加到我们的文件中并启用组件扫描。组件扫描扫描具有某些注释的 bean 并自动注册它们。同样,在类本身中发现的注释将被处理。以下是包含相应 XML 命名空间的修改后的 XML 文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="... http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples"/>
<!-- ... same as before ... -->
</beans>
我们在org.springframework.samples
包中使用 Spring 框架的@Component
注释注释的所有 bean 都将被拾取并作为 bean 注册到上下文中,就像我们在 XML 中使用bean
元素配置它们一样。我们已经用@Component
注释了DatabaseCustomerService
和CustomerClient
类,这使我们能够从 XML 配置中删除这些 bean 的等效bean
元素。组件扫描非常方便,因为 Spring 承担了大部分繁重的工作,尽管它分散了配置。
我们知道此 bean 依赖于JdbcTemplate
。JdbcTemplate
已在上下文中配置。由于只配置了一个,因此我们可以简单地用@Autowired
注释类上的 setter,这告诉 Spring 按类型解析依赖项并注入它。如果上下文中配置了多个实例,则在这种情况下将抛出错误。
@Autowired
之外,还有许多方法可以告诉 Spring 注入哪个 bean。您可以使用 JSR 330 支持的注释,如@javax.inject.Inject
,或 JSR 250 的@javax.annotation.Resource
,或 Spring 的@Value
注释。package org.springframework.samples;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.sql.ResultSet;
import java.sql.SQLException;
@Component
public class DatabaseCustomerService implements CustomerService {
private JdbcTemplate jdbcTemplate;
private RowMapper<Customer> customerRowMapper = new CustomerRowMapper();
public Customer getCustomerById(long id) {
return jdbcTemplate.queryForObject(
"select * from CUSTOMERS where ID = ?", this.customerRowMapper, id);
}
@Autowired
public void setJdbcTemplate( JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
class CustomerRowMapper implements RowMapper<Customer> {
public Customer mapRow(ResultSet resultSet, int i) throws SQLException {
String fn = resultSet.getString("FIRST_NAME");
String ln = resultSet.getString("LAST_NAME");
String email = resultSet.getString("EMAIL");
long id = resultSet.getInt("ID");
return new Customer(id, fn, ln, email);
}
}
}
最后一个类是使用CustomerService
实例的客户端。我们像以前一样使用@Component
注释注册它。它需要一个对CustomerService
实例的引用,就像DatabaseCustomerService
实例需要一个对JdbcTemplate
的引用一样。因此,我们使用我们老朋友@Autowired
。
package org.springframework.samples;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class CustomerClient {
private CustomerService customerService ;
@Autowired
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
public void printCustomerInformation ( long customerId ) {
Customer customer = this.customerService.getCustomerById( customerId );
System.out.println( customer ) ;
}
}
我们修改后的 XML 文件为我们节省了许多麻烦
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples"/>
<jdbc:embedded-database id="ds" type="H2">
<jdbc:script location="classpath:setup.sql"/>
</jdbc:embedded-database>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<constructor-arg ref="ds"/>
</bean>
</beans>
我们使用命名空间支持来简化嵌入式javax.sql.DataSource
实例的配置。我们使用组件扫描让Spring自动为我们注册DatabaseCustomerService
和CustomerClient
bean。情况看起来还不错,但我们仍然使用XML来描述JdbcTemplate
的配置,而这可以用Java更轻松地描述。在这种情况下,XML并不是比普通的Java更具吸引力的解决方案;它仅仅提供了同等的功能。如果我们也可以对JdbcTemplate
实例使用组件扫描就好了。但是,组件扫描仅适用于使用@Component
注解的bean。由于我们无法向可能没有源代码的第三方类添加@Component
注解,因此组件扫描对于JdbcTemplate
实例来说不是一种选择。
Spring提供了Java配置支持,允许您直接使用Java来描述和配置bean。Java配置选项提供了两全其美的优势:它允许您配置任何类,即使是您没有源代码的类(与XML配置选项一样),并且它仍然以Java为中心,因此可以从Java语言的所有类型安全优势(以及Java IDE中提供的重构工具)中受益。
Java配置处理注册到上下文中的bean,并查找使用@Bean
注解的方法并调用它们。方法调用的结果将作为bean注册到应用程序上下文中,就像您使用XML配置对象一样。bean的类型是返回对象的类型,并且id
取自方法名称。由于配置由方法中的Java代码提供,因此您可以进行任何设置,就像FactoryBean
允许您做的那样。人们通常选择Java配置选项,因为它允许您将bean配置保存在一两个众所周知的中心类中。XML配置和Java配置都提供了一种集中描述应用程序的方式。
配置类是一个Spring bean,与其他任何bean一样。适用于常规Spring bean的所有规则都适用于配置bean,使用@Bean
注解的方法除外。Spring将使用组件扫描拾取您的配置类。如果您想在配置类中使用其他bean(例如,我们之前使用XML命名空间配置的javax.sql.DataSource
实例),那么您可以使用所有可用的正常选项来获取它们,包括@Autowired
。让我们来看一下我们示例的配置类。
package org.springframework.samples;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class CustomerConfiguration {
@Autowired private DataSource dataSource;
@Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(this.dataSource);
}
}
此类使用@Autowired
获取对嵌入式数据源的引用。这类似于它在先前示例中的使用方法,只是这里我们正在私有字段变量上使用注解,而不是setter方法。Spring框架将与类构造函数、字段变量或setter方法上的注解一起工作。最后,我们有一个使用@Bean
注解的方法。此方法提供了JdbcTemplate
实例的定义,这意味着我们可以从文件中删除XML配置。我们在这里不做此操作,但您可以在一个类中定义多个@Bean
定义方法,并且它们可以通过简单地相互调用来相互引用。如果一个使用@Bean
注解的方法调用另一个方法,则返回值要么是一个新创建的对象,要么是——如果bean已经创建——已经注册到上下文中的bean。在上面的类中,我们还在类上有一个@Configuration
注解。此注解告诉Spring将此类视为一种特殊的组件类型,专门用于配置。从本质上讲,此bean受益于与在Spring上下文中注册的任何bean相同的服务,并且它应用了额外的服务来启用Java配置。要使用Java配置,请确保您的类路径上有CGLIB库。
最终修改后的XML文件如下所示
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.springframework.samples"/>
<jdbc:embedded-database id="ds" type="H2">
<jdbc:script location="classpath:setup.sql"/>
</jdbc:embedded-database>
</beans>
在这篇文章中,我们追踪了您也可以遵循的步骤,通过Spring的依赖注入功能实现更简洁、更友好的代码库。尽管我们讨论了提供比其他任何东西都更灵活和更多功能的出色技术,但重要的是要记住,这种令人难以置信的支持已经存在至少4年了。大多数情况下,时间更长。人们长期以来一直在使用这些部分作为其应用程序的基础。依赖注入和控制反转仅仅是开始。Spring框架为大量不断增长的用例提供了许多简化库,这些用例建立在此处建立的组件模型之上。
当您在Spring之上构建应用程序时,您可以避免与Web服务器、应用程序服务器和云环境(应用程序部署到的环境)的锁定,同时最大程度地提高对底层平台的投资回报。