Spring 5 設計模式 - 開始
Spring 5 設計模式 - 開始
依賴注入
物件之間的依賴增加複雜性,導致物件之間緊耦合。
比如下面的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();
}
}
AOP
依賴注入讓元件之間鬆耦合,面向切面程式設計(Spring AOP)可以捕獲程式中重複出現的常用功能。能以最優雅的方式,把下列功能分開。可以通過宣告的方式,透明地應用這些服務:
- 日誌和跟蹤
- 事務管理
- 安全
- 快取
- 錯誤處理
- 效能監視
- 自定義業務規則
列表中的元件不是你核心程式的一部分,但是他們是額外的責任,通常稱為cross-cutting concerns,在核心責任之上跨多個元件。如果你把這些元件放到你的核心功能,而沒有模組化,會導致兩個主要問題:
- Code tangling:比如安全、事務、日誌等程式碼和業務邏輯耦合在一起
- Code scattering:相同的concern散佈在模組之間
Spring AOP可以把cross-cutting concern模組化。Spring AOP是通過代理模式實現的:
- 實現程式邏輯:你實現業務邏輯的時候,不需要擔心附加功能,比如日誌、安全等。
- 寫aspects,實現cross-cutting concerns
- 在程式裡Weave aspects:把cross-cutting行為放到正確的位置,可以通過宣告的方式注入
比如,使用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();
}
}
template
在企業程式裡,經常會看到很多相似的程式碼。比如使用JDBC,經常會寫相同的程式碼,處理下列問題:
- 從連線池獲取連線
- 增加PreparedStatement物件
- 繫結SQL引數
- 執行PreparedStatement物件
- 從ResultSet物件檢索資料,填充資料容器
- 釋放資料庫資源
比如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容器
有兩種不同的Spring容器:
- Bean factory:物件池,通過配置建立和管理物件
- Application contexts:bean工廠的簡單包裝,還提供了額外的程式上下文
bean的生命週期
- 載入所有的bean定義,增加有序圖(ordered graph)
- 例項化,執行BeanFactoryPostProcessors(可以在這裡修改bean定義)
- 例項化每個bean
- 把值和bean引用注入bean的屬性
- 如果bean實現了BeanNameAware介面,就呼叫setBeanName()方法設定bean的ID
- 如果bean實現了BeanFactoryAware,就呼叫setBeanFactory()方法設定bean工廠的引用
- 如果bean實現了ApplicationContextAware,就呼叫setApplicationContext()方法設定程式上下文的引用
- 如果bean實現了BeanPostProcessor,呼叫postProcessBeforeInitialization()修改bean的例項
- 如果bean實現了InitializingBean,呼叫afterPropertiesSet()方法做初始化或者載入資源。也可以用其他方法實現這一步,比如@PostConstruct註解
- 如果bean實現了BeanPostProcessor,呼叫postProcessAfterInitialization(),在初始化之後修改bean。此時,bean已經可以使用,你的程式可以通過使用getBean()訪問這個bean。呼叫程式上下文的close()之前,bean一直活著
- 如果bean實現了DisposibleBean,Spring呼叫它的destroy()方法執行銷燬過程,釋放資源。也可以用其他方法實現這一步,@PreDestroy