『壹』 spring中如何使用策略模式
這里使用登錄做例子介紹如何實現登錄的多種策略
上圖是策略模式的基礎模型。
context
Context上下文角色,也叫Context封裝角色,起承上啟下的作用,屏蔽高層模塊對策略、演算法的直接訪問,封裝可能存在的變化。
Strategy
策略角色,介面類,封裝相同類型策略公共方法,具體策略實現演算法
ConcreteStrategy
具體策略實現Strategy中的抽象方法,編寫具體演算法實現。
至此基本的策略模式就說完了,具體實現看下面在spring中我們如何實現這種策略模式來實現多種登錄方式:
在spring中怎樣實現Context 上下文角色
/**
}
具體策略實現
@Component
@Slf4j
public class PhoneChatLoginStrategy implements LoginService {
@Override
public LoginType getLoginType() {
return LoginType.PHONE;
}
}
@Component
@Slf4j
public class WeChatLoginStrategy implements LoginService {
@Override
public LoginType getLoginType() {
return LoginType.WE_CHAT;
}
}
每個策略我們提供了一個枚舉,這樣方便我們取具體策略。
public enum LoginType {
QQ,
WE_CHAT,
PHONE;
}
我們這里寫了兩個登錄策略,具體調用:
@RestController
@RequestMapping("/user")
@Slf4j
public class PublicUserController {
@Autowired
private LoginStrategyFactory loginStrategyFactory;
}
這樣我們就完成了在spring中使用策略模式完成多種登錄策略。
『貳』 Spring Tx源碼解析(二)
上一篇 我們介紹了 spring-tx 中的底層抽象,本篇我們一起來看看圍繞這些抽象概念 spring-tx 是如何打造出聲明式事務的吧。籠統的說, spring-tx-5.2.6.RELEASE 的實現主要分為兩個部分:
這兩部分彼此獨立又相互成就,並且每個部分都有著大量的源碼支撐,本篇我們先來分析 spring-tx 中的AOP部分吧。
EnableTransactionManagement 註解想必大家都很熟悉了,它是啟用 Spring 中注釋驅動的事務管理功能的關鍵。
EnableTransactionManagement 註解的主要作用是向容器中導入 ,至於註解中定義的幾個屬性在 Spring AOP源碼解析 中有過詳細分析,這里就不再贅述了。
由於我們並沒有使用 AspectJ ,因此導入容器的自然是 這個配置類。
這個配置類的核心是向容器中導入一個類型為 的Bean。這是一個 PointcutAdvisor ,它的 Pointcut 是 , Advice 是 TransactionInterceptor 。
利用 TransactionAttributeSource 解析 @Transactional 註解的能力來選取標注了 @Transactional 註解的方法,而 TransactionInterceptor 則根據應用提出的需求(來自對 @Transactional 註解的解析)將方法增強為事務方法,因此 可以識別出那些標注了 @Transactional 註解的方法,為它們應用上事務相關功能。
TransactionInterceptor 能對方法進行增強,但是它卻不知道該如何增強,比如是為方法新開一個獨立事務還是沿用已有的事務?什麼情況下需要回滾,什麼情況下不需要?必須有一個『人』告訴它該如何增強,這個『人』便是 TransactionAttributeSource 。
@Transactional 註解定義了事務的基礎信息,它表達了應用程序期望的事務形態。 TransactionAttributeSource 的主要作用就是解析 @Transactional 註解,提取其屬性,包裝成 TransactionAttribute ,這樣 TransactionInterceptor 的增強便有了依據。
前面我們已經見過, spring-tx 使用 來做具體的解析工作,其父類 定義了解析 TransactionAttribute 的優先順序,核心方法是 computeTransactionAttribute(...) 。
默認只解析 public 修飾的方法,這也是導致 @Transactional 註解失效的一個原因,除此之外它還實現了父類中定義的兩個模板方法:
同時為了支持 EJB 中定義的 javax.ejb.TransactionAttribute 和 JTA 中定義的 javax.transaction.Transactional 註解, 選擇將實際的提取工作代理給 TransactionAnnotationParser 。Spring 提供的 @Transactional 註解由 進行解析。
的源碼還是很簡單的,它使用 AnnotatedElementUtils 工具類定義的 find 語義來獲取 @Transactional 註解信息。 RuleBasedTransactionAttribute 中 rollbackOn(...) 的實現還是挺有意思的,其它的都平平無奇。
RollbackRuleAttribute 是用來確定在發生特定類型的異常(或其子類)時是否應該回滾,而 NoRollbackRuleAttribute 繼承自 RollbackRuleAttribute ,但表達的是相反的含義。 RollbackRuleAttribute 持有某個異常的名稱,通過 getDepth(Throwable ex) 演算法來計算指定的 Throwable 和持有的異常在繼承鏈上的距離。
程序猿只有在拿到需求以後才能開工, TransactionInterceptor 也一樣,有了 TransactionAttributeSource 之後就可以有依據的增強了。觀察類圖, TransactionInterceptor 實現了 MethodInterceptor 介面,那麼自然要實現介面中的方法:
可以看到, TransactionInterceptor 本身是沒有實現任何邏輯的,它更像一個適配器。這樣分層以後, TransactionAspectSupport 理論上就可以支持任意類型的 Advice 而不只是 MethodInterceptor 。實現上 TransactionAspectSupport 確實也考慮了這一點,我們馬上就會看到。
invokeWithinTransaction(...) 的流程還是非常清晰的:
第一步前文已經分析過了,我們來看第二步。
TransactionInfo 是一個非常簡單的類,我們就不費什麼筆墨去分析它了。接著看第三步,這一步涉及到兩個不同的操作——提交或回滾。
至此, TransactionInterceptor 於我們而言已經沒有任何秘密了。
本篇我們一起分析了 spring-tx 是如何通過 spring-aop 的攔截器將普通方法增強為事務方法的,下篇就該說道說道 PlatformTransactionManager 抽象下的事務管理細節啦,我們下篇再見~~
『叄』 設計模式-Spring中常用的設計模式
設計模式是一種思想,是一種更快更好更優雅地解決問題的一種思想。這種思想在很多優秀的框架中都有落地。比如 Spring 框架。
接下來,我將從我的角度出發,粗淺的想一下,Spring 框架中常用的一些設計模式。
Spring 框架核心解決的問題是什麼呢?個人理解是為了解決對象之間復雜的依賴關系,降低耦合。或者可以這么說,做項目我們也可以什麼框架都不用,實現功能的時候,創建多個類,自己去維護類什麼時候創建、使用、銷毀等生命周期以及類之間的關系。自己維護的很好或者功能很簡單的話,也可以不使用框架。框架本身也就是一個黑盒工具而已,開源使我們可以將其白盒化。
既然Spring框架的核心內容假設是解決對象之間復雜的依賴關系。 通俗來說便是「要啥給啥」。為了實現這個目的,Spring 核心的 IOC 容器出現了,對象都放在這個容器里,需要的時候從裡面取。那就涉及到幾點:
1. 創建對象,保存對象,保存對象之間的關聯關系
2. 獲取對象
涉及到如何創建,是只創建一次還是創建多次,有關聯關系,先創建A還是先創建B等。 單例模式、原型模式、工廠模式、策略模式 可以來幫忙。
工廠模式:
Spring 中的 BeanFactory、FactoryBean
單例模式 :
保證一個類僅有一個實例,並提供一個全局訪問點。Spring 下默認創建的 Bean 都是單例對象。
常用的單例模式寫法又有很多:最簡單的就是 懶漢式了,還有 餓漢式、注冊式、序列化方式、枚舉方式等。
原型模式 :
Java 中的克隆對象。以某個對象為原型,復制出一個新的對象。兩個對象內容相同,但是對象實例不同。
用於創建重復的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。
這種模式是實現了一個原型介面,該介面用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的資料庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫調用。
策略模式:
一個類的行為或其演算法可以在運行時更改。這種類型的設計模式屬於行為型模式。
在策略模式中,我們創建表示各種策略的對象和一個行為隨著策略對象改變而改變的 context 對象。策略對象改變 context 對象的執行演算法。
定義一系列的演算法,把它們一個個封裝起來, 並且使它們可相互替換。
獲取對象的時候,有策略方法,也可以通過代理進行功能增強等。策略模式、代理模式、模板方法模式、適配器模式、裝飾器模式等
代理模式:
為其他對象提供一種代理以控制對這個對象的訪問。從結構上來看和 裝飾器模式類似, 但 Proxy 是控制,更像是一種對功能的限制,而 裝飾器是增加職責。
Spring 的 AOP 代理,應該是很出名的。JdkDynamicAopProxy、Cglib2AopProxy。
模板模式:
定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。比如 JdbcTemplate
適配器模式 :
將一個類的介面轉換成客戶希望的另外一個介面。適配器模式使得原本由於介面不兼容而不能一起工作的那些類可以一起工作。
Spring AOP 模塊對 BeforeAdvice、AfterAdvice、ThrowsAdvice 三種通知類型的支持實際上是藉助適配器模式來實現的,這樣的好處是使得框架允許用戶向框架中加入自己想要支持的任何一種通知類型,上述三種通知類型是 Spring AOP 模塊定義的,它們是 AOP 聯盟定義的 Advice 的子類型。
屬於結構型模式,適配類與被適配類之間沒有必然聯系。滿足 has-a 關系。
裝飾器模式 :
屬於結構型模式。滿足 is-a 關系。
Spring 中用到的包裝器模式在類名上有兩種表現:一種是類名中含有 Wrapper,另一種是類名中含有 Decorator。基本上都是動態地給一個對象添加一些額外的職責。
觀察者模式:
定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象 都得到通知並被自動更新。
當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬於行為型模式。
Spring 中 Observer 模式常用的地方是 Listener 的實現。如 ApplicationListener。
『肆』 Spring Security PasswordEncoder
Spring Security使用單向密碼轉換存儲密碼,也就是加密後的用戶密碼無法恢復成明文,只能用作密碼比較。另外隨著計算機性能的提升,傳統的SHA-256哈希加密方式不再安全。Spring Security採用了自適應單向加密方式,它通過刻意消耗計算機計算能力來加強密碼被破解的難度,比如一個密碼加密一次需要100毫秒,可能破解整個系統的密碼只需要幾小時,如果一個密碼加密一次需要1秒那麼破解整個系統的密碼就需要幾天。以 BCryptPasswordEncoder 為例,它內部有一個叫 strength 的工作因素,其值范圍是4~31,值越大其循環加密的次數就越多。
當通過 BCryptPasswordEncoder.encode 進行加密的時候, strength 這個參數會被附加到 salt 中, BCrypt.hashpw 通過 salt 獲取 strength ,然後通過 BCrypt.crypt_raw 來使用。
在 BCrypt.crypt_raw 中的入參 log_rounds 就是之前提到的 strength ,它通過 rounds = 1 << log_rounds; 左移獲得一個循環數,最終通過該循環數提高整個加密過程的計算能力消耗。
PasswordEncoder 介面是Spring Security提供的統一密碼介面,主要為整個安全框架提供一個統一的加密過程。其主要的實現類如下:
DelegatingPasswordEncoder 是Spring Security默認使用的加密演算法。我們從它的名稱其實可以猜測出來它本身並不是一個具體的演算法實現類,而是一個演算法代理類。這個類主要目的是兼容老舊系統,方便老舊系統的升級改造。
DelegatingPasswordEncoder 可以通 PasswordEncoderFactories.() 來創建一個默認的實現方式。
PasswordEncoderFactories.() 會首先創建一個 Map ,然後將各種 PasswordEncoder 的具體演算法對象存入 Map 中。那麼如何使用 DelegatingPasswordEncoder 呢?
以上就是 DelegatingPasswordEncoder 所存儲的密碼例子,其具體格式如下
其中 {id} 就是所使用的加密演算法, encodedPassword 就是 {id} 所對應的具體加密演算法加密後的值。
以 {bcrypt} 為例, DelegatingPasswordEncoder 會首先解析出 {bcrypt} ,然後在 Map 中查找具體的實現演算法,最終由 BCryptPasswordEncoder 來完成加密或匹配過程。