從原始碼的角度理解Java設計模式的裝飾模式
一、裝飾模式介紹
修飾符模式定義:不改變原始物件的附加函式比生成子類更靈活。
適用場景:動態的給一個物件新增或者撤銷功能。
優點:它能夠在不改變原有物件的情況下動態擴充套件函式,使擴充套件函式按照期望的順序執行,達到不同的效果。
缺點:更多的類,使程式複雜
型別:結構型。
類圖:
原始碼分析中的典型應用
- Java I/O 中的裝飾者模式
- Spring Session 中的裝飾者模式
- Mybatis 快取中的裝飾者模式
二、給系統新增日誌,安全、限流示例
可以抽取出通用系統的安全性、日誌、當前限制等獨立於業務的程式碼,在控制器轉換前後使用模板法模式可以部分解決上述問題。
public abstract class BaseAspect { Logger logger = LoggerFactory.getLogger(BaseCommand.class); public void execute(){ //記錄日誌 logger.debug("..start.."); //過濾跨站指令碼攻擊 paramXssAspect(); //限制速率 doRateLimit(); doBusiness(); logger.debug("..end.."); } public abstract void doBusiness(); } class PlaceOrderAspect extends BaseAspect { @Override public void doBusiness() { //下單操作 } } class PayOrderAspect extends BaseAspect { @Override public void doBusiness() { //支付操作 } }
在父類中,已經編寫了“雜亂”的非業務程式碼,只剩下一個抽象的方法和其他子類來實現,子類變得非常清新,只關注業務邏輯。
這種方法的最大缺點是父類定義了一切:為了執行那些非業務程式碼,按照什麼順序等等,子類只能被無條件地接受。如果有一個子類不限制速率,那麼它就無法擺脫它。
通過使用裝飾器模型,我們可以靈活地處理上述問題。
//最高層抽象元件 interface IAspect { String doHandlerAspect(); } //基本被裝飾類,做一些公共處理 class AspectImpl implements IAspect{ @Override public String doHandlerAspect() { return "裸跑程式碼."; } } abstract class AbstractDecorator implements IAspect{ //很重要,組合抽象構件到自己的類中 private IAspect aspect; public AbstractDecorator(IAspect aspect) {//通過IAspect構造自己 this.aspect = aspect; } @Override public String doHandlerAspect() { return this.aspect.doHandlerAspect(); } }
附加記錄日誌,安全,限流功能:
class LoggerAspect extends AbstractDecorator{
public LoggerAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+記錄日誌.";
}
}
class ParamXssAspect extends AbstractDecorator{
public ParamXssAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+過濾危險字元.";
}
}
class LimitAspect extends AbstractDecorator{
public LimitAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+限流.";
}
}
測試一下:
public class Test {
public static void main(String[] args) {
IAspect aspect = new LimitAspect(new ParamXssAspect(new LoggerAspect(new AspectImpl())));
System.out.println(aspect.doHandlerAspect());
}
}
執行結果:
------
裸跑程式碼.+記錄日誌.+過濾危險字元.+限流.
------
從上面可以看出,裝飾器模式可以按任意順序組裝函式,是否非常靈活?此外,上述三個函式還可以封裝到註釋@Log、@ParamXss、@AccessLimit中以實現可插拔性。
三、原始碼中的裝飾者模式
3.1、Java IO中是體現最明顯的裝飾者模式。
它是.stream(InputStream/OutputStream),bytestream(Reader/Writer)是類,InputStream中的輸入,ReaderClass:
這裡總結幾種常用流的應用場景:
3.2、Spring Session中的ServletRequestWrapper(Response也一樣)的裝飾者模式。
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;//組合抽象介面到自己的類中
public ServletRequestWrapper(ServletRequest request) {//可以構造自己
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
public ServletRequest getRequest() {
return this.request;
}
public void setRequest(ServletRequest request) {
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
//省略...
}
3.3、Spring Cache中的TransactionAwareCacheDecorator的裝飾者模式。
其實從類名就可以看出。
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;//把Cache組合到自己類中
public TransactionAwareCacheDecorator(Cache targetCache) {//通過Cache構造自己
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public <T> T get(Object key, Class<T> type) {
return this.targetCache.get(key, type);
}
public void put(final Object key, final Object value) {
// 判斷是否開啟了事務
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 將操作註冊到 afterCommit 階段
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
} else {
this.targetCache.put(key, value);
}
}
// ...省略...
}
3.4、Mybatis中的裝飾者。
Cache是一個抽象的元件類,PerpetualCache是一個具體的元件類,decorators包下的類是裝飾類。沒有抽象裝飾類。