新闻资讯

新闻资讯 行业动态

Spring框架中的设计模式(四)​——适配器 装饰 单例

编辑:008     时间:2020-02-27

适配器

当我们需要在给定场景下(也就是给定接口)想要不改变自身行为而又想做到一些事情的情况下(就是我给电也就是接口了,你来做事也就是各种电器),使用适配器设计模式(这里再说一点,就相当于我们再一个规章制度的环境下,如何去适应并达到我们期待的效果,放在架构设计这里,可以拿一个php系统和一个Java系统来说,假如两者要互相调用对方的功能,我们可以设计一套对外的api来适配)。这意味着在调用此对象之前,我们将更改使用对象而不改变机制。拿一个现实中的例子进行说明,想象一下你想要用电钻来钻一个洞。要钻一个小洞,你会使用小钻头,钻一个大的需要用大钻头。可以看下面的代码:

  1. publicclassAdapterTest{


  2.  publicstaticvoidmain(String[]args){

  3.    HoleMakermaker=newHoleMakerImpl();

  4.    maker.makeHole(1);

  5.    maker.makeHole(2);

  6.    maker.makeHole(30);

  7.    maker.makeHole(40);

  8.  }

  9. }


  10. interfaceHoleMaker{

  11.  publicvoidmakeHole(intdiameter);

  12. }


  13. interfaceDrillBit{

  14.  publicvoidmakeSmallHole();

  15.  publicvoidmakeBigHole();

  16. }


  17. // Two adaptee objects

  18. classBigDrillBitimplementsDrillBit{


  19.  @Override

  20.  publicvoidmakeSmallHole(){

  21.    // do nothing

  22.  }


  23.  @Override

  24.  publicvoidmakeBigHole(){

  25.    System.out.println("Big hole is made byt WallBigHoleMaker");

  26.  }

  27. }


  28. classSmallDrillBitimplementsDrillBit{


  29.  @Override

  30.  publicvoidmakeSmallHole(){

  31.    System.out.println("Small hole is made byt WallSmallHoleMaker");

  32.  }


  33.  @Override

  34.  publicvoidmakeBigHole(){

  35.    // do nothing

  36.  }

  37. }


  38. // Adapter class

  39. classDrillimplementsHoleMaker{


  40.  privateDrillBitdrillBit;


  41.  publicDrill(intdiameter){

  42.    drillBit=getMakerByDiameter(diameter);

  43.  }


  44.  @Override

  45.  publicvoidmakeHole(intdiameter){

  46.    if(isSmallDiameter(diameter)){

  47.            drillBit.makeSmallHole();

  48.    }else{

  49.            drillBit.makeBigHole();

  50.    }

  51.  }


  52.  privateDrillBitgetMakerByDiameter(intdiameter){

  53.    if(isSmallDiameter(diameter)){

  54.            returnnewSmallDrillBit();

  55.    }

  56.    returnnewBigDrillBit();

  57.  }


  58.  privatebooleanisSmallDiameter(intdiameter){

  59.    returndiameter<10;

  60.  }

  61. }


  62. // Client class

  63. classHoleMakerImplimplementsHoleMaker{


  64.  @Override

  65.  publicvoidmakeHole(intdiameter){

  66.    HoleMakermaker=newDrill(diameter);

  67.    maker.makeHole(diameter);

  68.  }

  69. }

以上代码的结果如下:

  1. Smallholeismade bytSmallDrillBit

  2. Smallholeismade bytSmallDrillBit

  3. Bigholeismade bytBigDrillBit

  4. Bigholeismade bytBigDrillBit

可以看到,hole 是由所匹配的DrillBit对象制成的。如果孔的直径小于10,则使用SmallDrillBit。如果它更大,我们使用BigDrillBit。

思路就是,要打洞,那就要有打洞的工具,这里提供一个电钻接口和钻头。电钻就是用来打洞的,所以,它就一个接口方法即可,接下来定义钻头的接口,无非就是钻头的尺寸标准,然后搞出两个钻头实现类出来,接下来就是把钻头和电钻主机组装起来咯,也就是Drill类,里面有电钻接口+钻头(根据要钻的孔大小来确定用哪个钻头),其实也就是把几个单一的东西组合起来拥有丰富的功能,最后我们进行封装下:HoleMakerImpl,这样只需要根据尺寸就可以打相应的孔了,对外暴露的接口极为简单,无须管内部逻辑是多么复杂

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的构造函数完全相同):

  1. publicJBossLoadTimeWeaver(ClassLoaderclassLoader){

  2.  privatefinalJBossClassLoaderAdapteradapter;


  3.  Assert.notNull(classLoader,"ClassLoader must not be null");

  4.  if(classLoader.getClass().getName().startsWith("org.jboss.modules")){

  5.    // JBoss AS 7 or WildFly 8

  6.    this.adapter=newJBossModulesAdapter(classLoader);

  7.  }

  8.  else{

  9.    // JBoss AS 6

  10.    this.adapter=newJBossMCAdapter(classLoader);

  11.  }

  12. }

