javascript
Spring框架中的设计模式(四)
適配器
當(dāng)我們需要在給定場(chǎng)景下(也就是給定接口)想要不改變自身行為而又想做到一些事情的情況下(就是我給電也就是接口了,你來做事也就是各種電器),使用適配器設(shè)計(jì)模式(這里再說一點(diǎn),就相當(dāng)于我們?cè)僖粋€(gè)規(guī)章制度的環(huán)境下,如何去適應(yīng)并達(dá)到我們期待的效果,放在架構(gòu)設(shè)計(jì)這里,可以拿一個(gè)php系統(tǒng)和一個(gè)Java系統(tǒng)來說,假如兩者要互相調(diào)用對(duì)方的功能,我們可以設(shè)計(jì)一套對(duì)外的api來適配)。這意味著在調(diào)用此對(duì)象之前,我們將更改使用對(duì)象而不改變機(jī)制。拿一個(gè)現(xiàn)實(shí)中的例子進(jìn)行說明,想象一下你想要用電鉆來鉆一個(gè)洞。要鉆一個(gè)小洞,你會(huì)使用小鉆頭,鉆一個(gè)大的需要用大鉆頭。可以看下面的代碼:
| 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); } } |
以上代碼的結(jié)果如下:
| 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對(duì)象制成的。如果孔的直徑小于10,則使用SmallDrillBit。如果它更大,我們使用BigDrillBit。
思路就是,要打洞,那就要有打洞的工具,這里提供一個(gè)電鉆接口和鉆頭。電鉆就是用來打洞的,所以,它就一個(gè)接口方法即可,接下來定義鉆頭的接口,無非就是鉆頭的尺寸標(biāo)準(zhǔn),然后搞出兩個(gè)鉆頭實(shí)現(xiàn)類出來,接下來就是把鉆頭和電鉆主機(jī)組裝起來咯,也就是Drill類,里面有電鉆接口+鉆頭(根據(jù)要鉆的孔大小來確定用哪個(gè)鉆頭),其實(shí)也就是把幾個(gè)單一的東西組合起來擁有豐富的功能,最后我們進(jìn)行封裝下:HoleMakerImpl,這樣只需要根據(jù)尺寸就可以打相應(yīng)的孔了,對(duì)外暴露的接口極為簡(jiǎn)單,無須管內(nèi)部邏輯是多么復(fù)雜
Spring使用適配器設(shè)計(jì)模式來處理不同servlet容器中的加載時(shí)編織(load-time-weaving)。在面向切面編程(AOP)中使用load-time-weaving,一種方式是在類加載期間將AspectJ的方面注入字節(jié)碼。另一種方式是對(duì)類進(jìn)行編譯時(shí)注入或?qū)σ丫幾g的類進(jìn)行靜態(tài)注入。
我們可以從關(guān)于Spring和JBoss的處理接口這里找到一個(gè)很好的例子,它包含在org.springframework.instrument.classloading.jboss包中。我們檢索JBossLoadTimeWeaver類負(fù)責(zé)JBoss容器的編織管理。然而,類加載器對(duì)于JBoss 6(使用JBossMCAdapter實(shí)例)和JBoss 7/8(使用JBossModulesAdapter實(shí)例)是不同的。根據(jù)JBoss版本,我們?cè)贘BossLoadTimeWeaver構(gòu)造函數(shù)中初始化相應(yīng)的適配器(與我們示例中的Drill的構(gòu)造函數(shù)完全相同):
| 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); } } |
而且,此適配器所創(chuàng)建的實(shí)例用于根據(jù)運(yùn)行的servlet容器版本進(jìn)行編織操作:
| @Override public void addTransformer(ClassFileTransformer transformer) { this.adapter.addTransformer(transformer); } @Override public ClassLoader getInstrumentableClassLoader() { return this.adapter.getInstrumentableClassLoader(); } |
總結(jié):適配器模式,其實(shí)就是我們用第一人稱的視角去看世界,我想拓展我自己的技能的時(shí)候,就實(shí)行拿來主義,就好比這里的我是電鉆的視角,那么我想擁有鉆大孔或者小孔的功能,那就把鉆頭拿到手組合起來就好。
和裝飾模式的區(qū)別:裝飾模式屬于第三人稱的視角,也就是上帝視角!我只需要把幾個(gè)功能性的組件給拿到手,進(jìn)行組合一下,實(shí)現(xiàn)一個(gè)更加niubility的功能這里提前說下,這樣看下面的內(nèi)容能好理解些。下面解釋裝飾模式
裝飾
這里描述的第二種設(shè)計(jì)模式看起來類似于適配器。它是裝飾模式。這種設(shè)計(jì)模式的主要作用是為給定的對(duì)象添加補(bǔ)充角色。舉個(gè)現(xiàn)實(shí)的例子,就拿咖啡來講。通常越黑越苦,你可以添加(裝飾)糖和牛奶,使咖啡不那么苦??Х仍谶@里被裝飾的對(duì)象,糖與牛奶是用來裝飾的??梢詤⒖枷旅娴睦?#xff1a;
| 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; } } |
上面這個(gè)簡(jiǎn)單的裝飾器的小例子是基于對(duì)父方法的調(diào)用,從而改變最后的屬性(我們這里是指價(jià)格和加糖多少)。在Spring中,我們?cè)谔幚砼cSpring管理緩存同步事務(wù)的相關(guān)類中可以 發(fā)現(xiàn)裝飾器設(shè)計(jì)模式的例子。這個(gè)類是org.springframework.cache.transaction.TransactionAwareCacheDecorator。
這個(gè)類的哪些特性證明它是org.springframework.cache.Cache對(duì)象的裝飾器?首先,與我們的咖啡示例一樣,TransactionAwareCacheDecorator的構(gòu)造函數(shù)接收參數(shù)裝飾對(duì)象(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; } |
其次,通過這個(gè)對(duì)象,我們可以得到一個(gè)新的行為:為給定的目標(biāo)緩存創(chuàng)建一個(gè)新的TransactionAwareCache。這個(gè)我們可以在TransactionAwareCacheDecorator的注釋中可以閱讀到,其主要目的是提供緩存和Spring事務(wù)之間的同步級(jí)別。這是通過org.springframework.transaction.support.TransactionSynchronizationManager中的兩種緩存方法實(shí)現(xiàn)的:put?和?evict(其實(shí)最終不還是通過targetCache來實(shí)現(xiàn)的么):
| @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); } } |
這種模式看起來類似于適配器,對(duì)吧?但是,它們還是有區(qū)別的。我們可以看到,適配器將對(duì)象適配到運(yùn)行時(shí)環(huán)境,即。如果我們?cè)贘Boss 6中運(yùn)行,我們使用與JBoss 7不同的類加載器。Decorator每次使用相同的主對(duì)象(Cache)工作,并且僅向其添加新行為(與本例中的Spring事務(wù)同步),另外,可以通過我在解讀這個(gè)設(shè)計(jì)模式之前的說法來區(qū)分二者。
我們?cè)僖詓pringboot的初始化來舉個(gè)例子的,這塊后面會(huì)進(jìn)行仔細(xì)的源碼分析的,這里就僅僅用設(shè)計(jì)模式來說下的:
| /** * 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的時(shí)候Event published走起,接著就是Environment配置好,ApplicationContext進(jìn)行初始化完畢,那我們?nèi)タ碅pplicationListener的源碼:
| /** * 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); } |
看類注釋我們可以知道,需要實(shí)現(xiàn)此接口內(nèi)所定義的這幾個(gè)方法,ok,來看個(gè)實(shí)現(xiàn)類:
| /** * {@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里對(duì)接口功能的實(shí)現(xiàn),主要是通過SpringApplication?ApplicationEventMulticaster?來實(shí)現(xiàn)的,自己不干活,掛個(gè)虛名,從上帝模式的角度來看,這不就是應(yīng)用了裝飾模式來實(shí)現(xiàn)的么。
更多源碼解析請(qǐng)關(guān)注后續(xù)的本人對(duì)Spring框架全面的重點(diǎn)部分解析系列博文
單例
單例,我們最常用的設(shè)計(jì)模式。正如我們?cè)诤芏郤pring Framework中關(guān)于單例和原型bean的文章(網(wǎng)上太多了)中已經(jīng)看到過的,單例是幾個(gè)bean作用域中的中的一個(gè)。此作用域在每個(gè)應(yīng)用程序上下文中僅創(chuàng)建一個(gè)給定bean的實(shí)例。與signleton設(shè)計(jì)模式有所區(qū)別的是,Spring將實(shí)例的數(shù)量限制的作用域在整個(gè)應(yīng)用程序的上下文。而Singleton設(shè)計(jì)模式在Java應(yīng)用程序中是將這些實(shí)例的數(shù)量限制在給定類加載器管理的整個(gè)空間中。這意味著我們可以為兩個(gè)Spring的上下文(同一份配置文件起兩個(gè)容器,也就是不同端口的容器實(shí)例)使用相同的類加載器,并檢索兩個(gè)單例作用域的bean。
在看Spring單例應(yīng)用之前,讓我們來看一個(gè)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 { } |
這個(gè)測(cè)試?yán)幼C明,只有一個(gè)由SingletonsHolder所持有的President實(shí)例。在Spring中,我們可以在bean工廠中找到單例應(yīng)用的影子(例如在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(); } } |
我們看到,當(dāng)需求對(duì)象被視為單例時(shí),它只被初始化一次,并且在每次使用同一個(gè)bean類的實(shí)例后返回。我們可以在給定的例子中看到,類似于我們以前看到的President情況。將測(cè)試bean定義為:
| <bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" /> |
測(cè)試用例如下所示:
| 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); } } |
這個(gè)測(cè)試案例顯示了Spring單例模式與純粹的單例設(shè)計(jì)模式的主要區(qū)別。盡管使用相同的類加載器來加載兩個(gè)應(yīng)用程序上下文,但是ShoppingCart的實(shí)例是不一樣的。但是,當(dāng)我們比較兩次創(chuàng)建并屬于相同上下文的實(shí)例時(shí),我們認(rèn)為它們是相等的。
也正因?yàn)橛辛藛卫?#xff0c;Spring可以控制在每個(gè)應(yīng)用程序上下文中只有一個(gè)這樣指定的bean的實(shí)例可用。因?yàn)檫m配器,Spring可以決定使用由誰來處理JBoss servlet容器中的加載時(shí)編織,也可以實(shí)現(xiàn)ConfigurableListableBeanFactory的相應(yīng)實(shí)例。第三種設(shè)計(jì)模式,裝飾器,用于向Cache對(duì)象添加同步功能,還有Springboot的容器初始化。
其實(shí)對(duì)于適配器和裝飾者確實(shí)有太多的相似的地方,一個(gè)是運(yùn)行時(shí)選擇,一個(gè)是加料組合產(chǎn)生新的化學(xué)效應(yīng),還有從看待事物的角度不同得到不同的行為,適配適配,更注重面向接口的實(shí)現(xiàn),而內(nèi)部又根據(jù)不同情況調(diào)用面向一套接口的多套實(shí)現(xiàn)的實(shí)例的相應(yīng)方法來實(shí)現(xiàn)所要實(shí)現(xiàn)的具體功能,裝飾者更注重添油加醋,通過組合一些其他對(duì)象實(shí)例來讓自己的功能實(shí)現(xiàn)的更加華麗一些(達(dá)到1+1>2的這種效果)。一家之言,有更好的理解可以聯(lián)系我。
總結(jié)
以上是生活随笔為你收集整理的Spring框架中的设计模式(四)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring框架中的设计模式(三)
- 下一篇: Spring框架中的设计模式(五)