Spring框架中的设计模式(二)

上一篇 中我们在Spring中所谈到的设计模式涉及到了创建模式三剑客和1个行为模式(解释器模式)。这次我们会将眼光更多地关注在具有结构性和行为性的设计模式上。 在这篇文章中,我们将看到每个类型的两种模式。首先将关注类型是的结构设计模式。它将包含代理和复合。下一个将介绍行为模式:策略和模板方法。

代理模式

面向对象编程(OOP)可能是编程中最流行的概念。然而,Spring引入了另一种编码规范,面向切面编程(AOP)。为了简化定义,AOP是面向系统特定点的一种编程,如:异常抛出,特定类别方法的执行等.AOP允许在执行这些特定点之前或之后执行补充动作。如何实现这种操作?它可以通过监听器(listeners)进行。但在这种情况下,我们应该在只要可能存在调用的地方都需要定义监听器来进行监听(比如在一个方法的开始的地方)。这就是为什么Spring不采用这个idea。相反,Spring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式

代理就像对象的镜像一样。也正因为如此,代理对象不仅可以覆盖真实对象,还可以扩展其功能。因此,对于只能在屏幕上打印一些文本的对象,我们可以添加另一个对象来过滤显示单词。可以通过代理来定义第二个对象的调用。代理是封装真实对象的对象。例如,如果您尝试调用Waiter bean,那么您将调用该Bean的代理,其行为方式完全相同。

代理设计模式的一个很好的例子是org.springframework.aop.framework.ProxyFactoryBean。该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的 FactoryBean接口。此方法用于将需求 Bean的实例返回给 bean factory。在这种情况下,它不是返回的实例,而是 AOP代理。在执行代理对象的方法之前,可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已,而不是装饰模式什么也不做就加了件衣服,其他还得由你来全权完成)。

ProxyFactory的一个例子是:

public class TestProxyAop {

  @Test
  public void test() {
    ProxyFactory factory = new ProxyFactory(new House());
    factory.addInterface(Construction.class);
    factory.addAdvice(new BeforeConstructAdvice());
    factory.setExposeProxy(true);

    Construction construction = (Construction) factory.getProxy();
    construction.construct();
    assertTrue("Construction is illegal. "
      + "Supervisor didn't give a permission to build "
      + "the house", construction.isPermitted());
  }

}

interface Construction {
  public void construct();
  public void givePermission();
  public boolean isPermitted();
}

class House implements Construction{

  private boolean permitted = false;

  @Override
  public boolean isPermitted() {
    return this.permitted;
  }

  @Override
  public void construct() {
    System.out.println("I'm constructing a house");
  }

  @Override
  public void givePermission() {
    System.out.println("Permission is given to construct a simple house");
    this.permitted = true;
  }
}

class BeforeConstructAdvice implements MethodBeforeAdvice {

  @Override
  public void before(Method method, Object[] arguments, Object target) throws Throwable {
    if (method.getName().equals("construct")) {
      ((Construction) target).givePermission();
    }
  }

}

这个测试应该通过,因为我们不直接在House实例上操作,而是代理它。代理调用第一个 BeforeConstructAdvicebefore方法(指向在执行目标方法之前执行,在我们的例子中为 construct())通过它,给出了一个“权限”来构造对象的字段(house)。代理层提供了一个额外新功能,因为它可以简单地分配给另一个对象。要做到这一点,我们只能在before方法之前修改过滤器。

复合模式

另一种结构模式是复合模式。在关于Spring中设计模式的第一篇文章中,我们使用构建器来构造复杂对象。另一种实现方法是使用复合模式。这种模式是基于具有共同行为的多个对象的存在,用于构建更大的对象。较大的对象仍然具有与最小对象相同的特征。那么用它来定义相同的行为。

复合对象的非Spring示例可以是一个写入HTML的文本对象,由包含span或em标签的段落组成:

public class CompositeTest {

