設計模式 - 說說常用開源框架中設計模式使用分析

學習完整課程請移步 互聯網 Java 全棧工程師

一、前言

設計模式代表了軟件設計的最佳實踐,是經過不斷總結提煉出來的代碼設計經驗的分類總結,這些模式或者可以簡化代碼,或者可以是代碼邏輯開起來清晰,或者對功能擴展很方便

設計模式按照使用場景可以分為三大類:

  • 創建型模式(Creational Patterns):對對象的實例化過程進行抽象,這使得一個系統可以不用關心這些對象是如何創建,組合,呈現的,對于類創建模式來說通過使用繼承改變實例化的類,對于對象創建模式來說通過使用代理來實例化所需要的對象。

    • 工廠模式
    • 原型模式
    • 構建者模式
    • 單例模式
  • 結構型模式(Structural Patterns):通過對多個類和對象進行組合得到復雜結構的類,一般使用繼承繼承或者成員變量引用形式來實現。

    • 適配器模式
    • 橋接模式
    • 過濾器模式
    • 組合模式
    • 裝飾者模式
    • 門面模式
    • 享元模式
    • 代理模式
  • 行為型模式(Behavioral Patterns):行為模式不僅表達了對象和類,還表達了他們之間的交互,涉及到了對象和算法的分配。

    • 責任鏈模式
    • 命令模式
    • 解釋器模式
    • 迭代器模式
    • 中介者模式
    • 備忘錄模式
    • 觀察者模式
    • 狀態模式
    • 空對象模式
    • 策略模式
    • 模板方法模式
    • 訪問者模式

二、責任鏈設計模式(Chain of Responsibility Pattern)

介紹

責任鏈模式是把多個對象串聯起來形成一個鏈狀結構,讓每個對象都有機會對事件發送者的請求進行處理。責任鏈模式是設計模式中的行為模式,設計意圖是為了使事件發送者和事件接受者之間解耦。通常責任鏈鏈中的每個對象都有下一個對象的引入(例如 Tomcat 里面 StandardPipeline 用來管理 valve),或者有個同一個鏈管理工廠里面使用數組存放了所有的對象(例如 Tomcat 里面 ApplicationFilterChain 用來關系 filter)。

Tomcat 中 Valve 鏈

Tomcat 中 StandardEngine, StandardHost, StandardContext 里面都有自己 StandardPipeline,下面以 StandardEngine 里面 StandardPipeline 為例講解

image

從上面類圖可知道每個 Valve 都要繼承 ValveBase 類,該類里面有一個 Valve 的引用,實際是鏈中下一個節點對象,Valve 就是通過每個 Valve 里面的 next 串聯為鏈的。

image

每個 valveinvoke 方法里面調用 next.invoke 激活鏈中下一個節點,并且 StandardEngine, StandardHost, StandardContext 都有一個 basic valve 這個 valve 在鏈的末尾用來激活子容器的 valve 鏈。

Tomcat 中 Filter 鏈

Tomcat 中 Filter 鏈是使用 ApplicationFilterChain 來管理的,具體結構如下圖:

image

可知 Filter 鏈不是像 Valve 一樣在內部維護下個節點的引用,而是在 ApplicationFilterChain 中搞了個數組存放所有的 Filter,并通過 n 統計 Filter 總個數,pos 是當前 filter 的下標。

ApplicationFilterChaindoFilter 代碼如下:

public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        ...
        internalDoFilter(request,response);
        ...
    }


private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {

        // Call the next filter if there is one
        if (pos < n) {

            //獲取filter鏈中下標為pos的filter
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                            Boolean.FALSE);
                }

                ...

                //調用自定義filter的dofilter方法
                filter.doFilter(request, response, this);


                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                          filter, request, response);
            } 
            ....

    }
   .....
}

使用場景

  • 當一個請求需要根據請求參數的不同由不同對象來處理時候。
  • 當一個請求需要固定對象順序處理,并且可擴展性的在固定順序里面插入新的對象進行處理時候。

三、工廠模式(Factory Pattern)

介紹

工廠模式是創建型模式,他封裝了對象的創建過程,調用者使用具體的工廠方法根據參數就可以獲取對應的對象。

Spring 框架中 BeanFactory

image

如圖 BeanFactory 接口提供了 getBean 方法,在 AbstractBeanFactory 中實現了該方法,經過層層繼承,實現,最后 DefaultListableBeanFactory 實現了 BeanDefinitionRegistry 接口用來保存 bean 定義,繼承了 AbstractAutowireCapableBeanFactory 用來支撐 autowired

舉個例子