而且,此适配器所创建的实例用于根据运行的servlet容器版本进行编织操作:

  1. @Override

  2. publicvoidaddTransformer(ClassFileTransformertransformer){

  3.  this.adapter.addTransformer(transformer);

  4. }


  5. @Override

  6. publicClassLoadergetInstrumentableClassLoader(){

  7.  returnthis.adapter.getInstrumentableClassLoader();

  8. }

总结:适配器模式,其实就是我们用第一人称的视角去看世界,我想拓展我自己的技能的时候,就实行拿来主义,就好比这里的我是电钻的视角,那么我想拥有钻大孔或者小孔的功能,那就把钻头拿到手组合起来就好。

和装饰模式的区别:装饰模式属于第三人称的视角,也就是上帝视角!我只需要把几个功能性的组件给拿到手,进行组合一下,实现一个更加niubility的功能这里提前说下,这样看下面的内容能好理解些。下面解释装饰模式

装饰

这里描述的第二种设计模式看起来类似于适配器。它是装饰模式。这种设计模式的主要作用是为给定的对象添加补充角色。举个现实的例子,就拿咖啡来讲。通常越黑越苦,你可以添加(装饰)糖和牛奶,使咖啡不那么苦。咖啡在这里被装饰的对象,糖与牛奶是用来装饰的。可以参考下面的例子:

  1. publicclassDecoratorSample{


  2.  @Test

  3.  publicvoidtest(){

  4.    CoffeesugarMilkCoffee=newMilkDecorator(newSugarDecorator(newBlackCoffee()));

  5.    assertEquals(sugarMilkCoffee.getPrice(),6d,0d);

  6.  }

  7. }


  8. // decorated

  9. abstractclassCoffee{

  10.  protectedintcandied=0;

  11.  protecteddoubleprice=2d;

  12.  publicabstractintmakeMoreCandied();

  13.  publicdoublegetPrice(){

  14.    returnthis.price;

  15.  }

  16.  publicvoidsetPrice(doubleprice){

  17.    this.price+=price;

  18.  }

  19. }

  20. classBlackCoffeeextendsCoffee{

  21.  @Override

  22.  publicintmakeMoreCandied(){

  23.    return0;

  24.  }

  25.  @Override

  26.  publicdoublegetPrice(){

  27.    returnthis.price;

  28.  }

  29. }


  30. // abstract decorator

  31. abstractclassCoffeeDecoratorextendsCoffee{

  32.  protectedCoffeecoffee;

  33.  publicCoffeeDecorator(Coffeecoffee){

  34.    this.coffee=coffee;

  35.  }

  36.  @Override

  37.  publicdoublegetPrice(){

  38.    returnthis.coffee.getPrice();

  39.  }

  40.  @Override

  41.  publicintmakeMoreCandied(){

  42.    returnthis.coffee.makeMoreCandied();

  43.  }

  44. }


  45. // concrete decorators

  46. classMilkDecoratorextendsCoffeeDecorator{

  47.  publicMilkDecorator(Coffeecoffee){

  48.    super(coffee);

  49.  }

  50.  @Override

  51.  publicdoublegetPrice(){

  52.    returnsuper.getPrice()+1d;

  53.  }

  54.  @Override

  55.  publicintmakeMoreCandied(){

  56.    returnsuper.makeMoreCandied()+1;

  57.  }

  58. }

  59. classSugarDecoratorextendsCoffeeDecorator{

  60.  publicSugarDecorator(Coffeecoffee){

  61.    super(coffee);

  62.  }

  63.  @Override

  64.  publicdoublegetPrice(){

  65.    returnsuper.getPrice()+3d;

  66.  }

  67.  @Override

  68.  publicintmakeMoreCandied(){

  69.    returnsuper.makeMoreCandied()+1;

  70.  }

  71. }

上面这个简单的装饰器的小例子是基于对父方法的调用,从而改变最后的属性(我们这里是指价格和加糖多少)。在Spring中,我们在处理与Spring管理缓存同步事务的相关类中可以 发现装饰器设计模式的例子。这个类是org.springframework.cache.transaction.TransactionAwareCacheDecorator。