  @Test
  public void test() {
    TextTagComposite composite = new PTag();
    composite.addTag(new SpanTag());
    composite.addTag(new EmTag());

    // sample client code
    composite.startWrite();
    for (TextTag leaf : composite.getTags()) {
      leaf.startWrite();
      leaf.endWrite();
    }
    composite.endWrite();
    assertTrue("Composite should contain 2 tags but it contains "+composite.getTags().size(), composite.getTags().size() == 2);
  }

}


interface TextTag {
  public void startWrite();
  public void endWrite();
}

interface TextTagComposite extends TextTag {
  public List<TextTag> getTags();
  public void addTag(TextTag tag);
}

class PTag implements TextTagComposite {
  private List<TextTag> tags = new ArrayList<TextTag>();

  @Override
  public void startWrite() {
    System.out.println("<p>");
  }

  @Override
  public void endWrite() {
    System.out.println("</p>");
  }

  @Override
  public List<TextTag> getTags() {
    return tags;
  }

  @Override
  public void addTag(TextTag tag) {
    tags.add(tag);
  }
}

class SpanTag implements TextTag {

  @Override
  public void startWrite() {
    System.out.println("<span>");
  }

  @Override
  public void endWrite() {
    System.out.println("</span>");
  }

}

class EmTag implements TextTag {

  @Override
  public void startWrite() {
    System.out.println("<em>");
  }

  @Override
  public void endWrite() {
    System.out.println("</em>");
  }

}

在这种情况下,可以看到一个复合对象。我们可以区分复合与非复合对象,因为第一个可以容纳一个或多个非复合对象( PTag类中的 privateListtags字段)。非复合对象称为叶子。 TextTag接口被称为组件,因为它为两个对象类型提供了共同的行为规范(有点像 Linux文件管理系统的有共同点的文件放在一个文件夹下进行管理,其实就是节点管理)。

Spring世界中,我们检索复合对象的概念是org.springframework.beans.BeanMetadataElement接口,用于配置 bean对象。它是所有继承对象的基本界面。现在,在一方面,我们有一个叶子,由org.springframework.beans.factory.parsing.BeanComponentDefinition表示,另一边是复合org.springframework.beans.factory.parsing.CompositeComponentDefinitionCompositeComponentDefinition类似于组件,因为它包含addNestedComponent(ComponentDefinition component)方法,它允许将叶添加到私有final列表中 nestedComponents。您可以看到,由于此列表, BeanComponentDefinitionCompositeComponentDefinition的组件是org.springframework.beans.factory.parsing.ComponentDefinition

策略模式

本文描述的第三个概念是策略设计模式。策略定义了通过不同方式完成相同事情的几个对象。完成任务的方式取决于采用的策略。举个例子说明,我们可以去一个国家。我们可以乘公共汽车,飞机,船甚至汽车去那里。所有这些方法将把我们运送到目的地国家。但是,我们将通过检查我们的银行帐户来选择最适应的方式。如果我们有很多钱,我们将采取最快的方式(可能是私人飞行)。如果我们没有足够的话,我们会采取最慢的(公车,汽车)。该银行账户作为确定适应策略的因素。

Spring在org.springframework.web.servlet.mvc.multiaction.MethodNameResolver类(过时,但不影响拿来研究)中使用策略设计模式。它是 MultiActionController(同样过时)的参数化实现。在开始解释策略之前,我们需要了解MultiActionController的实用性。这个类允许同一个类处理几种类型的请求。作为Spring中的每个控制器,MultiActionController执行方法来响应提供的请求。策略用于检测应使用哪种方法。解析过程在MethodNameResolver实现中实现,例如在同一个包中的ParameterMethodNameResolver中。方法可以通过多个条件解决:属性映射,HTTP请求参数或URL路径。

