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的容器初始化。
其实对于适配器和装饰者确实有太多的类似的地方,一个是运行时选择,一个是加料组合产生新的化学效应,还有从看待事物的角度不一样获得不一样的行为,适配适配,更注重面向接口的实现,而内部又根据不一样状况调用面向一套接口的多套实现的实例的相应方法来实现所要实现的具体功能,装饰者更注重添油加醋,经过组合一些其余对象实例来让本身的功能实现的更加华丽一些.