上一部分我们谈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
前,代理对象会首先调用BeforeConstructAdvice
的before
方法,在这个方法中,它会对原对象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.CompositeComponentDefinition
。CompositeComponentDefinition
充当了组件因为它包含了一个方法addNestedComponent(ComponentDefinition component)
,该方法允许向private final List nestedComponents
属性中增加叶子。从这个列表你可以看出,BeanComponentDefinition
和CompositeComponentDefinition
的组件是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框架中的设计模式(一)