@Override
public String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {
  String methodName = null;

  // Check parameter names where the very existence of each parameter
  // means that a method of the same name should be invoked, if any.
  if (this.methodParamNames != null) {
    for (String candidate : this.methodParamNames) {
      if (WebUtils.hasSubmitParameter(request, candidate)) {
        methodName = candidate;
        if (logger.isDebugEnabled()) {
          logger.debug("Determined handler method '" + methodName +
            "' based on existence of explicit request parameter of same name");
        }
        break;
      }
    }
  }

  // Check parameter whose value identifies the method to invoke, if any.
  if (methodName == null && this.paramName != null) {
    methodName = request.getParameter(this.paramName);
    if (methodName != null) {
      if (logger.isDebugEnabled()) {
        logger.debug("Determined handler method '" + methodName +
          "' based on value of request parameter '" + this.paramName + "'");
      }
    }
  }

  if (methodName != null && this.logicalMappings != null) {
    // Resolve logical name into real method name, if appropriate.
    String originalName = methodName;
    methodName = this.logicalMappings.getProperty(methodName, methodName);
    if (logger.isDebugEnabled()) {
      logger.debug("Resolved method name '" + originalName + "' to handler method '" + methodName + "'");
    }
  }

  if (methodName != null && !StringUtils.hasText(methodName)) {
    if (logger.isDebugEnabled()) {
      logger.debug("Method name '" + methodName + "' is empty: treating it as no method name found");
    }
    methodName = null;
  }

  if (methodName == null) {
    if (this.defaultMethodName != null) {
      // No specific method resolved: use default method.
      methodName = this.defaultMethodName;
      if (logger.isDebugEnabled()) {
        logger.debug("Falling back to default handler method '" + this.defaultMethodName + "'");
      }
    }
    else {
      // If resolution failed completely, throw an exception.
      throw new NoSuchRequestHandlingMethodException(request);
    }
  }

  return methodName;
}

正如我们在前面的代码中可以看到的,方法的名称通过提供的参数映射,URL中的预定义属性或参数存在来解决(默认情况下,该参数的名称是action)。

模板模式

本文提出的最后一个设计模式是模板方法。此模式定义了类行为的骨架,并将子步骤的某些步骤的延迟执行(具体就是下面例子中一个方法放在另一个方法中,只有调用另一方方法的时候这个方法才会执行,而且还可能会在其他行为方法之后按顺序执行)。其中写了一种方法(下面例子中的construct()),注意定义为final,起着同步器的角色。它以给定的顺序执行由子类定义的方法。在现实世界中,我们可以将模板方法与房屋建设进行比较。独立于建造房屋的公司,我们需要从建立基础开始,只有在我们完成之后才能做其他的工作。这个执行逻辑将被保存在一个我们不能改变的方法中。例如基础建设或刷墙会被作为一个模板方法中的方法,具体到建筑房屋的公司。我们可以在给定的例子中看到它:

public class TemplateMethod {

  public static void main(String[] args) {
    HouseAbstract house = new SeaHouse();
    house.construct();
  }

}

abstract class HouseAbstract {
  protected abstract void constructFoundations();
  protected abstract void constructWall();

  // template method
  public final void construct() {
    constructFoundations();
    constructWall();
  }
}

class EcologicalHouse extends HouseAbstract {

  @Override
  protected void constructFoundations() {
    System.out.println("Making foundations with wood");
  }

  @Override
  protected void constructWall() {
    System.out.println("Making wall with wood");
  }

}

class SeaHouse extends HouseAbstract {

  @Override
  protected void constructFoundations() {
    System.out.println("Constructing very strong foundations");
  }

  @Override
  protected void constructWall() {
    System.out.println("Constructing very strong wall");
  }

}

该代码应该输出:

Constructing very strong foundations
Constructing very strong wall

Spring在org.springframework.context.support.AbstractApplicationContext类中使用模板方法。他们不是一个模板方法(在我们的例子中是construct ),而是多个。例如,getsFreshBeanFactory返回内部 bean工厂的新版本,调用两个抽象方法: refreshBeanFactory(刷新工厂bean)和 getBeanFactory(以获取更新的工厂bean)。这个方法和其他一些方法一样,用在public void refresh()中,抛出构造应用程序上下文的BeansException,IllegalStateException方法(这里会在后面Spring中与应用程序上下文分析中再次提到)。

