配置元数据(Configuration Metadata)
即我们常说的配置文件,用于实例化Bean和指定如何对Bean进行装配(接口的实现方式)。可基于Java或者基于XML配置。
作用示意
在庞大项目中常常把不同层的Bean配置放到不同的配置文件中,比如以下若干种配置文件
- beans-web.xml或ConfigurationForWeb.class对应Web层/表现层Bean配置
- beans-service.xml或ConfigurationForService.class对应服务层/业务层Bean配置
- beans-dao.xml或ConfigurationForDao.class对应数据访问层Bean配置
基于Java的Bean配置
首先创建一个maven项目,用quick start模板就好
展示一下目录结构
我的Maven包依赖如下,这些包基本可以满足这本书的非web应用
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>JdbcTemplateAndLambdaExpression</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.3.10.RELEASE</spring-framework.version>
<!-- Hibernate / JPA -->
<hibernate.version>5.2.10.Final</hibernate.version>
<!-- Logging -->
<logback.version>1.2.3</logback.version>
<slf4j.version>1.7.25</slf4j.version>
<!-- Test -->
<junit.version>4.12</junit.version>
<!-- Database -->
<h2.version>1.4.196</h2.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<!-- Test Artifacts -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>
</dependencies>
</project>
首先,我们在src/main/java路径下创建一个com.wiley.beginning.spring.ch2程序包
先在该包下创建Account数据类
package com.wiley.beginningspring.ch2;
import java.util.Date;
public class Account {
private long id;
private String ownerName;
private double balance;
private Date accessTime;
private boolean locked;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Date getAccessTime() {
return accessTime;
}
public void setAccessTime(Date accessTime) {
this.accessTime = accessTime;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
}
该类(上一章提到的典型POJO!)中包含了Account数据类型的几个属性以及他们的getter和setter方法(由于没有定义含参构造方法,我们可以断定这个Bean是setter注入!)
为了完成Account数据的增改删查业务,我们创建AccountDao接口
package com.wiley.beginningspring.ch2;
import java.util.List;
public interface AccountDao {
public void insert(Account account);
public void update(Account account);
public void update(List<Account> accounts);
public void delete(long accountId);
public Account find(long accountId);
public List<Account> find(List<Long> accountIds);
public List<Account> find(String ownerName);
public List<Account> find(boolean locked);
}
接下来是实现AccountDao接口的实现类的创建,为了不涉及数据库知识的方便起见,我们将数据直接储存在内存中,因此该实现类取名为AccounDaoInMemoryImpl
package com.wiley.beginningspring.ch2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AccountDaoInMemoryImpl implements AccountDao {
private Map<Long,Account> accountsMap = new HashMap<>();
{
Account account1 = new Account();
account1.setId(1L);
account1.setOwnerName("John");
account1.setBalance(10.0);
Account account2 = new Account();
account2.setId(2L);
account2.setOwnerName("Mary");
account2.setBalance(20.0);
accountsMap.put(account1.getId(), account1);
accountsMap.put(account2.getId(), account2);
}
@Override
public void insert(Account account) {
accountsMap.put(account.getId(), account);
}
@Override
public void update(Account account) {
accountsMap.put(account.getId(), account);
}
@Override
public void update(List<Account> accounts) {
for(Account account:accounts) {
update(account);
}
}
@Override
public void delete(long accountId) {
accountsMap.remove(accountId);
}
@Override
public Account find(long accountId) {
return accountsMap.get(accountId);
}
@Override
public List<Account> find(List<Long> accountIds) {
List<Account> accounts = new ArrayList<>();
for(Long id:accountIds) {
accounts.add(accountsMap.get(id));
}
return accounts;
}
@Override
public List<Account> find(String ownerName) {
List<Account> accounts = new ArrayList<>();
for(Account account:accountsMap.values()) {
if(ownerName.equals(account.getOwnerName())) {
accounts.add(account);
}
}
return accounts;
}
@Override
public List<Account> find(boolean locked) {
List<Account> accounts = new ArrayList<>();
for(Account account:accountsMap.values()) {
if(locked == account.isLocked()) {
accounts.add(account);
}
}
return accounts;
}
}
该类将Account数据都储存在一个HashMap中,通过对HashMap中元素的查找和访问来完成Account数据的增改删查。
然后我们创建AccountService接口定义用户对账户可以进行的操作——转账和存钱以及查找账户
package com.wiley.beginningspring.ch2;
public interface AccountService {
public void transferMoney(long sourceAccountId, long targetAccountId, double amount);
public void depositMoney(long accountId, double amount) throws Exception;
public Account getAccount(long accountId);
}
接着,创建利用之前创建好的数据访问接口来实现服务层类AccountServiceImpl
package com.wiley.beginningspring.ch2;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transferMoney(long sourceAccountId, long targetAccountId, double amount) {
Account sourceAccount = accountDao.find(sourceAccountId);
Account targetAccount = accountDao.find(targetAccountId);
sourceAccount.setBalance(sourceAccount.getBalance() - amount);
targetAccount.setBalance(targetAccount.getBalance() + amount);
accountDao.update(sourceAccount);
accountDao.update(targetAccount);
}
@Override
public void depositMoney(long accountId, double amount) throws Exception {
Account account = accountDao.find(accountId);
account.setBalance(account.getBalance() + amount);
accountDao.update(account);
}
@Override
public Account getAccount(long accountId) {
return accountDao.find(accountId);
}
}
为了确定主程序中的接口使用的是哪种实现,我们创建Ch2BeanConfiguration来配置Bean
package com.wiley.beginningspring.ch2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Ch2BeanConfiguration {
@Bean
public AccountService accountService() {
AccountServiceImpl bean = new AccountServiceImpl();
bean.setAccountDao(accountDao());
return bean;
}
@Bean
public AccountDao accountDao() {
AccountDaoInMemoryImpl bean = new AccountDaoInMemoryImpl();
//depedencies of accountDao bean will be injected here...
return bean;
}
}
其中@Configuration是配置类的标志,@Bean是工厂方法的标志
很明显的可以看出针对AccountService返回的为AccountServiceImpl实例,针对AccountDao返回的是AccountDaoInMemoryImpl实例,工厂方法的返回类型为接口而不是实现类可以方便我们更换接口的实现方式,这也就是IoC的优势,依赖对象管理的方便。
比如我们如果想用JDBC来实现DAO层就只要在配置文件中把返回值改为AccountDaoJdbcImpl(假设我们实现了这个类),服务层装配的类就变成了后者,而并不需要修改任何服务层代码,实现了层与层之间的去耦合。
最后创建Main函数测试服务层的功能
package com.wiley.beginningspring.ch2;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Ch2BeanConfiguration.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
System.out.println("Before money transfer");
System.out.println("Account 1 balance :" + accountService.getAccount(1).getBalance());
System.out.println("Account 2 balance :" + accountService.getAccount(2).getBalance());
accountService.transferMoney(1, 2, 5.0);
System.out.println("After money transfer");
System.out.println("Account 1 balance :" + accountService.getAccount(1).getBalance());
System.out.println("Account 2 balance :" + accountService.getAccount(2).getBalance());
}
}
Main函数通过语句
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Ch2BeanConfiguration.class);
引入了配置文件后,实例都从上下文对象(Spring容器)applicationContext中通过Spring的工厂获取,ApplicationContext.getBean()方法执行Bean查找,返回对象的类型由配置类决定,该程序打印了两个账户转账前后的余额,运行得到的结果为
功能实现正确
基于XML的配置
将基于Java配置的项目中的配置类删除,用一个ch2-beans.xml文件来代替之即可。新的目录结构如下:
ch2-beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="com.wiley.beginningspring.ch2.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.wiley.beginningspring.ch2.AccountDaoInMemoryImpl">
</bean>
</beans>
可以从中辨别出针对accountService的对象,实例化指向的是AccountServiceImpl类,其中的accountDao属性被指向名为accountDao的bean,而accountDao的Bean在之后被定义指向AccountDaoInMemoryImpl实现类,从而指定了各个Bean的装配
修改了配置方法为XML配置后,Main函数中获取配置上下文的方法也要由
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Ch2BeanConfiguration.class);
改为
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/com/wiley/beginningspring/ch2/ch2-beans.xml");
基于注释的配置
除了以上两种在一个文件中包含Bean的装配方法外,也可以通过注释来指定Bean对象并通过@Autowired注解来自动装配Bean,此时配置XML中就只要指定Bean查找的包范围
此时Bean实现类都加上@Component注释或者扩展自它的注释表示该类为一个Bean,扫描Bean的时候就可以扫描到该类
@Repository
public class AccountDaoInMemoryImpl implements AccountDao {
//Implementation
}
@Service
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
@Autowired
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
}
@Service和@Repository都扩展自@Component,前者仅仅表明该类为一个Bean,后者还与一些Spring数据访问功能相关。setAccountDao()方法上的@Autowired注释表明该处参数accountDao的实现会在配置文件指定的包中自动查找
此时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: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/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.wiley.beginningspring.ch2"/>
</beans>
指定了查找定义为Bean的类的包
注意,配置Bean的XML文件中xsi:schemaLocation指定时
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
和
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
都是可行的,后者没有指定版本会默认采用最新版本