Spring框架中的設計模式(五)

Spring框架中的設計模式(五)通過以前的4篇文章,我們看到Spring采用了大量的關于創(chuàng)建和結構方面的設計模式。

本文將描述屬于行為方面的兩種設計模式:命令和訪問者。


命令模式

這篇文章描述的第一個行為設計模式是命令。它允許將請求封裝在一個對象內并附加一個回調動作(每次遇到所所謂的回調大家就只需要理解為一個函數(shù)方法就好,省的去浪費那么多腦子)。請求被封裝在命令對象之下,而請求的結果被發(fā)送到接收者。命令本身不是由調用者執(zhí)行。為了直白了解其中的主要思想,想象一下管理服務器的情況(遠程通過ssh操作Linux服務器)。管理員(invoker)在命令行(commands)中啟動一些操作,將結果發(fā)送到服務器(接收器)。在這里,所有這一切都是由客戶端的終端(也就是我們用的xshell)來完成的。搞個Demo來說明一下(對于命令,它的動作就是執(zhí)行,對于管理員來講,我們的動作其實就是一個回車,執(zhí)不執(zhí)行當然是管理員說的算了,執(zhí)行交給命令對象了,服務器最后就是一個展示結果):

public class CommandTest {

  // This test method is a client
  @Test
  public void test() {
    Administrator admin = new Administrator();
    Server server = new Server();

    // start Apache
    admin.setCommand(new StartApache(server));
    admin.typeEnter();

    // start Tomcat
    admin.setCommand(new StartTomcat(server));
    admin.typeEnter();

    // check executed commands
    int executed = server.getExecutedCommands().size();
    assertTrue("Two commands should be executed but only "+
      executed+ " were", executed == 2);
  }

}

// commands
abstract class ServerCommand {

  protected Server server;

  public ServerCommand(Server server) {
    this.server = server;
  }

  public abstract void execute();
}

class StartTomcat extends ServerCommand {

  public StartTomcat(Server server) {
    super(server);
  }

  @Override
  public void execute() {
    server.launchCommand("sudo service tomcat7 start");
  }
}

class StartApache extends ServerCommand {

  public StartApache(Server server) {
    super(server);
  }

  @Override
  public void execute() {
    server.launchCommand("sudo service apache2 start");
  }
}

// invoker
class Administrator {

  private ServerCommand command;

  public void setCommand(ServerCommand command) {
    this.command = command;
  }

  public void typeEnter() {
    this.command.execute();
  }

}

// receiver
class Server {

  // as in common terminals, we store executed commands in history
  private List<String> executedCommands = new ArrayList<String>();

  public void launchCommand(String command) {
    System.out.println("Executing: "+command+" on server");
    this.executedCommands.add(command);
  }

  public List<String> getExecutedCommands() {
    return this.executedCommands;
  }

}

測試應通過并打印兩個命令:

Executing: sudo service apache2 start on server
Executing: sudo service tomcat7 start on server

命令模式不僅允許封裝請求(ServerCommand)并將其傳輸?shù)浇邮掌鳎⊿erver),而且還可以更好地處理給定的請求。在這里,這種更好的處理是通過存儲命令的執(zhí)行歷史。在Spring中,我們在beanFactory后置處理器的特性中來找到指令設計模式的原理。要通過快速對它們進行定義,應用程序上下文會啟動后置處理器,并可以用來對創(chuàng)建的bean進行一些操作(這里不打算細說了,具體的我后面會專門寫一篇這方面的文章,來分析其中的源碼細節(jié))。

當我們將先前Demo里呈現(xiàn)的命令邏輯轉換并對比到Spring bean工廠后處理器時,我們可以區(qū)分以下actors:后置處理器bean(是指實現(xiàn)BeanFactoryPostProcessor接口)是命令,org.springframework.context.support.PostProcessorRegistrationDelegate是調用者(它執(zhí)行postProcessBeanFactory方法注冊所有的后置處理器bean,此處看下面第二段代碼)和接收器org.springframework.beans.factory.config.ConfigurableListableBeanFactory可以在元素(bean)構造初始化之前修改它們(例如:在初始化bean之前可以更改屬性)。

另外,回顧下上面的那個Demo,和我們的Demo中的命令歷史管理一樣。PostProcessorRegistrationDelegate包含一個內部類BeanPostProcessorChecker,它可以記錄當一個bean不符合處理條件的情況。

可以觀察PostProcessorRegistrationDelegate中的兩段代碼:

