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

上一部分我们谈Spring中的设计模式,讲了三种对象创建模式(生成器builder,工厂方法factory method,抽象工厂abstract factory)和一种行为模式(解释器interpreter),这一部分我们会聚焦在更多结构和行为模式上面。

这部分我们分别看两种结构模式(structural)和两种行为(behavioral)模式。前两个会是结构化设计模式:代理模式(proxy)和组合模式(composite)。后两个例子是行为模式:策略(strategy)和模板方法(template method)。

Spring 设计模式 - 代理模式 proxy

面向对象(OOP)可能成编程中最流行的概念了。然而,Spring引入了另外一种编码范例,面向切面编程(AOP)。简单点儿来定义,AOP是一种面向一些系统特定点的编程,比如异常抛出,特定类别方法的执行等等。AOP允许我们在系统的这些点的执行前后执行一些额外的动作。怎样才能做到这一点呢 ?可以通过监听器(listener)。但这种做法里,我们在每个地方都得定义任何一个可能的监听器调用(比如在方法开头)。正是因为这个原因,Spring没有使用这个思路。相反,Spring实现了另外一种设计模式,能够通过额外的方法调用完成该任务,这就是代理设计模式(proxy)。

“代理”工作起来像是对象的一面镜子。通过代理模式,代理对象不仅可以覆盖原对象,而且可以扩展它们的功能。因此,对于一个只能在屏幕上打印一些文本的某个对象,我们可以为其增加一个对象来过滤显示出来的文字。对第二个对象的调用就可以通过代理来定义。代理是一个封装了真实的原对象的对象。比如,如果你想调用bean Waiter,你可以调用这个bean的代理对象,而其代理对象的行为表现跟原对象一模一样(译注:可以理解成二者实现了同样的接口)。

代理模式一个好的例子是org.springframework.aop.framework.ProxyFactoryBean。这个工厂构造了Spring bean的AOP代理。该类实现了FactoryBean接口,该接口中定义了一个方法getObject()。这个方法用于返回向bean工厂所请求的bean实例。应用这种模式的情况下,该方法返回的不是一个原bean对象本身,而是bean对象的AOP代理对象。该代理对象跟背后的bean对象相比,除了具备相同的行为外,还可以增加一些修饰,可以在被代理bean对象的方法执行前执行一些额外的方法。
使用ProxyFactory的一个例子 :

public class TestProxyAop {

  @Test
  public void test() {
    // new House()是将要被代理的对象,House类实现了接口Construction
    ProxyFactory factory = new ProxyFactory(new House());
    // 告诉代理工厂代理对象和被代理对象哪些行为必须保持一致,通过接口Construction定义告知
    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")) {
      // 该分支在被代理对象的 construct 方法被调用之前被调用  , 授权可以盖房
      ((Construction) target).givePermission();
    }
  }

}

该测试会通过,因为我们并不直接在原House对象实例上操作而是在代理对象上操作。在调用原对象House的目标方法construct前,代理对象会首先调用BeforeConstructAdvicebefore方法,在这个方法中,它会对原对象House做一个”授权”(译注:通过调用原House对象上的givePermission()方法)。因为代理层能够很简单地把请求分发给其他对象,所以它为目标对象提供了额外的功能补充。想做到这一点,我们可以只修改before方法中的过滤条件。

Spring 设计模式 - 组合模式 composite

另外一种结构设计模式是组合模式composite。在上一部分中,我们使用生成器builder来构造复杂对象。另外一种构造复杂对象的方式就是组合模式composite。该模式基于多个已经存在的具备同样行为的对象,然后构造一个更大的对象。这个更大的对象仍然具备每个小对象的同样的特征,比如有同样的行为方法。

组合模式一个非Spring的例子,比如写一段HTML文本,这段HTML文本由段落组成,这些段落可以是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对象是一个组合对象。我们能区分组合对象和非组合对象是因为PTag能够包含一个或者多个非组合对象(PTag中的私有属性 List tags)。非组合对象被称作叶子。TextTag被称作组件,因为它提供了两类对象的共同的行为。