@Test
public void testBeanFactoy() throws NamingException, SQLException, ParseException, IOException {

    // 創建 Bean 工廠
    DefaultListableBeanFactory bf = new DefaultListableBeanFactory();

    // 給 Bean 工廠添加 Bean 定義, 解析 xml 里面的 Bean 放入 Bean 工廠
    loadBeanDefinitions(bf);

    // 根據名字從 Bean 工廠獲取 Bean
    Hello hello = (Hello) bf.getBean("hello");
    hello.sayHello();

    Hello2 hello2 = (Hello2) bf.getBean("hello2");
    hello2.sayHello();


}

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    String[] configLocations = new String[] { "beans2.xml" };
    if (configLocations != null) {
        beanDefinitionReader.loadBeanDefinitions(configLocations);
    }
}

使用場景

  • 不同條件下創建不同實例,用于統一管理 Bean
  • 不同條件下調用不同工廠方法獲取不同場景下的 Bean

四、單例設計模式(Singleton Pattern)

介紹

單例模式是一種創建型模式,單例模式提供一個創建對象的接口,但是多次調用該接口返回的是同一個實例的引用,目的是為了保證只有一個實例,并且提供一個訪問這個實例的統一接口。

Spring 中單例 Bean 的創建

Spring 中默認配置的 beanscopesingleton,也就是單例作用域。那么看看它是如何做到的。在 AbstractBeanFactory 類里面的 doGetBean 方法:

protected Object doGetBean(
            final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean = null;

    // 解決set循環依賴
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        ...
    }

    else {
        ...
        // 創建單件bean.
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, new ObjectFactory() {
                public Object getObject() throws BeansException {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                    ...
                        throw ex;
                    }
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
       //創建原型bean
        else if (mbd.isPrototype()) {
            ...
        }
        //創建request作用域bean
        else {
            ...
        }
    }
            ...
    return bean;
}

getSingleton 代碼:

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    Assert.notNull(beanName, "'beanName' must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            ...
            beforeSingletonCreation(beanName);
            ...
            try {
                singletonObject = singletonFactory.getObject();
            }
            catch (BeanCreationException ex) {
                ...
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            addSingleton(beanName, singletonObject);
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

private final Map singletonObjects = CollectionFactory.createConcurrentMapIfPossible(16);

可知 Spring 內部四通過一個 ConcurrentMap 來管理單件 bean 的。獲取 bean 時候會先看看 singletonObjects 中是否有,有則直接返回,沒有則創建后放入。

image

Spring 的 bean 工廠管理的單例模式管理的是多個 bean 實例的單例,是工廠模式管理所有的 bean,而每個 bean 的創建又使用了單例模式。

使用場景

  • 同一個 JVM 應用的不同模塊需要使用同一個對象實例進行信息共享。
  • 需要同一個實例來生成全局統一的序列號

五、原型設計模式(Prototype Pattern)

介紹

相比單例設計模式,原型模式是每次創建一個對象,下面看下 Spring 是如何使用原型模式的

Spring 中原型 bean 的創建

創建原型 bean 需要在 xml 特別說明:

<bean id="hello" class="com.lusifer.demo.Hello" scope="prototype"/>
protected <T> T doGetBean(
        final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
        throws BeansException {

    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
     ...
    }

    else {
        ...

        try {
            ...

            // Create bean instance.
            if (mbd.isSingleton()) {
                ...
            }
            //創建原型bean
            else if (mbd.isPrototype()) {
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                ...
            }
        }
        catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }
 ...
    return (T) bean;
}

createBean 函數里面則是根據 bean 定義創建新 bean,感興趣的可以看看。

使用場景

當有業務場景使用某個 bean 時候需要使用自己的一個拷貝的時候使用。

六、策略模式(Strategy Pattern)

介紹

策略模式屬于行為性模式,它定義一系列的算法對象,使用時候可以使它們相互替換。

Spring 中 bean 實例化策略

image

從圖知道:接口 InstantiationStrategy 是實例化策略接口類,它定義了三個實例化接口,然后 SimpleInstantiationStrategy 實現了該策略,它主要做一些簡單的根據構造函數實例號 bean 的工作,然后 CglibSubclassingInstantiationStrategy 又繼承了 SimpleInstantiationStrategy 新增了方法注入方式根據 cglib 生成代理類實例化方法。

AbstractAutowireCapableBeanFactory 中管理了該策略的一個對象,默認是 CglibSubclassingInstantiationStrategy 策略,運行時候可以通過 setInstantiationStrategy 改變實例化策略,如果你自己寫個個策略的話。

Spring 中 AOP 代理策略

image

首先看 AopProxyFactory 接口類提供了 createAopProxy 接口,這個是策略模式的接口方法。然后 DefaultAopProxyFactory 實現了該接口作為策略的實現者。然后 ProxyCreatorSupport 里面引用了 AopProxyFactory,并且提供了 get, set 方法用來運行時改變策略,這里 Spring 只實現了 DefaultAopProxyFactory 這一個策略,如果需要自己也可以寫個。

DefaultAopProxyFactory 里面的 createAopProxy 的邏輯如下,可以在運行時根據參數決定用 Cglib 策略還是 JDK動態代理 策略生成代理類:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    // 如果 XML 打開了優化開關,或者設置為了代理目標類,或者目前類沒有接口
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }

        // 如果有接口,或者通過 Proxy.newProxyInstance 生成的,則使用 JDK動態代理
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }

        // 使用 CGLIB
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        // 使用 JDK動態代理
        return new JdkDynamicAopProxy(config);
    }
}