这个类的哪些特性证明它是org.springframework.cache.Cache对象的装饰器?首先,与我们的咖啡示例一样,TransactionAwareCacheDecorator的构造函数接收参数装饰对象(Cache):

  1. privatefinalCachetargetCache;

  2. /**

  3. * Create a new TransactionAwareCache for the given target Cache.

  4. * @param targetCache the target Cache to decorate

  5. */

  6. publicTransactionAwareCacheDecorator(CachetargetCache){

  7.  Assert.notNull(targetCache,"Target Cache must not be null");

  8.  this.targetCache=targetCache;

  9. }

其次,通过这个对象,我们可以得到一个新的行为:为给定的目标缓存创建一个新的TransactionAwareCache。这个我们可以在TransactionAwareCacheDecorator的注释中可以阅读到,其主要目的是提供缓存和Spring事务之间的同步级别。这是通过org.springframework.transaction.support.TransactionSynchronizationManager中的两种缓存方法实现的:put和evict(其实最终不还是通过targetCache来实现的么):

  1. @Override

  2. publicvoidput(finalObjectkey,finalObjectvalue){

  3.  if(TransactionSynchronizationManager.isSynchronizationActive()){

  4.    TransactionSynchronizationManager.registerSynchronization(

  5.      newTransactionSynchronizationAdapter(){

  6.        @Override

  7.        publicvoidafterCommit(){

  8.          targetCache.put(key,value);

  9.        }

  10.    });

  11.  }

  12.  else{

  13.    this.targetCache.put(key,value);

  14.  }

  15. }


  16. @Override

  17. publicvoidevict(finalObjectkey){

  18.  if(TransactionSynchronizationManager.isSynchronizationActive()){

  19.          TransactionSynchronizationManager.registerSynchronization(

  20.            newTransactionSynchronizationAdapter(){

  21.              @Override

  22.              publicvoidafterCommit(){

  23.                targetCache.evict(key);

  24.              }

  25.          });

  26.  }

  27.  else{

  28.    this.targetCache.evict(key);

  29.  }

  30. }

这种模式看起来类似于适配器,对吧?但是,它们还是有区别的。我们可以看到,适配器将对象适配到运行时环境,即。如果我们在JBoss 6中运行,我们使用与JBoss 7不同的类加载器。Decorator每次使用相同的主对象(Cache)工作,并且仅向其添加新行为(与本例中的Spring事务同步),另外,可以通过我在解读这个设计模式之前的说法来区分二者。

我们再以springboot的初始化来举个例子的,这块后面会进行仔细的源码分析的,这里就仅仅用设计模式来说下的:

  1. /**

  2. * Event published as early as conceivably possible as soon as a {@link SpringApplication}

  3. * has been started - before the {@link Environment} or {@link ApplicationContext} is

  4. * available, but after the {@link ApplicationListener}s have been registered. The source

  5. * of the event is the {@link SpringApplication} itself, but beware of using its internal

  6. * state too much at this early stage since it might be modified later in the lifecycle.

  7. *

  8. * @author Dave Syer

  9. */

  10. @SuppressWarnings("serial")

  11. publicclassApplicationStartedEventextendsSpringApplicationEvent{


  12.    /**

  13.     * Create a new {@link ApplicationStartedEvent} instance.

  14.     * @param application the current application

  15.     * @param args the arguments the application is running with

  16.     */

  17.    publicApplicationStartedEvent(SpringApplicationapplication,String[]args){

  18.        super(application,args);

  19.    }


  20. }

从注释可以看出ApplicationListener要先行到位的,然后就是started的时候Eventpublished走起,接着就是Environment配置好,ApplicationContext进行初始化完毕,那我们去看ApplicationListener的源码:

  1. /**

  2. * Listener for the {@link SpringApplication} {@code run} method.

  3. * {@link SpringApplicationRunListener}s are loaded via the {@link SpringFactoriesLoader}

  4. * and should declare a public constructor that accepts a {@link SpringApplication}

  5. * instance and a {@code String[]} of arguments. A new

  6. * {@link SpringApplicationRunListener} instance will be created for each run.

  7. *

  8. * @author Phillip Webb

  9. * @author Dave Syer

  10. */

  11. publicinterfaceSpringApplicationRunListener{


  12.    /**

  13.     * Called immediately when the run method has first started. Can be used for very

  14.     * early initialization.

  15.     */

  16.    voidstarted();


  17.    /**

  18.     * Called once the environment has been prepared, but before the

  19.     * {@link ApplicationContext} has been created.

  20.     * @param environment the environment

  21.     */

  22.    voidenvironmentPrepared(ConfigurableEnvironmentenvironment);


  23.    /**

  24.     * Called once the {@link ApplicationContext} has been created and prepared, but

  25.     * before sources have been loaded.

  26.     * @param context the application context

  27.     */

  28.    voidcontextPrepared(ConfigurableApplicationContextcontext);


  29.    /**

  30.     * Called once the application context has been loaded but before it has been

  31.     * refreshed.

  32.     * @param context the application context

  33.     */

  34.    voidcontextLoaded(ConfigurableApplicationContextcontext);


  35.    /**

  36.     * Called immediately before the run method finishes.

  37.     * @param context the application context or null if a failure occurred before the

  38.     * context was created

  39.     * @param exception any run exception or null if run completed successfully.

  40.     */

  41.    voidfinished(ConfigurableApplicationContextcontext,Throwableexception);


  42. }