/**
     * BeanPostProcessor that logs an info message when a bean is created during
     * BeanPostProcessor instantiation, i.e. when a bean is not eligible for
     * getting processed by all BeanPostProcessors.
     */
    private static class BeanPostProcessorChecker implements BeanPostProcessor {
        private static final Log logger = LogFactory.getLog(BeanPostProcessorChecker.class);
        private final ConfigurableListableBeanFactory beanFactory;
        private final int beanPostProcessorTargetCount;
        public BeanPostProcessorChecker(ConfigurableListableBeanFactory beanFactory, int beanPostProcessorTargetCount) {
            this.beanFactory = beanFactory;
            this.beanPostProcessorTargetCount = beanPostProcessorTargetCount;
        }
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (bean != null && !(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
                    this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
                if (logger.isInfoEnabled()) {
                    logger.info("Bean '" + beanName + "' of type [" + bean.getClass() +
                            "] is not eligible for getting processed by all BeanPostProcessors " +
                            "(for example: not eligible for auto-proxying)");
                }
            }
            return bean;
        }
        private boolean isInfrastructureBean(String beanName) {
            if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
                BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
                return RootBeanDefinition.ROLE_INFRASTRUCTURE == bd.getRole();
            }
            return false;
        }
    }

定義后的調用,用的就是ConfigurableListableBeanFactory的實例(看BeanPostProcessorChecker注釋):

public static void registerBeanPostProcessors(
            ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
        // Register BeanPostProcessorChecker that logs an info message when
        // a bean is created during BeanPostProcessor instantiation, i.e. when
        // a bean is not eligible for getting processed by all BeanPostProcessors.
        int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
  //BeanPostProcessorChecker
        beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
        // Separate between BeanPostProcessors that implement PriorityOrdered,
        // Ordered, and the rest.
        List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
        List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
        List<String> orderedPostProcessorNames = new ArrayList<>();
        List<String> nonOrderedPostProcessorNames = new ArrayList<>();
        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
                priorityOrderedPostProcessors.add(pp);
                if (pp instanceof MergedBeanDefinitionPostProcessor) {
                    internalPostProcessors.add(pp);
                }
            }
            else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            }
            else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }
        // First, register the BeanPostProcessors that implement PriorityOrdered.
        sortPostProcessors(beanFactory, priorityOrderedPostProcessors);
        registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
        // Next, register the BeanPostProcessors that implement Ordered.
        List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
        for (String ppName : orderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            orderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        sortPostProcessors(beanFactory, orderedPostProcessors);
        registerBeanPostProcessors(beanFactory, orderedPostProcessors);
        // Now, register all regular BeanPostProcessors.
        List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
        for (String ppName : nonOrderedPostProcessorNames) {
            BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
            nonOrderedPostProcessors.add(pp);
            if (pp instanceof MergedBeanDefinitionPostProcessor) {
                internalPostProcessors.add(pp);
            }
        }
        registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
        // Finally, re-register all internal BeanPostProcessors.
        sortPostProcessors(beanFactory, internalPostProcessors);
        registerBeanPostProcessors(beanFactory, internalPostProcessors);
        // Re-register post-processor for detecting inner beans as ApplicationListeners,
        // moving it to the end of the processor chain (for picking up proxies etc).
        beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    }

總結一個過程就是,我要BeanFactory里面得到對象(也就是為了得到一個命令的執(zhí)行結果),那么,想要在得到對象的時候就已經(jīng)實現(xiàn)了一些對其修改的想法,那么就通過后置處理器,也是就實現(xiàn)了后置處理器接口的beans(命令里可以通過傳入不同的參數(shù)來得到不同結果,或者對命令的腳本進行修改),然后還需要一個執(zhí)行者(我們在做自動化運維的時候,不止操作一個腳本,這里的PostProcessorRegistrationDelegate就是集中來管理這些的),最后得到的結果就由BeanFactory來展示咯。

訪問者模式

接下來要介紹的一個行為設計模式是Visitor:抽象一點就是通過另一種類型的對象來使一個對象訪問。在這個簡短定義中,使用這個設計模式中的對象將被視為訪問者或對象可被訪問。第一個訪問者要有可訪問支持。這個模式的一個現(xiàn)實的例子可以是一個汽車質檢員,他們檢查一些汽車零件,比如輪子,制動器和發(fā)動機,以判斷汽車質量是否合格。我們來做個JUnit測試用例:

public class VisitorTest {

  @Test
  public void test() {
    CarComponent car = new Car();
    Mechanic mechanic = new QualifiedMechanic();
    car.accept(mechanic);
    assertTrue("After qualified mechanics visit, the car should be broken",
      car.isBroken());
    Mechanic nonqualifiedMechanic = new NonQualifiedMechanic();
    car.accept(nonqualifiedMechanic);
    assertFalse("Car shouldn't be broken becase non qualified mechanic " +
      " can't see breakdowns", car.isBroken());
  }

}