另外 AopProxy 也是一個策略接口類,具體實現的策略為 JdkDynamicAopProxyCglibAopProxyObjenesisCglibAopProxy

Tomcat 中 Digester 解析 server.xml

Tomcat 中的 Digester 是為了解析 server.xml 的,其中每個元素都有一個解析規則就是 Rule,DigestER 一開始先指定不同的解析策略(Rule),然后在具體解析 server.xml 時候根據節點不同使用不同解析策略來解析節點。

image

如圖在解析每個節點時候會先找到該節點對應的解析策略,然后循環去調用所有解析策略的方法去處理。

使用場景

運行時根據條件的不同使用不同的策略處理一個事情,與責任鏈不同在于,責任鏈是一個鏈條,一個事情可以被責任鏈里面所有節點處理,而 策略模式則是只有有一個對象來處理。

七、門面模式(Facade Pattern)

介紹

門面模式是一種結構性模式,它通過新增一個門面類對外暴露系統提供的一部分功能,或者屏蔽了內部系統的復雜性,對外部僅僅暴露一個簡單的接口,或者通過調用不同的服務對外提供統一的接口,讓使用者對這些內部服務透明化。

模板引擎 Velocity 中門面模式使用

Velocity 里面的 VelocityEngineVelocity 類都是 RuntimeInstance 類的門面,后者提供了模板渲染的所有功能,前兩者則是內部維護 RuntimeInstance 的實例,具體工作還是委托給 RuntimeInstance 來實現。

image

如圖 RuntimeInstance 提供了 Velocity 引擎的所用功能,VelocityEngine 內部直接引用了 RuntimeInstance 的一個實例,VelocityEngine 對外暴露的服務都是委托 RuntimeInstance 實現,并且每次 new 一個 VelocityEngine 內部都會有 RuntimeInstance 的一個實例被創建。而 Velocity 類調用了單例模式類 RuntimeSingleton 里面的方法,RuntimeSingleton 又是 RuntimeInstance 的一個單例模式。

使用場景

  • 當需要對外屏蔽一個系統的復雜性時候可以考慮使用門面模式對外提供簡單可讀性高的接口類
  • 當需要對外部暴露系統一部分權限的接口時候可以考慮使用門面模式減少系統權限。
  • 當系統需要調用不同服務匯總后在對外提供服務時候可以考慮使用門面模式對外屏蔽細節,之暴露一個接口。

八、裝飾器模式(Decorator Pattern)

介紹

裝飾器模式是一種結構性模式,它作用是對對象已有功能進行增強,但是不改變原有對象結構。這避免了通過繼承方式進行功能擴充導致的類體系臃腫。

Spring 中 BeanDefinitionDecorator

image

如圖 ScopedProxyBeanDefinitionDecorator 實現了 decorate 方法用來對 scope 作用域為 requestbean 定義進行包裝。具體時序圖為:

image
class ScopedProxyBeanDefinitionDecorator implements BeanDefinitionDecorator {

    private static final String PROXY_TARGET_CLASS = "proxy-target-class";


    @Override
    public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
        boolean proxyTargetClass = true;
        if (node instanceof Element) {
            Element ele = (Element) node;
            if (ele.hasAttribute(PROXY_TARGET_CLASS)) {
                proxyTargetClass = Boolean.valueOf(ele.getAttribute(PROXY_TARGET_CLASS));
            }
        }

        // 創建scoped的代理類,并注冊到容器
        BeanDefinitionHolder holder =
                ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);
        String targetBeanName = ScopedProxyUtils.getTargetBeanName(definition.getBeanName());
        parserContext.getReaderContext().fireComponentRegistered(
                new BeanComponentDefinition(definition.getBeanDefinition(), targetBeanName));
        return holder;
    }

}