看类注释我们可以知道,需要实现此接口内所定义的这几个方法,ok,来看个实现类:

  1. /**

  2. * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.

  3. * <p>

  4. * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired

  5. * before the context is actually refreshed.

  6. *

  7. * @author Phillip Webb

  8. * @author Stephane Nicoll

  9. */

  10. publicclassEventPublishingRunListenerimplementsSpringApplicationRunListener,Ordered{


  11.    privatefinalSpringApplicationapplication;


  12.    privatefinalString[]args;


  13.    privatefinalApplicationEventMulticasterinitialMulticaster;


  14.    publicEventPublishingRunListener(SpringApplicationapplication,String[]args){

  15.        this.application=application;

  16.        this.args=args;

  17.        this.initialMulticaster=newSimpleApplicationEventMulticaster();

  18.        for(ApplicationListener<?>listener:application.getListeners()){

  19.            this.initialMulticaster.addApplicationListener(listener);

  20.        }

  21.    }


  22.    @Override

  23.    publicintgetOrder(){

  24.        return0;

  25.    }


  26.    @Override

  27.    publicvoidstarted(){

  28.        this.initialMulticaster

  29.                .multicastEvent(newApplicationStartedEvent(this.application,this.args));

  30.    }


  31.    @Override

  32.    publicvoidenvironmentPrepared(ConfigurableEnvironmentenvironment){

  33.        this.initialMulticaster.multicastEvent(newApplicationEnvironmentPreparedEvent(

  34.                this.application,this.args,environment));

  35.    }


  36.    @Override

  37.    publicvoidcontextPrepared(ConfigurableApplicationContextcontext){


  38.    }


  39.    @Override

  40.    publicvoidcontextLoaded(ConfigurableApplicationContextcontext){

  41.        for(ApplicationListener<?>listener:this.application.getListeners()){

  42.            if(listenerinstanceofApplicationContextAware){

  43.                ((ApplicationContextAware)listener).setApplicationContext(context);

  44.            }

  45.            context.addApplicationListener(listener);

  46.        }

  47.        this.initialMulticaster.multicastEvent(

  48.                newApplicationPreparedEvent(this.application,this.args,context));

  49.    }


  50.    @Override

  51.    publicvoidfinished(ConfigurableApplicationContextcontext,Throwableexception){

  52.        // Listeners have been registered to the application context so we should

  53.        // use it at this point

  54.        context.publishEvent(getFinishedEvent(context,exception));

  55.    }


  56.    privateSpringApplicationEventgetFinishedEvent(

  57.            ConfigurableApplicationContextcontext,Throwableexception){

  58.        if(exception!=null){

  59.            returnnewApplicationFailedEvent(this.application,this.args,context,

  60.                    exception);

  61.        }

  62.        returnnewApplicationReadyEvent(this.application,this.args,context);

  63.    }


  64. }

从上可以看出,EventPublishingRunListener里对接口功能的实现,主要是通过SpringApplicationApplicationEventMulticaster来实现的,自己不干活,挂个虚名,从上帝模式的角度来看,这不就是应用了装饰模式来实现的么。

更多源码解析请关注后续的本人对Spring框架全面的重点部分解析系列博文

单例

单例,我们最常用的设计模式。正如我们在很多Spring Framework中关于单例和原型bean的文章(网上太多了)中已经看到过的,单例是几个bean作用域中的中的一个。此作用域在每个应用程序上下文中仅创建一个给定bean的实例。与signleton设计模式有所区别的是,Spring将实例的数量限制的作用域在整个应用程序的上下文。而Singleton设计模式在Java应用程序中是将这些实例的数量限制在给定类加载器管理的整个空间中。这意味着我们可以为两个Spring的上下文(同一份配置文件起两个容器,也就是不同端口的容器实例)使用相同的类加载器,并检索两个单例作用域的bean。

