spring框架中的设计模式二

在这篇文章中,介绍4种设计模式。结构型设计模式:代理和复合模式。行为型设计模式:策略和模板方法模式。

代理模式

面向对象编程(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示例可以是一个写入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类中的private List tags字段)。非复合对象称为叶子。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部分并通过复合模式来构造复杂对象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343