關于 ScopedProxyBeanDefinitionDecorator 干啥用的呢:

<bean id="lavaPvgInfo" class="com.alibaba.lava.privilege.PrivilegeInfo"
    scope="request">
    <property name="aesKey" value="666" />
    <aop:scoped-proxy />
</bean>

其實就是處理 <aop:scoped-proxy /> 的,具體作用是包裝 lavaPvgInfobean 定義為 ScopedProxyFactoryBean,作用是實現 request 作用域 bean

commons-collections 包中 ListUtils

image
  • ListUtils 中的四個方法分別依賴 list 的四種裝飾器類對 List 功能進行擴充和限制。
  • 其中 FixedSizeList 類通過禁止 add/remove 操作保證 list 的大小固定,但是可以修改元素內容
  • 其中 UnmodifiableList 類通過禁用 add, clear, remove, set,保證 list 的內容不被修改
  • 其中 SynchronizedList 類通過使用 Lock 來保證 add, set, get, remove 等的同步安全
  • 其中 LazyList 類則當調用 get 方法發現 list 里面不存在對象時候,自動使用 factory 創建對象.

使用場景

在不改變原有類結構基礎上,新增或者限制或者改造功能時候。

九、適配器模式(Adapter Pattern)

介紹

適配器模式屬于結構性模式,它為兩個不同接口之間互通提供了一種手段。

Spring 中 MethodInterceptor 適配器

在 Spring Aop 框架中,MethodInterceptor 接口被用來攔截指定的方法,對方法進行增強。

image

大家都知道在 AOP 中每個 advistor 里面會有一個 advice 具體做切面動作,Spring 提供了 AspectJAfterReturningAdviceAspectJMethodBeforeAdviceAspectJAroundAdviceAspectJAfterAdvice 這幾個 advice,在 XML 配置 AOP 時候會指定 <aop:after-returning/>, <aop:before/>, <aop:around/>, <aop:after/>, 其實內部就是創建上面對應的這些 advice

從圖知道 AspectJAfterReturningAdviceAspectJMethodBeforeAdvice 沒有實現 MethodInterceptor 接口,其他兩者則實現了該接口。而 Spring Aop 的方法攔截器卻必須是實現了 MethodInterceptor 的,所以 Spring 提供了對應的適配器來適配這個問題,分別是 MethodBeforeAdviceAdapterAfterReturningAdviceAdapterThrowsAdviceAdapter

image

看下 DefaultAdvisorAdapterRegistrygetInterceptors 方法:

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
    List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);

    //從advistor中獲取advice
    Advice advice = advisor.getAdvice();

    //如果實現了MethodInterceptor則直接加入,比如AspectJAroundAdvice,AspectJAfterAdvice
    if (advice instanceof MethodInterceptor) {
        interceptors.add((MethodInterceptor) advice);
    }

    //否者看是否有當前advice的適配器,首先檢驗是否支持,支持則返回對應的適配器
    for (AdvisorAdapter adapter : this.adapters) {
        if (adapter.supportsAdvice(advice)) {
            interceptors.add(adapter.getInterceptor(advisor));
        }
    }
    if (interceptors.isEmpty()) {
        throw new UnknownAdviceTypeException(advisor.getAdvice());
    }
    return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
}

MethodBeforeAdviceAdapter 為例子看下:

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
    }

    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }

}

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {

    private MethodBeforeAdvice advice;


    /**
     * Create a new MethodBeforeAdviceInterceptor for the given advice.
     * @param advice the MethodBeforeAdvice to wrap
     */
    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    public Object invoke(MethodInvocation mi) throws Throwable {
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
        return mi.proceed();
    }

}

可知 MethodBeforeAdviceInterceptor 繼承了 MethodInterceptor 作為了一個適配器內部委托請求給 MethodBeforeAdvice

使用場景

  • 兩個系統交互時候由于接口參數不一樣沒辦法直接對接,則可以搞個適配器接口做參數轉換。
  • 適配器模式經常是在一個系統或者設計已經定型時候用的,而不是在初始設計時候。一般是因為不影響現在業務情況下,通過適配方式統一接口

十、模板方法模式(Template Pattern)

介紹

模板設計模式是一種行為設計模式,它使用一個抽象類定義了一個模板,這個模板里面定義了一系列的接口,子類則只需要繼承該抽象類并且根據需要重寫一部分接口。

ibatis2 中 AbstractDAOTemplate

image

如圖 AbstractDAOTemplate 是抽象模板類,里面定義了 configure 方法,configure 方法里面定義了好多 protected 方法,其中就有些是抽象方法。類 SpringDAOTemplateIbatisDAOTemplateGenericCIDAOTemplateGenericSIDAOTemplate 則繼承了 AbstractDAOTemplate 類并重寫了一部分方法。