在看Spring单例应用之前,让我们来看一个Java的单例例子:

  1. publicclassSingletonTest{


  2.  @Test

  3.  publicvoidtest(){

  4.    Presidentpresident1=(President)SingletonsHolder.PRESIDENT.getHoldedObject();

  5.    Presidentpresident2=(President)SingletonsHolder.PRESIDENT.getHoldedObject();

  6.    assertTrue("Both references of President should point to the same object",president1==president2);

  7.    System.out.println("president1 = "+president1+" and president2 = "+president2);

  8.    // sample output

  9.    // president1 = com.waitingforcode.test.President@17414c8 and president2 = com.waitingforcode.test.President@17414c8


  10.  }


  11. }


  12. enumSingletonsHolder{


  13.  PRESIDENT(newPresident());


  14.  privateObjectholdedObject;


  15.  privateSingletonsHolder(Objecto){

  16.          this.holdedObject=o;

  17.  }


  18.  publicObjectgetHoldedObject(){

  19.          returnthis.holdedObject;

  20.  }


  21. }


  22. classPresident{

  23. }

这个测试例子证明,只有一个由SingletonsHolder所持有的President实例。在Spring中,我们可以在bean工厂中找到单例应用的影子(例如在org.springframework.beans.factory.config.AbstractFactoryBean中):

  1. /**

  2. * Expose the singleton instance or create a new prototype instance.

  3. * @see #createInstance()

  4. * @see #getEarlySingletonInterfaces()

  5. */

  6. @Override

  7. publicfinalT getObject()throwsException{

  8.  if(isSingleton()){

  9.    return(this.initialized?this.singletonInstance:getEarlySingletonInstance());

  10.  }

  11.  else{

  12.    returncreateInstance();

  13.  }

  14. }

我们看到,当需求对象被视为单例时,它只被初始化一次,并且在每次使用同一个bean类的实例后返回。我们可以在给定的例子中看到,类似于我们以前看到的President情况。将测试bean定义为:

  1. <beanid="shoppingCart"class="com.waitingforcode.data.ShoppingCart"/>

测试用例如下所示:

  1. publicclassSingletonSpringTest{


  2.  @Test

  3.  publicvoidtest(){

  4.    // retreive two different contexts

  5.    ApplicationContextfirstContext=newFileSystemXmlApplicationContext("applicationContext-test.xml");

  6.    ApplicationContextsecondContext=newFileSystemXmlApplicationContext("applicationContext-test.xml");


  7.    // prove that both contexts are loaded by the same class loader

  8.    assertTrue("Class loaders for both contexts should be the same",

  9.      firstContext.getClassLoader()==secondContext.getClassLoader());

  10.    // compare the objects from different contexts

  11.    ShoppingCartfirstShoppingCart=(ShoppingCart)firstContext.getBean("shoppingCart");

  12.    ShoppingCartsecondShoppingCart=(ShoppingCart)secondContext.getBean("shoppingCart");

  13.    assertFalse("ShoppingCart instances got from different application context shouldn't be the same",

  14.      firstShoppingCart==secondShoppingCart);


  15.    // compare the objects from the same context

  16.    ShoppingCartfirstShoppingCartBis=(ShoppingCart)firstContext.getBean("shoppingCart");

  17.    assertTrue("ShoppingCart instances got from the same application context should be the same",

  18.      firstShoppingCart==firstShoppingCartBis);

  19.  }

  20. }

这个测试案例显示了Spring单例模式与纯粹的单例设计模式的主要区别。尽管使用相同的类加载器来加载两个应用程序上下文,但是ShoppingCart的实例是不一样的。但是,当我们比较两次创建并属于相同上下文的实例时,我们认为它们是相等的。

也正因为有了单例,Spring可以控制在每个应用程序上下文中只有一个这样指定的bean的实例可用。因为适配器,Spring可以决定使用由谁来处理JBossservlet容器中的加载时编织,也可以实现ConfigurableListableBeanFactory的相应实例。第三种设计模式,装饰器,用于向Cache对象添加同步功能,还有Springboot的容器初始化。

其实对于适配器和装饰者确实有太多的相似的地方,一个是运行时选择,一个是加料组合产生新的化学效应,还有从看待事物的角度不同得到不同的行为,适配适配,更注重面向接口的实现,而内部又根据不同情况调用面向一套接口的多套实现的实例的相应方法来实现所要实现的具体功能,装饰者更注重添油加醋,通过组合一些其他对象实例来让自己的功能实现的更加华丽一些(达到1+1>2的这种效果)。一家之言,有更好的理解可以联系我。


原文链接:https://mp.weixin.qq.com/s/ZJK2U_3EBRmPPKfobjIoug

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

回复列表

相关推荐