领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Spring框架在2003年成为事实上的标准,并一直以来帮助人们用更简洁的代码构建更大、更好的应用程序。在本文中,我们将讨论使用Spring组件模型配置应用程序的可用选项。我们将从最简单的形式构建一个简单的应用程序,并对其进行重构,以利用Spring框架中的许多简化功能,这些功能使其成为当今应用程序的事实标准,并将继续如此。
当今的企业Java应用程序有许多协同工作的对象,它们协同工作以达成一个目标,通常是具有内在商业价值的目标。这个对象图很深,即使在简单的情况下也是如此。例如,考虑一个想要与数据库通信的服务(可能是一个支持处理客户数据的服务?)。这样的服务需要一个数据源,并且可以选择某种便利库来促进数据库访问,无论是通过JDBC、JPA、JDO、NoSQL选项等。在这种图中,直接在需要它们的站点创建对象是很诱人的。在这种系统中,对象构造或获取的知识分散在使用它们的bean中。如果——就像数据库javax.sql.DataSource一样——该对象在多个地方都需要,那么在一个地方设置所有对象然后再共享新创建的实例会更清晰。这具有将易出错和易变的配置信息集中在一个易于更改的地方的优点(例如,当开发和生产环境中的数据库凭据不同时)。
这是人们使用Spring的主要原因之一——因为Spring使人们能够集中描述这些协同工作的对象。从Spring的早期版本开始,就有一个XML文件用于描述对象图。在早期(约2003年),该文件使用DTD,但现在使用XML Schema。下面是我们用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<T>接口,则将调用接口上的getObject()方法,其结果将是Spring上下文中可用的对象。这种做法在Spring框架本身中被广泛使用,以提供一种可重用方式方便地构造复杂的对象图。
在我们的示例中,我们使用一个名为H2的嵌入式数据库,它是一个功能强大的内存Java数据库。通常,嵌入式数据库在开发环境中使用。一种常见的做法是在开发中使用嵌入式数据库来快速测试和重置数据集。通常,这还包括从SQL脚本加载数据来引导嵌入式数据库。Spring框架为配置嵌入式数据源和然后评估javax.sql.DataSource提供显式支持。
javax.sql.DataSource——就是这样的场景。当然,使用Spring 3.0或更早版本实现相同灵活性的其他方法包括FactoryBean和PropertyPlaceHolderConfigurer。回顾我们之前的例子,我们可以像这样声明我们的javax.sql.DataSource,名为ds,而不是
<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 Schema提供的验证在设计时提供更多指导。这就是为什么Spring框架长期以来支持使用基于XML Schema的命名空间,这些命名空间提供更多的反馈和简化。让我们回顾一下嵌入式数据库的例子。Spring框架提供了一个用于配置嵌入式数据源的命名空间。要在Spring中使用命名空间,只需限定命名空间并添加对schemaLocation元素的引用。之前的Spring配置文件,在修改以支持JDBC 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"
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自行处理,而不是必须显式地写出来。要做到这一点,我们需要在文件中添加context命名空间并启用组件扫描。组件扫描扫描带有某些注解的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已经配置在上下文中了。由于只配置了一个,我们可以简单地将类上的setter注解为@Autowired,这告诉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一样。适用于普通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服务器、应用程序服务器和云环境的锁定,同时最大化您在基础平台上的投资回报。