对象之间的依赖增加复杂性,导致对象之间紧耦合。
比如下面的TransferService组件依赖其他两个组件:TransferRepository和AccountRepository:
如果直接使用实例:
public class TransferService {
private AccountRepository accountRepository;
public TransferService () {
this.accountRepository = new AccountRepository();
}
public void transferMoney(Account a, Account b) {
accountRepository.transfer(a, b);
}
}
TransferService对象需要一个AccountRepository对象,实现账户A到账户B的转账功能。于是,直接增加AccountRepository的实例。这样很难维护,也很难做TransferService的单元测试。
也可以使用工厂模式实现该功能:
public class TransferService {
private AccountRepository accountRepository;
public TransferService() {
this.accountRepository = AccountRepositoryFactory.getInstance("jdbc");
}
public void transferMoney(Account a, Account b) {
accountRepository.transfer(a, b);
}
}
上面的代码,我们使用工厂模式增加AccountRepository类的对象。更好的做法是program-to-interface,针对这个具体问题,我们可以增加一个接口,降低耦合:
public interface AccountRepository {
void transfer();
//other methods
}
我们可以增加JdbcAccountRepositry类,实现AccountRepository接口:
public class JdbcAccountRepositry implements AccountRepositry{
//实现AccountRepositry定义的方法
// 实现其他方法
}
这样,由工厂类增加的对象很容易维护。使用工厂类,也可以增加可配置的对象。但是,为了降低耦合,获取协作组件的时候在业务组件里增加了工厂类。让我们看看依赖注入怎么解决这个问题。
对于依赖注入模式,我们只是定义依赖性,而不用解决对象之间的依赖。看下图,我们会在需要对象的时候注入他们:
TransferService依赖AccountRepository和TransferRepository。TransferService可以使用多种TransferRepository完成转账功能,比如可以使用JdbcTransferRepository或者是JpaTransferRepository,依赖哪个,由部署环境决定。
public class TransferServiceImpl implements TransferService {
private TransferRepository transferRepository;
private AccountRepository accountRepository;
public TransferServiceImpl(TransferRepository transferRepository, AccountRepository accountRepository) {
this.transferRepository = transferRepository;//TransferRepository is injected
this.accountRepository = accountRepository;//AccountRepository is injected
}
public void transferMoney(Long a, Long b, Amount amount) {
Account accountA = accountRepository.findByAccountId(a);
Account accountB = accountRepository.findByAccountId(b);
transferRepository.transfer(accountA, accountB, amount);
}
}
TransferServiceImpl没有增加自己的repositories实现,而是在构造器里通过构造参数传递的。这叫构造器注入。这样,TransferServiceImpl没有和任何repositories的实现相耦合,可以根据需要切换不同的实现。
Spring提供了把程序的各个部件组装成程序的支持:
配置代码可以是这的:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public TransferService transferService(){
return new TransferServiceImpl(accountRepository(), transferRepository());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository();
}
@Bean
public TransferRepository transferRepository() {
return new JdbcTransferRepository();
}
}
Spring程序,使用程序上下文的一个实现加载bean定义,放进Spring容器。比如下面的代码,就使用AnnotationConfigApplicationContext加载配置类AppConfig,得到AccountService的一个对象:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TransferMain {
public static void main(String[] args) {
//加载上下文
ConfigurableApplicationContext applicationContext =
new AnnotationConfigApplicationContext(AppConfig.class);
//得到 TransferService bean
TransferService transferService =
applicationContext.getBean(TransferService.class);
//转账
transferService.transferAmmount(100l, 200l, new Amount(2000.0));
applicationContext.close();
}
}
依赖注入让组件之间松耦合,面向切面编程(Spring AOP)可以捕获程序中重复出现的常用功能。能以最优雅的方式,把下列功能分开。可以通过声明的方式,透明地应用这些服务:
列表中的组件不是你核心程序的一部分,但是他们是额外的责任,通常称为cross-cutting concerns,在核心责任之上跨多个组件。如果你把这些组件放到你的核心功能,而没有模块化,会导致两个主要问题:
Spring AOP可以把cross-cutting concern模块化。Spring AOP是通过代理模式实现的:
比如,使用LoggingAspect实现日志功能:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* *.transferAmount(..))")
public void logBeforeTransfer(){
System.out.println("####LoggingAspect.logBeforeTransfer() method called before transfer amount####");
}
@After("execution(* *.transferAmount(..))")
public void logAfterTransfer(){
System.out.println("####LoggingAspect.logAfterTransfer() method called after transfer amount####");
}
}
修改AppConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public TransferService transferService(){
return new TransferServiceImpl(accountRepository(), transferRepository());
}
@Bean
public AccountRepository accountRepository() {
return new JdbcAccountRepository();
}
@Bean
public TransferRepository transferRepository() {
return new JdbcTransferRepository();
}
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}
在企业程序里,经常会看到很多相似的代码。比如使用JDBC,经常会写相同的代码,处理下列问题:
比如getAccountById方法的代码可能是这样的:
public Account getAccountById(long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(
"select id, name, amount from " +
"account where id=?");
stmt.setLong(1, id);
rs = stmt.executeQuery();
Account account = null;
if (rs.next()) {
account = new Account();
account.setId(rs.getLong("id"));
account.setName(rs.getString("name"));
account.setAmount(rs.getString("amount"));
}
return account;
} catch (SQLException e) {
} finally {
if(rs != null) {
try {
rs.close();
} catch(SQLException e) {}
}
if(stmt != null) {
try {
stmt.close();
} catch(SQLException e) {}
}
if(conn != null) {
try {
conn.close();
} catch(SQLException e) {}
}
}
return null;
}
Spring JDBC使用模板设计模式,让数据访问代码更简洁,也能防止连接泄漏:
public Account getAccountById(long id) {
return jdbcTemplate.queryForObject(
"select id, name, amoount" +
"from account where id=?",
new RowMapper<Account>() {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
account = new Account();
account.setId(rs.getLong("id"));
account.setName(rs.getString("name"));
account.setAmount(rs.getString("amount"));
return account;
}
},
id);
}
有两种不同的Spring容器: