Spring框架中的設計模式(四) php
本文是Spring框架中使用的設計模式第四篇。本文將在此呈現出新的3種模式。一開始,咱們會討論2種結構模式:適配器和裝飾器。在第三部分和最後一部分,咱們將討論單例模式。java
前傳:spring
當咱們須要在給定場景下(也就是給定接口)想要不改變自身行爲而又想作到一些事情的狀況下(就是我給電也就是接口了,你來作事也就是各類電器),使用適配器設計模式(這裏再說一點,就至關於咱們再一個規章制度的環境下,如何去適應並達到咱們期待的效果,放在架構設計這裏,能夠拿一個php系統和一個Java系統來講,假如二者要互相調用對方的功能,咱們能夠設計一套對外的api來適配)。這意味着在調用此對象以前,咱們將更改使用對象而不改變機制。拿一個現實中的例子進行說明,想象一下你想要用電鑽來鑽一個洞。要鑽一個小洞,你會使用小鑽頭,鑽一個大的須要用大鑽頭。能夠看下面的代碼:緩存
public class AdapterTest { public static void main(String[] args) { HoleMaker maker = new HoleMakerImpl(); maker.makeHole(1); maker.makeHole(2); maker.makeHole(30); maker.makeHole(40); } } interface HoleMaker { public void makeHole(int diameter); } interface DrillBit { public void makeSmallHole(); public void makeBigHole(); } // Two adaptee objects class BigDrillBit implements DrillBit { @Override public void makeSmallHole() { // do nothing } @Override public void makeBigHole() { System.out.println("Big hole is made byt WallBigHoleMaker"); } } class SmallDrillBit implements DrillBit { @Override public void makeSmallHole() { System.out.println("Small hole is made byt WallSmallHoleMaker"); } @Override public void makeBigHole() { // do nothing } } // Adapter class class Drill implements HoleMaker { private DrillBit drillBit; public Drill(int diameter) { drillBit = getMakerByDiameter(diameter); } @Override public void makeHole(int diameter) { if (isSmallDiameter(diameter)) { drillBit.makeSmallHole(); } else { drillBit.makeBigHole(); } } private DrillBit getMakerByDiameter(int diameter) { if (isSmallDiameter(diameter)) { return new SmallDrillBit(); } return new BigDrillBit(); } private boolean isSmallDiameter(int diameter) { return diameter < 10; } } // Client class class HoleMakerImpl implements HoleMaker { @Override public void makeHole(int diameter) { HoleMaker maker = new Drill(diameter); maker.makeHole(diameter); } }
以上代碼的結果以下:springboot
Small hole is made byt SmallDrillBit Small hole is made byt SmallDrillBit Big hole is made byt BigDrillBit Big hole is made byt BigDrillBit
能夠看到,hole 是由所匹配的DrillBit對象製成的。若是孔的直徑小於10,則使用SmallDrillBit。若是它更大,咱們使用BigDrillBit。架構
思路就是,要打洞,那就要有打洞的工具,這裏提供一個電鑽接口和鑽頭。電鑽就是用來打洞的,因此,它就一個接口方法便可,接下來定義鑽頭的接口,無非就是鑽頭的尺寸標準,而後搞出兩個鑽頭實現類出來,接下來就是把鑽頭和電鑽主機組裝起來咯,也就是 Drill
類,裏面有電鑽接口+鑽頭(根據要鑽的孔大小來肯定用哪一個鑽頭),其實也就是把幾個單一的東西組合起來擁有豐富的功能,最後咱們進行封裝下: HoleMakerImpl
,這樣只須要根據尺寸就能夠打相應的孔了,對外暴露的接口極爲簡單,無須管內部邏輯是多麼複雜app
Spring使用適配器設計模式來處理不一樣servlet容器中的加載時編織(load-time-weaving)。在面向切面編程(AOP)中使用load-time-weaving,一種方式是在類加載期間將AspectJ的方面注入字節碼。另外一種方式是對類進行編譯時注入或對已編譯的類進行靜態注入。
咱們能夠從關於Spring和JBoss的處理接口這裏找到一個很好的例子,它包含在org.springframework.instrument.classloading.jboss包中。咱們檢索 JBossLoadTimeWeaver類
負責 JBoss容器
的編織管理。然而,類加載器對於 JBoss6
(使用 JBossMCAdapter
實例)和 JBoss7/8
(使用 JBossModulesAdapter
實例)是不一樣的。根據 JBoss
版本,咱們在 JBossLoadTimeWeaver
構造函數中初始化相應的適配器(與咱們示例中的 Drill
的構造函數徹底相同):
public JBossLoadTimeWeaver(ClassLoader classLoader) { private final JBossClassLoaderAdapter adapter; Assert.notNull(classLoader, "ClassLoader must not be null"); if (classLoader.getClass().getName().startsWith("org.jboss.modules")) { // JBoss AS 7 or WildFly 8 this.adapter = new JBossModulesAdapter(classLoader); } else { // JBoss AS 6 this.adapter = new JBossMCAdapter(classLoader); } }
並且,此適配器所建立的實例用於根據運行的servlet容器版本進行編織操做:
@Override public void addTransformer(ClassFileTransformer transformer) { this.adapter.addTransformer(transformer); } @Override public ClassLoader getInstrumentableClassLoader() { return this.adapter.getInstrumentableClassLoader(); }
總結:適配器模式,其實就是咱們用第一人稱的視角去看世界,我想拓展我本身的技能的時候,就實行拿來主義,就比如這裏的我是電鑽的視角,那麼我想擁有鑽大孔或者小孔的功能,那就把鑽頭拿到手組合起來就好。
和裝飾模式的區別:裝飾模式屬於第三人稱的視角,也就是上帝視角!我只須要把幾個功能性的組件給拿到手,進行組合一下,實現一個更加
niubility
的功能這裏提早說下,這樣看下面的內容能好理解些。下面解釋裝飾模式
這裏描述的第二種設計模式看起來相似於適配器。它是裝飾模式。這種設計模式的主要做用是爲給定的對象添加補充角色。舉個現實的例子,就拿咖啡來說。一般越黑越苦,你能夠添加( 裝飾
)糖和牛奶,使咖啡不那麼苦。咖啡在這裏被裝飾的對象,糖與牛奶是用來裝飾的。能夠參考下面的例子:
public class DecoratorSample { @Test public void test() { Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee())); assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d); } } // decorated abstract class Coffee{ protected int candied=0; protected double price=2d; public abstract int makeMoreCandied(); public double getPrice(){ return this.price; } public void setPrice(double price){ this.price+=price; } } class BlackCoffee extends Coffee{ @Override public int makeMoreCandied(){ return 0; } @Override public double getPrice(){ return this.price; } } // abstract decorator abstract class CoffeeDecorator extends Coffee{ protected Coffee coffee; public CoffeeDecorator(Coffee coffee){ this.coffee=coffee; } @Override public double getPrice(){ return this.coffee.getPrice(); } @Override public int makeMoreCandied(){ return this.coffee.makeMoreCandied(); } } // concrete decorators class MilkDecorator extends CoffeeDecorator{ public MilkDecorator(Coffee coffee){ super(coffee); } @Override public double getPrice(){ return super.getPrice()+1d; } @Override public int makeMoreCandied(){ return super.makeMoreCandied()+1; } } class SugarDecorator extends CoffeeDecorator{ public SugarDecorator(Coffee coffee){ super(coffee); } @Override public double getPrice(){ return super.getPrice()+3d; } @Override public int makeMoreCandied(){ return super.makeMoreCandied()+1; } }
上面這個簡單的裝飾器的小例子是基於對父方法的調用,從而改變最後的屬性(咱們這裏是指價格和加糖多少)。在Spring中,咱們在處理與Spring管理緩存同步事務的相關類中能夠 發現裝飾器設計模式的例子。這個類是org.springframework.cache.transaction.TransactionAwareCacheDecorator。
這個類的哪些特性證實它是org.springframework.cache.Cache對象的裝飾器?首先,與咱們的咖啡示例同樣, TransactionAwareCacheDecorator
的構造函數接收參數裝飾對象(Cache):
private final Cache targetCache; /** * Create a new TransactionAwareCache for the given target Cache. * @param targetCache the target Cache to decorate */ public TransactionAwareCacheDecorator(Cache targetCache) { Assert.notNull(targetCache, "Target Cache must not be null"); this.targetCache = targetCache; }
其次,經過這個對象,咱們能夠獲得一個新的行爲:爲給定的目標緩存建立一個新的TransactionAwareCache。這個咱們能夠在 TransactionAwareCacheDecorator
的註釋中能夠閱讀到,其主要目的是提供緩存和Spring事務之間的同步級別。這是經過org.springframework.transaction.support.TransactionSynchronizationManager中的兩種緩存方法實現的: put
和 evict
(其實最終不仍是經過 targetCache
來實現的麼):
@Override public void put(final Object key, final Object value) { if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { targetCache.put(key, value); } }); } else { this.targetCache.put(key, value); } } @Override public void evict(final Object key) { if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { targetCache.evict(key); } }); } else { this.targetCache.evict(key); } }
這種模式看起來相似於適配器,對吧?可是,它們仍是有區別的。咱們能夠看到,適配器將對象適配到運行時環境,即。若是咱們在JBoss 6中運行,咱們使用與JBoss 7不一樣的類加載器。Decorator每次使用相同的主對象(Cache)工做,而且僅向其添加新行爲(與本例中的Spring事務同步),另外,能夠經過我在解讀這個設計模式以前的說法來區分兩者。
咱們再以springboot的初始化來舉個例子的,這塊後面會進行仔細的源碼分析的,這裏就僅僅用設計模式來講下的:
/** * Event published as early as conceivably possible as soon as a {@link SpringApplication} * has been started - before the {@link Environment} or {@link ApplicationContext} is * available, but after the {@link ApplicationListener}s have been registered. The source * of the event is the {@link SpringApplication} itself, but beware of using its internal * state too much at this early stage since it might be modified later in the lifecycle. * * @author Dave Syer */ @SuppressWarnings("serial") public class ApplicationStartedEvent extends SpringApplicationEvent { /** * Create a new {@link ApplicationStartedEvent} instance. * @param application the current application * @param args the arguments the application is running with */ public ApplicationStartedEvent(SpringApplication application, String[] args) { super(application, args); } }
從註釋能夠看出 ApplicationListener
要先行到位的,而後就是started的時候 Eventpublished
走起,接着就是 Environment
配置好, ApplicationContext
進行初始化完畢,那咱們去看 ApplicationListener
的源碼:
/** * Listener for the {@link SpringApplication} {@code run} method. * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader} * and should declare a public constructor that accepts a {@link SpringApplication} * instance and a {@code String[]} of arguments. A new * {@link SpringApplicationRunListener} instance will be created for each run. * * @author Phillip Webb * @author Dave Syer */ public interface SpringApplicationRunListener { /** * Called immediately when the run method has first started. Can be used for very * early initialization. */ void started(); /** * Called once the environment has been prepared, but before the * {@link ApplicationContext} has been created. * @param environment the environment */ void environmentPrepared(ConfigurableEnvironment environment); /** * Called once the {@link ApplicationContext} has been created and prepared, but * before sources have been loaded. * @param context the application context */ void contextPrepared(ConfigurableApplicationContext context); /** * Called once the application context has been loaded but before it has been * refreshed. * @param context the application context */ void contextLoaded(ConfigurableApplicationContext context); /** * Called immediately before the run method finishes. * @param context the application context or null if a failure occurred before the * context was created * @param exception any run exception or null if run completed successfully. */ void finished(ConfigurableApplicationContext context, Throwable exception); }
看類註釋咱們能夠知道,須要實現此接口內所定義的這幾個方法,ok,來看個實現類:
/** * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s. * <p> * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired * before the context is actually refreshed. * * @author Phillip Webb * @author Stephane Nicoll */ public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; private final ApplicationEventMulticaster initialMulticaster; public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } @Override public int getOrder() { return 0; } @Override public void started() { this.initialMulticaster .multicastEvent(new ApplicationStartedEvent(this.application, this.args)); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent( this.application, this.args, environment)); } @Override public void contextPrepared(ConfigurableApplicationContext context) { } @Override public void contextLoaded(ConfigurableApplicationContext context) { for (ApplicationListener<?> listener : this.application.getListeners()) { if (listener instanceof ApplicationContextAware) { ((ApplicationContextAware) listener).setApplicationContext(context); } context.addApplicationListener(listener); } this.initialMulticaster.multicastEvent( new ApplicationPreparedEvent(this.application, this.args, context)); } @Override public void finished(ConfigurableApplicationContext context, Throwable exception) { // Listeners have been registered to the application context so we should // use it at this point context.publishEvent(getFinishedEvent(context, exception)); } private SpringApplicationEvent getFinishedEvent( ConfigurableApplicationContext context, Throwable exception) { if (exception != null) { return new ApplicationFailedEvent(this.application, this.args, context, exception); } return new ApplicationReadyEvent(this.application, this.args, context); } }
從上能夠看出, EventPublishingRunListener
裏對接口功能的實現,主要是經過 SpringApplication
ApplicationEventMulticaster
來實現的,本身不幹活,掛個虛名,從上帝模式的角度來看,這不就是應用了裝飾模式來實現的麼。
更多源碼解析請關注後續的本人對Spring框架全面的重點部分解析系列博文
單例,咱們最經常使用的設計模式。正如咱們在不少Spring Framework中關於單例和原型bean的文章(網上太多了)中已經看到過的,單例是幾個bean做用域中的中的一個。此做用域在每一個應用程序上下文中僅建立一個給定bean的實例。與signleton設計模式有所區別的是,Spring將實例的數量限制的做用域在整個應用程序的上下文。而Singleton設計模式在Java應用程序中是將這些實例的數量限制在給定類加載器管理的整個空間中。這意味着咱們能夠爲兩個Spring的上下文(同一份配置文件起兩個容器,也就是不一樣端口的容器實例)使用相同的類加載器,並檢索兩個單例做用域的bean。
在看Spring單例應用以前,讓咱們來看一個Java的單例例子:
public class SingletonTest { @Test public void test() { President president1 = (President) SingletonsHolder.PRESIDENT.getHoldedObject(); President president2 = (President) SingletonsHolder.PRESIDENT.getHoldedObject(); assertTrue("Both references of President should point to the same object", president1 == president2); System.out.println("president1 = "+president1+" and president2 = "+president2); // sample output // president1 = com.waitingforcode.test.President@17414c8 and president2 = com.waitingforcode.test.President@17414c8 } } enum SingletonsHolder { PRESIDENT(new President()); private Object holdedObject; private SingletonsHolder(Object o) { this.holdedObject = o; } public Object getHoldedObject() { return this.holdedObject; } } class President { }
這個測試例子證實,只有一個由SingletonsHolder所持有的President實例。在Spring中,咱們能夠在bean工廠中找到單例應用的影子(例如在org.springframework.beans.factory.config.AbstractFactoryBean中):
/** * Expose the singleton instance or create a new prototype instance. * @see #createInstance() * @see #getEarlySingletonInterfaces() */ @Override public final T getObject() throws Exception { if (isSingleton()) { return (this.initialized ? this.singletonInstance : getEarlySingletonInstance()); } else { return createInstance(); } }
咱們看到,當需求對象被視爲單例時,它只被初始化一次,而且在每次使用同一個bean類的實例後返回。咱們能夠在給定的例子中看到,相似於咱們之前看到的President狀況。將測試bean定義爲:
<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" />
測試用例以下所示:
public class SingletonSpringTest { @Test public void test() { // retreive two different contexts ApplicationContext firstContext = new FileSystemXmlApplicationContext("applicationContext-test.xml"); ApplicationContext secondContext = new FileSystemXmlApplicationContext("applicationContext-test.xml"); // prove that both contexts are loaded by the same class loader assertTrue("Class loaders for both contexts should be the same", firstContext.getClassLoader() == secondContext.getClassLoader()); // compare the objects from different contexts ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart"); ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart"); assertFalse("ShoppingCart instances got from different application context shouldn't be the same", firstShoppingCart == secondShoppingCart); // compare the objects from the same context ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart"); assertTrue("ShoppingCart instances got from the same application context should be the same", firstShoppingCart == firstShoppingCartBis); } }
這個測試案例顯示了Spring單例模式與純粹的單例設計模式的主要區別。儘管使用相同的類加載器來加載兩個應用程序上下文,可是ShoppingCart的實例是不同的。可是,當咱們比較兩次建立並屬於相同上下文的實例時,咱們認爲它們是相等的。
也正由於有了單例,Spring能夠控制在每一個應用程序上下文中只有一個這樣指定的bean的實例可用。由於適配器,Spring能夠決定使用由誰來處理 JBossservlet
容器中的加載時編織,也能夠實現 ConfigurableListableBeanFactory
的相應實例。第三種設計模式,裝飾器,用於向Cache對象添加同步功能,還有Springboot的容器初始化。
其實對於適配器和裝飾者確實有太多的類似的地方,一個是運行時選擇,一個是加料組合產生新的化學效應,還有從看待事物的角度不一樣獲得不一樣的行爲,適配適配,更注重面向接口的實現,而內部又根據不一樣狀況調用面向一套接口的多套實現的實例的相應方法來實現所要實現的具體功能,裝飾者更注重添油加醋,經過組合一些其餘對象實例來讓本身的功能實現的更加華麗一些.