Spring中,我们抽取组合对象的概念到了接口org.springframework.beans.BeanMetadataElement,用于配置bean对象。它是所有对象继承自的基础接口。现在,我们一方面有了叶子,通过org.springframework.beans.factory.parsing.BeanComponentDefinition来表示,另一方面,我们有了组合org.springframework.beans.factory.parsing.CompositeComponentDefinitionCompositeComponentDefinition充当了组件因为它包含了一个方法addNestedComponent(ComponentDefinition component),该方法允许向private final List nestedComponents属性中增加叶子。从这个列表你可以看出,BeanComponentDefinitionCompositeComponentDefinition的组件是org.springframework.beans.factory.parsing.ComponentDefinition

Spring 设计模式 - 策略模式 strategy

这篇文章里我们描述的第三个概念是策略模式。策略定义了使用不同方式完成同一件事的多个对象。完成这件事的方式取决于所采用的策略。作为例子,我们可以看一下去国外的方式。可以是坐公共汽车,飞机,轮船甚至是自驾。所有这些方法都能把我们带到目的地国家。但是,我们一定会先检查我们银行账号,再去决定最合适的方式。如果有足够的钱,我们会采用最快的方式(可能是私人飞机)。如果没那么多钱,那就最慢的(公共汽车,自驾)。这里,银行账号扮演了所选择的策略的一个定义因子。

在类org.springframework.web.servlet.mvc.multiaction.MethodNameResolver中,Spring使用了策略模式。其实现的目的是参数化MultiActionController。看是讲解该策略之前,我们需要理解一下工具类MultiActionController。该类允许在同一个类中处理多种类型的请求。向Spring中的每个控制器一样,MultiActionController执行响应所提供的请求的方法。决定哪个方法将被调用采用了策略。对应的决策过程在MethodNameResolver的实现中,同一个包中的ParameterMethodNameResolver就是一个这样的例子。决定调用哪个方法可以有多个标准 : 属性映射关系,HTTP请求参数或者URL路径。这个策略实现例子是ParameterMethodNameResolver类的方法getHandlerMethodName

@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()) {
        + "' 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中参数的存在性决定的。

Spring 设计模式 - 模板方法 template method

这一部分我们要讲的最后一个设计模式是模板方法。这个模式定义了类的行为的一个骨架,然后把一些步骤的执行延迟到了子类中。通常会有一个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

在类org.springframework.context.support.AbstractApplicationContext中,Spring使用了模板方法。他不是一个模板方法(像我们上面例子所举的),而是多个模板方法。比如,obtainFreshBeanFactory通过调用两个抽象方法refreshBeanFactory (用于刷新bean工厂) and getBeanFactory (用于获取bean工厂)返回最新版本的内部bean工厂。这个方法,还有其他一些方法,会被用于构建应用上下文对象的公开方法public void refresh() throws BeansException, IllegalStateException使用。这两个抽象方法的实现的具体例子你可以参考同一包下面的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如何使用行为设计模式来更好地组织上下文(模板方法template method),如何决定要执行的方法(策略模式strategy)。还有两个结构化设计模式用来简化AOP部分(代理模式proxy)和构造复杂对象(组合模式composite)。

英文原文

该系列文章目录

Spring框架中的设计模式(五)
Spring框架中的设计模式(四)
Spring框架中的设计模式(三)
Spring框架中的设计模式(二)
Spring框架中的设计模式(一)

该系列文章目录

Spring框架中的设计模式(五)
Spring框架中的设计模式(四)
Spring框架中的设计模式(三)
Spring框架中的设计模式(二)
Spring框架中的设计模式(一)

猜你喜欢

转载自blog.csdn.net/andy_zhang2007/article/details/79775745
今日推荐