Tomcat 中 Digester 里面的 Rule

Tomcat 中的 Digester 是為了解析 server.xml 的,其中每個元素都有一個解析規則就是 Rule ,類圖如下:

image

如圖:Rule 是抽象類,對于每個解析的節點來說 Rule 提供了解析所需所有的方法,而他的子類則根據自己的特殊性重寫一部分方法來支持自己的特性。

Tomcat 中 Endpoint

image

如圖 AbstractEndpoint 是個抽象類,定義了 Endpoint 的所有接口,然后 JIoEndpoint 繼承了該類并且重寫了一部分重要的方法實現了 BIO 方式 endpoint , NioEndpoint 則重寫了方法實現了 NIOendpoint.

使用場景

當多個子類具有共同的操作流程邏輯,并且其中某些流程節點操作需要自己定制化時候。

十一、建造者模式(Builder Pattern)

介紹

建造者模式是一種創建型模式,將一個復制對象的創建屏蔽到接口內部,用戶使用時候只需要傳遞固定的參數,內部就會執行復雜邏輯后返回會用戶需要的對象,用戶不需要知道創建的細節。

Mybatis 中的 SqlSessionFactoryBuilder

image

如圖 MyBaits 中的 SqlSessionFactoryBuilder 就是典型的創建者模式,他內部有多個 build 方法,根據參數的不同創建出 SqlSessionFactory 對象,使用者只需要傳遞具體參數而不用關系內部是如何創建出需要的對象的。SqlSessionFactoryBean 大家應該很熟悉,在 xml 里面配置的。

使用場景

當一個對象比較復雜并且容易出錯時候,可以考慮這種模式去屏蔽創造細節。

十二、觀察者模式(Observer Pattern)

介紹

觀察者模式是一種行為模式,它定義了當一個對象的狀態或者屬性發生變化時候,通通知其他對這些狀態感興趣的對象。觀察者模式也叫發布-訂閱模式,就是說當你訂閱了摸一個主體時候,如果發布者改變了主題內容,那么所有訂閱這個主體者都會受到通知。

Spring 中 ApplicationListener

image

如圖 黃色部分的 listener 們可以認為是訂閱者,紅色的 context 是發布者,contextIOC 不同狀態會給這些訂閱者發布不同的消息通知訂閱者容器狀態。藍色的為具體的事件(這里為容器不同狀態),其中 ContextRefreshedEventIOC 刷新完成(也就是 bean 解析完成,創建完畢并且 autowired 完成)后的事件這個經常用。
。這里 context 并不是直接來管理黃色的 listener 訂閱者的,而是委托給了綠色的部分,該部分是可以增加刪除訂閱者,并且發布事件給訂閱者。

其實 Tomcat 中的 Lifecycle 也是這種機制

使用場景

滿足發布-訂閱條件的,當一個對象狀態或者屬性變化,需要把這種變化通知到訂閱者時候。

十三、命令模式(Command Pattern)

介紹

命令模式是一種行為模式,通過把命令封裝為一個對象,命令發送者把命令對象發出后,就不去管是誰來接受處理這個命令,命令接受者接受到命令對象后進行處理,也不用管命令是誰發出的,所以命令模式實現了發送者與接受者之間的解耦,而具體把命令發送給誰還需要一個控制器。

Tomcat 中命令模式

Tomcat 作為一個服務器本身會接受外部大量請求,當一個請求過來后 Tomcat 根據域名去找對應的 host,找到 host 后會根據應用名去找具體的 context(應用),然后具體應用處理請求。對于具體 host 來說他不關心這個請求是誰給的,對應請求來說他不必關心誰來處理,但是兩者是通過 request 封裝請求對象進行關聯起來。

image

Tomcat 中 Connector 作為命令發出者,Connector 接受到請求后把請求內容封裝為 request 對象(命令對象),然后使用 CoyoteAdapter 作為分發器把請求具體發配到具體的 host, host 在根據 request 對象找到具體的 context,至此找到了具體的應用,交給具體應用處理。

另外對于使用 SpringMVC 的應用來說,上面找到具體應用,但是具體交給那個 controller 來處理那,這是不是也是命令模式的使用那。

使用場景

當事件發送者和接受者直接需要完全解耦(直接并不存在引用關系)時候。

總結

設計模式中每一個模式都描述了在我們工作中不斷重復發生的問題,以及問題的解決方案,所以真正掌握設計模式可以避免我們做不必要的重復勞動。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容