// visitor
interface Mechanic {
  public void visit(CarComponent element);
  public String getName();
}

class QualifiedMechanic implements Mechanic {

  @Override
  public void visit(CarComponent element) {
    element.setBroken(true);
  }

  @Override
  public String getName() {
    return "qualified";
  }
}

class NonQualifiedMechanic implements Mechanic {

  @Override
  public void visit(CarComponent element) {
    element.setBroken(true);
  }

  @Override
  public String getName() {
    return "unqualified";
  }
}

// visitable
abstract class CarComponent {
  protected boolean broken;

  public abstract void accept(Mechanic mechanic);

  public void setBroken(boolean broken) {
    this.broken = broken;
  }

  public boolean isBroken() {
    return this.broken;
  }
}

class Car extends CarComponent {

  private boolean broken = false;
  private CarComponent[] components;

  public Car() {
    components = new CarComponent[] {
      new Wheels(), new Engine(), new Brake()
    };
  }

  @Override
  public void accept(Mechanic mechanic) {
    this.broken = false;
    if (mechanic.getName().equals("qualified")) {
      int i = 0;
      while (i < components.length && this.broken == false) {
        CarComponent component = components[i];
        mechanic.visit(component);
        this.broken = component.isBroken();
        i++;
      }
    }
    // if mechanic isn't qualified, we suppose that 
    // he isn't able to see breakdowns and so 
    // he considers the car as no broken 
    // (even if the car is broken)
  }

  @Override
  public boolean isBroken() {
          return this.broken;
  }
}

class Wheels extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

class Engine extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

class Brake extends CarComponent {

  @Override
  public void accept(Mechanic mechanic) {
    mechanic.visit(this);
  }
}

在這個例子中,我們可以看到他們有兩個機制(訪問者,其實就是免檢和不免檢):合格和不合格。暴露于他們的可見對象是汽車。通過其接受方式,決定哪個角色應該適用于被訪問者(通過代碼mechanic.getName().equals("qualified")來判斷)。當訪問者合格時,Car讓他分析所有組件。如果訪問者不合格,Car認為其干預是無用的,并且在方法isBroken()中直接返回false(其實就是為了達到一個免檢的效果)。Spring在beans配置中實現(xiàn)了訪問者設計模式。為了觀察,我們可以看看org.springframework.beans.factory.config.BeanDefinitionVisitor對象,該對象用于解析bean元數(shù)據(jù)并將其解析為String(例如:具有作用域或工廠方法名稱的XML屬性)或Object(例如:構造函數(shù)定義中的參數(shù))。已解析的值在與分析的bean關聯(lián)的BeanDefinition實例中進行判斷設置。具體的源碼請看BeanDefinitionVisitor的代碼片段:

/**
 * Traverse the given BeanDefinition object and the MutablePropertyValues
 * and ConstructorArgumentValues contained in them.
 * @param beanDefinition the BeanDefinition object to traverse
 * @see #resolveStringValue(String)
 */
public void visitBeanDefinition(BeanDefinition beanDefinition) {
  visitParentName(beanDefinition);
  visitBeanClassName(beanDefinition);
  visitFactoryBeanName(beanDefinition);
  visitFactoryMethodName(beanDefinition);
  visitScope(beanDefinition);
  visitPropertyValues(beanDefinition.getPropertyValues());
  ConstructorArgumentValues cas = beanDefinition.
    getConstructorArgumentValues();
  visitIndexedArgumentValues(cas.
    getIndexedArgumentValues());
  visitGenericArgumentValues(cas.
    getGenericArgumentValues());
}

protected void visitParentName(BeanDefinition beanDefinition) {
  String parentName = beanDefinition.getParentName();
  if (parentName != null) {
    String resolvedName = resolveStringValue(parentName);
    if (!parentName.equals(resolvedName)) {
      beanDefinition.setParentName(resolvedName);
    }
  }
}

在這種情況下,他們只是訪問方式,沒有對訪問者做任何補充的控制(在Demo里對car的質檢員做了控制)。這里訪問包括分析給定BeanDefinition的參數(shù),并將其替換為已解析對象。

在最后一篇關于Spring中設計模式的文章中,我們發(fā)現(xiàn)了2種行為模式:用于處理bean工廠的后置處理的命令模式和用于將定義的bean參數(shù)轉換為面向對象(String或Object的實例)參數(shù)的訪問者模式。

原文:Spring框架中的設計模式(五)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容