我们可以从同一个包中的GenericApplicationContext找到一些通过模板方法所实现的抽象方法的实现的例子(说的有点拗口,多读几遍就好):

/**
  * Do nothing: We hold a single internal BeanFactory and rely on callers
  * to register beans through our public methods (or the BeanFactory's).
  * @see #registerBeanDefinition
  */
@Override
protected final void refreshBeanFactory() throws IllegalStateException {
  if (this.refreshed) {
    throw new IllegalStateException(
      "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
  }
  this.beanFactory.setSerializationId(getId());
  this.refreshed = true;
}

@Override
protected void cancelRefresh(BeansException ex) {
  this.beanFactory.setSerializationId(null);
  super.cancelRefresh(ex);
}

/**
  * Not much to do: We hold a single internal BeanFactory that will never
  * get released.
  */
@Override
protected final void closeBeanFactory() {
  this.beanFactory.setSerializationId(null);
}

/**
  * Return the single internal BeanFactory held by this context
  * (as ConfigurableListableBeanFactory).
  */
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
  return this.beanFactory;
}

/**
  * Return the underlying bean factory of this context,
  * available for registering bean definitions.
  * <p><b>NOTE:</b> You need to call {@link #refresh()} to initialize the
  * bean factory and its contained beans with application context semantics
  * (autodetecting BeanFactoryPostProcessors, etc).
  * @return the internal bean factory (as DefaultListableBeanFactory)
  */
public final DefaultListableBeanFactory getDefaultListableBeanFactory() {
  return this.beanFactory;
}

经过上面这些可以让我们发现Spring如何通过使用行为和结构设计模式来更好地组织上下文(模板方法),并通过相应策略来解决执行方法。它使用两种结构设计模式,通过代理模式来简化AOP部分并通过复合模式来构造复杂对象。

原文发布于微信公众号 - 程序猿DD(didispace)

原文发表时间:2017-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

《Kotlin 程序设计》第七章 Kotlin 编译过程分析第七章 Kotlin 编译过程分析

http://mp.weixin.qq.com/s/lEFRH523W7aNWUO1QE6ULQ

1602
来自专栏冰霜之地

高效的序列化/反序列化数据方式 Protobuf

上篇文章中其实已经讲过了 encode 的过程,这篇文章以 golang 为例,从代码实现的层面讲讲序列化和反序列化的过程。

5175
来自专栏码匠的流水账

聊聊sentinel的DegradeSlot

com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java

1301
来自专栏GIS讲堂

shape文件的生成与打包下载

1782
来自专栏逍遥剑客的游戏开发

MPQ文件系统优化(续)

1705
来自专栏社区的朋友们

iOS 中的 Promise 设计模式

无论是代理模式,还是闭包,在处理单一任务的时候,都出色的完成了任务。可是当两种模式要相互配合,一起完成一系列任务,并且每个任务之间还要共享信息,相互衔接,雇主就...

2.1K1
来自专栏JAVA高级架构开发

Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码

美国时间 09 月 25 日,Oralce 正式发布了 Java 11,这是据 Java 8 以后支持的首个长期版本。

2130
来自专栏大内老A

[ASP.NET MVC]通过对HtmlHelper扩展简化“列表控件”的绑定

在众多表单元素中,有一类<select>元素用于绑定一组预定义列表。传统的ASP.NET Web Form中,它对应着一组重要的控件类型,即ListContro...

1925
来自专栏mini188

java中的锁

java中有哪些锁 这个问题在我看了一遍<java并发编程>后尽然无法回答,说明自己对于锁的概念了解的不够。于是再次翻看了一下书里的内容,突然有点打开脑门的感觉...

3799
来自专栏iOS开发

iOS开发之 Method Swizzling 深入浅出

如果产品经理突然说:"在所有页面添加统计功能,也就是用户进入这个页面就统计一次"。我们会想到下面的一些方法:

4757

扫码关注云+社区

领取腾讯云代金券