设计模式有助于遵循良好的编程实践。作为最流行的Web框架之一的Spring框架也使用其中的一些。
本文将介绍Spring Framework中使用的设计模式。这是5篇专题文章的第一部分。这次我们将发现Spring框架中使用的4种设计模式:解释器,构建器,工厂方法和抽象工厂。每部分将首先解释给定模式的原理。紧接着,将会使用Spring的一个例子来加深理解。
在现实世界中,我们人类需要解释手势。他们可以对文化有不同的含义。这是我们的解释,给他们一个意义。在编程中,我们还需要分析一件事情,并决定它是什么意思。我们可以用解释设计模式来做。
此模式基于表达式和评估器部分。第一个代表一个要分析的事情。这个分析是由评价者来做出的,它们知道构成表达的人物的意义。不必要的操作是在一个上下文中进行的。
Spring主要以Spring Expression Language(SpEL)为例。这里快速提个醒,SpEL是一种由Spring的org.springframework.expression.ExpressionParser实现分析和执行的语言。这些实现使用作为字符串给出的Spel表达式,并将它们转换为org.springframework.expression.Expression的实例。上下文组件由org.springframework.expression.EvaluationContext实现表示,例如:StandardEvaluationContext。
举个SpEL的一个例子:
|
输出应打印“Overriden writer’s name”。如你所见,一个对象的属性是通过一个表达式name = #name进行修改的,这个表达式只有在ExpressionParser才能理解,因为提供了context(前面的样例中的modifierContext实例)。
建设者设计模式是属于创建对象模式三剑客的第一种模式。该模式用于简化复杂对象的构造。要理解这个概念,想象一个说明程序员简历的对象。在这个对象中,我们想存储个人信息(名字,地址等)以及技术信息(知识语言,已实现的项目等)。该对象的构造可能如下所示:
|
Builder允许我们通过使用将值传递给父类的内部构建器对象来清楚地分解对象构造。所以对于我们这个程序员简历的对象的创建,构建器可以看起来像:
|
可以看出,构建器后面隐藏了对象构造的复杂性,内部静态类接受链接方法的调用。在Spring中,我们可以在org.springframework.beans.factory.support.BeanDefinitionBuilder类中检索这个逻辑。这是一个允许我们以编程方式定义bean的类。我们可以在关于bean工厂后处理器的文章中看到它,BeanDefinitionBuilder包含几个方法,它们为AbstractBeanDefinition抽象类的相关实现设置值,比如作用域,工厂方法,属性等。想看看它是如何工作的,请查看以下这些方法:
public class BeanDefinitionBuilder { /** * The {@code BeanDefinition} instance we are creating. */ private AbstractBeanDefinition beanDefinition;
// ... some not important methods for this article
// Some of building methods /** * Set the name of the parent definition of this bean definition. */ public BeanDefinitionBuilder setParentName(String parentName) { this.beanDefinition.setParentName(parentName); return this; }
/** * Set the name of the factory method to use for this definition. */ public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) { this.beanDefinition.setFactoryMethodName(factoryMethod); return this; }
/** * Add an indexed constructor arg value. The current index is tracked internally * and all additions are at the present point. * @deprecated since Spring 2.5, in favor of {@link #addConstructorArgValue} */ @Deprecated public BeanDefinitionBuilder addConstructorArg(Object value) { return addConstructorArgValue(value); }
/** * Add an indexed constructor arg value. The current index is tracked internally * and all additions are at the present point. */ public BeanDefinitionBuilder addConstructorArgValue(Object value) { this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue( this.constructorArgIndex++, value); return this; }
/** * Add a reference to a named bean as a constructor arg. * @see #addConstructorArgValue(Object) */ public BeanDefinitionBuilder addConstructorArgReference(String beanName) { this.beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue( this.constructorArgIndex++, new RuntimeBeanReference(beanName)); return this; }
/** * Add the supplied property value under the given name. */ public BeanDefinitionBuilder addPropertyValue(String name, Object value) { this.beanDefinition.getPropertyValues().add(name, value); return this; }
/** * Add a reference to the specified bean name under the property specified. * @param name the name of the property to add the reference to * @param beanName the name of the bean being referenced */ public BeanDefinitionBuilder addPropertyReference(String name, String beanName) { this.beanDefinition.getPropertyValues().add(name, new RuntimeBeanReference(beanName)); return this; }
/** * Set the init method for this definition. */ public BeanDefinitionBuilder setInitMethodName(String methodName) { this.beanDefinition.setInitMethodName(methodName); return this; }
// Methods that can be used to construct BeanDefinition /** * Return the current BeanDefinition object in its raw (unvalidated) form. * @see #getBeanDefinition() */ public AbstractBeanDefinition getRawBeanDefinition() { return this.beanDefinition; }
/** * Validate and return the created BeanDefinition object. */ public AbstractBeanDefinition getBeanDefinition() { this.beanDefinition.validate(); return this.beanDefinition; } } |
创建对象模式三剑客的第二个成员是工厂方法设计模式。它完全适于使用动态环境作为Spring框架。实际上,这种模式允许通过公共静态方法对象进行初始化,称为工厂方法。在这个概念中,我们需要定义一个接口来创建对象。但是创建是由使用相关对象的类创建的。
但是在跳到Spring世界之前,让我们用Java代码做一个例子:
public class FactoryMethodTest {
@Test public void test() { Meal fruit = Meal.valueOf("banana"); Meal vegetable = Meal.valueOf("carrot"); assertTrue("Banana should be a fruit but is "+fruit.getType(), fruit.getType().equals("fruit")); assertTrue("Carrot should be a vegetable but is "+vegetable.getType(), vegetable.getType().equals("vegetable")); }
}
class Meal {
private String type;
public Meal(String type) { this.type = type; }
public String getType() { return this.type; }
// Example of factory method - different object is created depending on current context public static Meal valueOf(String ingredient) { if (ingredient.equals("banana")) { return new Meal("fruit"); } return new Meal("vegetable"); } } |
在Spring中,我们可以通过指定的工厂方法创建bean。该方法与以前代码示例中看到的valueOf方法完全相同。它是静态的,可以采取没有或多个参数。为了更好地了解案例,让我们来看一下实例。首先搞定下配置:
|
现在请关注这个bean的初始化:
public class Welcomer { private String message;
public Welcomer(String message) { this.message = message; }
public static Welcomer createWelcomer(MessageLocator messagesLocator) { Calendar cal = Calendar.getInstance(); String msgKey = "welcome.pm"; if (cal.get(Calendar.AM_PM) == Calendar.AM) { msgKey = "welcome.am"; } return new Welcomer(messagesLocator.getMessageByKey(msgKey)); } } |
当Spring将构造welcomerBean时,它不会通过传统的构造函数,而是通过定义的静态工厂方法createWelcomer来实现。还要注意,这个方法接受一些参数(MessageLocator bean的实例包含所有可用的消息) 标签,通常保留给传统的构造函数。
最后一个,抽象的工厂设计模式,看起来类似于工厂方法。不同之处在于,我们可以将抽象工厂视为这个词的工业意义上的工厂,即。作为提供所需对象的东西。工厂部件有:抽象工厂,抽象产品,产品和客户。更准确地说,抽象工厂定义了构建对象的方法。抽象产品是这种结构的结果。产品是具有同样结构的具体结果。客户是要求创造产品来抽象工厂的人。
同样的,在进入Spring的细节之前,我们将首先通过示例Java代码说明这个概念:
public class FactoryTest {
// Test method which is the client @Test public void test() { Kitchen factory = new KitchenFactory(); KitchenMeal meal = factory.getMeal("P.1"); KitchenMeal dessert = factory.getDessert("I.1"); assertTrue("Meal's name should be 'protein meal' and was '"+meal.getName()+"'", meal.getName().equals("protein meal")); assertTrue("Dessert's name should be 'ice-cream' and was '"+dessert.getName()+"'", dessert.getName().equals("ice-cream")); }
}
// abstract factory abstract class Kitchen { public abstract KitchenMeal getMeal(String preferency); public abstract KitchenMeal getDessert(String preferency); }
// concrete factory class KitchenFactory extends Kitchen { @Override public KitchenMeal getMeal(String preferency) { if (preferency.equals("F.1")) { return new FastFoodMeal(); } else if (preferency.equals("P.1")) { return new ProteinMeal(); } return new VegetarianMeal(); }
@Override public KitchenMeal getDessert(String preferency) { if (preferency.equals("I.1")) { return new IceCreamMeal(); } return null; } }
// abstract product abstract class KitchenMeal { public abstract String getName(); }
// concrete products class ProteinMeal extends KitchenMeal { @Override public String getName() { return "protein meal"; } }
class VegetarianMeal extends KitchenMeal { @Override public String getName() { return "vegetarian meal"; } }
class FastFoodMeal extends KitchenMeal { @Override public String getName() { return "fast-food meal"; } }
class IceCreamMeal extends KitchenMeal { @Override public String getName() { return "ice-cream"; } } |
我们可以在这个例子中看到,抽象工厂封装了对象的创建。对象创建可以使用与经典构造函数一样使用的工厂方法模式。在Spring中,工厂的例子是org.springframework.beans.factory.BeanFactory。通过它的实现,我们可以从Spring的容器访问bean。根据采用的策略,getBean方法可以返回已创建的对象(共享实例,单例作用域)或初始化新的对象(原型作用域)。在BeanFactory的实现中,我们可以区分:ClassPathXmlApplicationContext,XmlWebApplicationContext,StaticWebApplicationContext,StaticPortletApplicationContext,GenericApplicationContext,StaticApplicationContext。
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"file:test-context.xml"}) public class TestProduct { @Autowired private BeanFactory factory; @Test public void test() { System.out.println("Concrete factory is: "+factory.getClass()); assertTrue("Factory can't be null", factory != null); ShoppingCart cart = (ShoppingCart) factory.getBean("shoppingCart"); assertTrue("Shopping cart object can't be null", cart != null); System.out.println("Found shopping cart bean:"+cart.getClass()); } } |
在这种情况下,抽象工厂由BeanFactory接口表示。具体工厂是在第一个System.out中打印的,是org.springframework.beans.factory.support.DefaultListableBeanFactory的实例。它的抽象产物是一个对象。在我们的例子中,具体的产品就是被强转为ShoppingCart实例的抽象产品(Object)。
在这里介绍了通过设计模式来正确组织的我们实现良好的编程风格。在这里,我们可以看到在Spring框架中使用解释器,构建器,工厂方法和工厂。第一个是帮助解释以SpEL表达的文本。三个最后的模式属于创建设计模式的三剑客,它们在Spring中的主要目的是简化对象的创建。他们通过分解复杂对象(构建器)的初始化或通过集中在公共点的初始化来做到对象的创建(要不然怎么叫工厂呢,必须有通用点的)。
面向对象编程(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实例上操作,而是代理它。代理调用第一个BeforeConstructAdvice的before方法(指向在执行目标方法之前执行,在我们的例子中为construct())通过它,给出了一个“权限”来构造对象的字段(house)。代理层提供了一个额外新功能,因为它可以简单地分配给另一个对象。要做到这一点,我们只能在before方法之前修改过滤器。
复合模式
另一种结构模式是复合模式。在关于Spring中设计模式的第一篇文章中,我们使用构建器来构造复杂对象。另一种实现方法是使用复合模式。这种模式是基于具有共同行为的多个对象的存在,用于构建更大的对象。较大的对象仍然具有与最小对象相同的特征。那么用它来定义相同的行为。
复合对象的非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 public void addTag(TextTag tag); }
class PTag implements TextTagComposite { private List
@Override public void startWrite() { System.out.println(" "); }
@Override public void endWrite() { System.out.println(" ");}
@Override public List return tags; }
@Override public void addTag(TextTag tag) { tags.add(tag); } }
class SpanTag implements TextTag {
@Override public void startWrite() { System.out.println(""); }
@Override public void endWrite() { System.out.println(""); }
}
class EmTag implements TextTag {
@Override public void startWrite() { System.out.println(""); }
@Override public void endWrite() { System.out.println(""); }
} |
在这种情况下,可以看到一个复合对象。我们可以区分复合与非复合对象,因为第一个可以容纳一个或多个非复合对象(PTag类中的private List tags字段)。非复合对象称为叶子。TextTag接口被称为组件,因为它为两个对象类型提供了共同的行为规范(有点像Linux文件管理系统的有共同点的文件放在一个文件夹下进行管理,其实就是节点管理)。
在Spring世界中,我们检索复合对象的概念是org.springframework.beans.BeanMetadataElement接口,用于配置bean对象。它是所有继承对象的基本界面。现在,在一方面,我们有一个叶子,由org.springframework.beans.factory.parsing.BeanComponentDefinition表示,另一边是复合org.springframework.beans.factory.parsing.CompositeComponentDefinition。CompositeComponentDefinition类似于组件,因为它包含addNestedComponent(ComponentDefinition component)方法,它允许将叶添加到私有final列表中nestedComponents。您可以看到,由于此列表,BeanComponentDefinition和CompositeComponentDefinition的组件是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 |
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. * NOTE: 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部分并通过复合模式来构造复杂对象。
这篇文章的第一个设计模式是原型。可以通过官方文档查找有关Spring作用域中的bean作用域的文章中介绍了类似的概念(prototype)。原型设计模式与有用相同名称的(prototype)作用域有点相似。此设计模式允许通过复制已存在的对象来创建一个对象的实例。副本应该是真正的副本。这意味着新对象的所有属性应与复制对象的属性相同。如果不清楚,比一个简单的JUnit案例更好的说明:
public class PrototypeTest { |
在Spring中,在org.springframework.beans.factory.support.AbstractBeanFactory中使用一种特定的原型设计模式,它将初始化bean原型作用域。新对象基于配置文件中的bean定义。我们可以看到,在给定的例子中:
|
@RunWith(SpringJUnit4ClassRunner.class) |
从前面的例子可以看出,ShoppingCart实例是直接从bean定义创建的。最初,cart1和cart2对象的id值为9.它在测试结束时被修改,以证明两个引用都属于两个不同的对象。
对象池
Spring中使用的另一个模型是对象池设计模式。其主要目的在于在一个池中保存特定数量的对象,并根据需要重新使用。通过它,我们可以改善我们想要使用巨型对象的响应时间。巨型意味着这些对象的构造需要很多时间(例如:持有数据库连接的对象),最好重用已经存在的和未获取的对象,而不是创建新对象。
Spring还使用线程池来管理其调度部分。一些示例位于org.springframework.scheduling.concurrent中。我们检索数据库(Spring JDBC)项目中的对象池的想法。数据库连接池不是由Spring直接实现的,而是适用于Spring工作方式的项目,如C3P0或Jakarta Commons DBCP连接池。
这里呈现的最后一个设计模式是观察者。当一个或几个课程正在等待具体事件时可以使用它。观察者模式由一个科目和观察员名单组成。一个很好的例子就是GUI界面,其中点击按钮(按钮是主题)会引起听众(观察者)启动的一些操作(再说的直白点就是电影院一场电影这个subject,需要观众(也就是观察者咯),电影产生的一些画面产生的事件,比如恐怖 电影给男人女人带来的不同的感官的感受,传播到观察者也就是观众的眼里所带来的不一样的反应,这个中间一般会添加一个事件传播者,在后面解释Spring的例子的时候会说到),例如:打开一个新页面这个动作。可以参考下面的例子:
public class ObserverTest { |
可以看到,关于我们的Button实例点击的事件被发送到所有的观察者对象。从这些对象开始下载页面内容,第二个将在事件的信息保存在注册表中。在Spring中,观察者设计模式用于将与应用程序上下文相关的事件传输到org.springframework.context.ApplicationListener的实现。要了解它们的实现方法,我们来看一下AbstractApplicationContext类(老版本的代码,新版本的请自行对照):
public abstract class AbstractApplicationContext extends DefaultResourceLoader |
在提供的代码中,监听器在内部添加到应用程序上下文类中,并且在registerListeners()方法之后,它们被注册到由接口org.springframework.context.event.ApplicationEventMulticaster表示的适当的事件多路广播器(因为有很多listeners)。EventMulticaster负责管理不同的listener和向他们发布事件。
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster { |
这次我们讲3种设计模式:用于在同一个调用作用域内创建bean的原型,避免重新创建巨型对象的对象池,以及将应用程序的上下文事件分派给适当的监听器的观察者。
当我们需要在给定场景下(也就是给定接口)想要不改变自身行为而又想做到一些事情的情况下(就是我给电也就是接口了,你来做事也就是各种电器),使用适配器设计模式(这里再说一点,就相当于我们再一个规章制度的环境下,如何去适应并达到我们期待的效果,放在架构设计这里,可以拿一个php系统和一个Java系统来说,假如两者要互相调用对方的功能,我们可以设计一套对外的api来适配)。这意味着在调用此对象之前,我们将更改使用对象而不改变机制。拿一个现实中的例子进行说明,想象一下你想要用电钻来钻一个洞。要钻一个小洞,你会使用小钻头,钻一个大的需要用大钻头。可以看下面的代码:
public class AdapterTest { |
以上代码的结果如下:
Small hole is made byt SmallDrillBit |
可以看到,hole 是由所匹配的DrillBit对象制成的。如果孔的直径小于10,则使用SmallDrillBit。如果它更大,我们使用BigDrillBit。
思路就是,要打洞,那就要有打洞的工具,这里提供一个电钻接口和钻头。电钻就是用来打洞的,所以,它就一个接口方法即可,接下来定义钻头的接口,无非就是钻头的尺寸标准,然后搞出两个钻头实现类出来,接下来就是把钻头和电钻主机组装起来咯,也就是Drill类,里面有电钻接口+钻头(根据要钻的孔大小来确定用哪个钻头),其实也就是把几个单一的东西组合起来拥有丰富的功能,最后我们进行封装下:HoleMakerImpl,这样只需要根据尺寸就可以打相应的孔了,对外暴露的接口极为简单,无须管内部逻辑是多么复杂
Spring使用适配器设计模式来处理不同servlet容器中的加载时编织(load-time-weaving)。在面向切面编程(AOP)中使用load-time-weaving,一种方式是在类加载期间将AspectJ的方面注入字节码。另一种方式是对类进行编译时注入或对已编译的类进行静态注入。
我们可以从关于Spring和JBoss的处理接口这里找到一个很好的例子,它包含在org.springframework.instrument.classloading.jboss包中。我们检索JBossLoadTimeWeaver类负责JBoss容器的编织管理。然而,类加载器对于JBoss 6(使用JBossMCAdapter实例)和JBoss 7/8(使用JBossModulesAdapter实例)是不同的。根据JBoss版本,我们在JBossLoadTimeWeaver构造函数中初始化相应的适配器(与我们示例中的Drill的构造函数完全相同):
public JBossLoadTimeWeaver(ClassLoader classLoader) { |
而且,此适配器所创建的实例用于根据运行的servlet容器版本进行编织操作:
@Override |
总结:适配器模式,其实就是我们用第一人称的视角去看世界,我想拓展我自己的技能的时候,就实行拿来主义,就好比这里的我是电钻的视角,那么我想拥有钻大孔或者小孔的功能,那就把钻头拿到手组合起来就好。
和装饰模式的区别:装饰模式属于第三人称的视角,也就是上帝视角!我只需要把几个功能性的组件给拿到手,进行组合一下,实现一个更加niubility的功能这里提前说下,这样看下面的内容能好理解些。下面解释装饰模式
这里描述的第二种设计模式看起来类似于适配器。它是装饰模式。这种设计模式的主要作用是为给定的对象添加补充角色。举个现实的例子,就拿咖啡来讲。通常越黑越苦,你可以添加(装饰)糖和牛奶,使咖啡不那么苦。咖啡在这里被装饰的对象,糖与牛奶是用来装饰的。可以参考下面的例子:
public class DecoratorSample { |
上面这个简单的装饰器的小例子是基于对父方法的调用,从而改变最后的属性(我们这里是指价格和加糖多少)。在Spring中,我们在处理与Spring管理缓存同步事务的相关类中可以 发现装饰器设计模式的例子。这个类是org.springframework.cache.transaction.TransactionAwareCacheDecorator。
这个类的哪些特性证明它是org.springframework.cache.Cache对象的装饰器?首先,与我们的咖啡示例一样,TransactionAwareCacheDecorator的构造函数接收参数装饰对象(Cache):
private final Cache targetCache; |
其次,通过这个对象,我们可以得到一个新的行为:为给定的目标缓存创建一个新的TransactionAwareCache。这个我们可以在TransactionAwareCacheDecorator的注释中可以阅读到,其主要目的是提供缓存和Spring事务之间的同步级别。这是通过org.springframework.transaction.support.TransactionSynchronizationManager中的两种缓存方法实现的:put 和 evict(其实最终不还是通过targetCache来实现的么):
@Override |
这种模式看起来类似于适配器,对吧?但是,它们还是有区别的。我们可以看到,适配器将对象适配到运行时环境,即。如果我们在JBoss 6中运行,我们使用与JBoss 7不同的类加载器。Decorator每次使用相同的主对象(Cache)工作,并且仅向其添加新行为(与本例中的Spring事务同步),另外,可以通过我在解读这个设计模式之前的说法来区分二者。
我们再以springboot的初始化来举个例子的,这块后面会进行仔细的源码分析的,这里就仅仅用设计模式来说下的:
/** |
从注释可以看出 ApplicationListener要先行到位的,然后就是started的时候Event published走起,接着就是Environment配置好,ApplicationContext进行初始化完毕,那我们去看ApplicationListener的源码:
/** |
看类注释我们可以知道,需要实现此接口内所定义的这几个方法,ok,来看个实现类:
/** |
从上可以看出,EventPublishingRunListener里对接口功能的实现,主要是通过SpringApplication ApplicationEventMulticaster 来实现的,自己不干活,挂个虚名,从上帝模式的角度来看,这不就是应用了装饰模式来实现的么。
更多源码解析请关注后续的本人对Spring框架全面的重点部分解析系列博文
单例,我们最常用的设计模式。正如我们在很多Spring Framework中关于单例和原型bean的文章(网上太多了)中已经看到过的,单例是几个bean作用域中的中的一个。此作用域在每个应用程序上下文中仅创建一个给定bean的实例。与signleton设计模式有所区别的是,Spring将实例的数量限制的作用域在整个应用程序的上下文。而Singleton设计模式在Java应用程序中是将这些实例的数量限制在给定类加载器管理的整个空间中。这意味着我们可以为两个Spring的上下文(同一份配置文件起两个容器,也就是不同端口的容器实例)使用相同的类加载器,并检索两个单例作用域的bean。
在看Spring单例应用之前,让我们来看一个Java的单例例子:
public class SingletonTest { |
这个测试例子证明,只有一个由SingletonsHolder所持有的President实例。在Spring中,我们可以在bean工厂中找到单例应用的影子(例如在org.springframework.beans.factory.config.AbstractFactoryBean中):
/** |
我们看到,当需求对象被视为单例时,它只被初始化一次,并且在每次使用同一个bean类的实例后返回。我们可以在给定的例子中看到,类似于我们以前看到的President情况。将测试bean定义为:
<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" /> |
测试用例如下所示:
public class SingletonSpringTest { |
这个测试案例显示了Spring单例模式与纯粹的单例设计模式的主要区别。尽管使用相同的类加载器来加载两个应用程序上下文,但是ShoppingCart的实例是不一样的。但是,当我们比较两次创建并属于相同上下文的实例时,我们认为它们是相等的。
也正因为有了单例,Spring可以控制在每个应用程序上下文中只有一个这样指定的bean的实例可用。因为适配器,Spring可以决定使用由谁来处理JBoss servlet容器中的加载时编织,也可以实现ConfigurableListableBeanFactory的相应实例。第三种设计模式,装饰器,用于向Cache对象添加同步功能,还有Springboot的容器初始化。
其实对于适配器和装饰者确实有太多的相似的地方,一个是运行时选择,一个是加料组合产生新的化学效应,还有从看待事物的角度不同得到不同的行为,适配适配,更注重面向接口的实现,而内部又根据不同情况调用面向一套接口的多套实现的实例的相应方法来实现所要实现的具体功能,装饰者更注重添油加醋,通过组合一些其他对象实例来让自己的功能实现的更加华丽一些(达到1+1>2的这种效果)。
这篇文章描述的第一个行为设计模式是命令。它允许将请求封装在一个对象内并附加一个回调动作(每次遇到所所谓的回调大家就只需要理解为一个函数方法就好,省的去浪费那么多脑子)。请求被封装在命令对象之下,而请求的结果被发送到接收者。命令本身不是由调用者执行。为了直白了解其中的主要思想,想象一下管理服务器的情况(远程通过ssh操作Linux服务器)。管理员(invoker)在命令行(commands)中启动一些操作,将结果发送到服务器(接收器)。在这里,所有这一切都是由客户端的终端(也就是我们用的xshell)来完成的。搞个Demo来说明一下(对于命令,它的动作就是执行,对于管理员来讲,我们的动作其实就是一个回车,执不执行当然是管理员说的算了,执行交给命令对象了,服务器最后就是一个展示结果):
public class CommandTest { |
测试应通过并打印两个命令:
Executing: sudo service apache2 start on server |
命令模式不仅允许封装请求(ServerCommand)并将其传输到接收器(Server),而且还可以更好地处理给定的请求。在这里,这种更好的处理是通过存储命令的执行历史。在Spring中,我们在beanFactory后置处理器的特性中来找到指令设计模式的原理。要通过快速对它们进行定义,应用程序上下文会启动后置处理器,并可以用来对创建的bean进行一些操作(这里不打算细说了,具体的我后面会专门写一篇这方面的文章,来分析其中的源码细节)。
当我们将先前Demo里呈现的命令逻辑转换并对比到Spring bean工厂后处理器时,我们可以区分以下actors:后置处理器bean(是指实现BeanFactoryPostProcessor接口)是命令,org.springframework.context.support.PostProcessorRegistrationDelegate是调用者(它执行postProcessBeanFactory方法注册所有的后置处理器bean,此处看下面第二段代码)和接收器org.springframework.beans.factory.config.ConfigurableListableBeanFactory可以在元素(bean)构造初始化之前修改它们(例如:在初始化bean之前可以更改属性)。
另外,回顾下上面的那个Demo,和我们的Demo中的命令历史管理一样。PostProcessorRegistrationDelegate包含一个内部类BeanPostProcessorChecker,它可以记录当一个bean不符合处理条件的情况。
可以观察PostProcessorRegistrationDelegate中的两段代码:
/** |
定义后的调用,用的就是ConfigurableListableBeanFactory的实例(看BeanPostProcessorChecker注释):
public static void registerBeanPostProcessors( |
总结一个过程就是,我要BeanFactory里面得到对象(也就是为了得到一个命令的执行结果),那么,想要在得到对象的时候就已经实现了一些对其修改的想法,那么就通过后置处理器,也是就实现了后置处理器接口的beans(命令里可以通过传入不同的参数来得到不同结果,或者对命令的脚本进行修改),然后还需要一个执行者(我们在做自动化运维的时候,不止操作一个脚本,这里的PostProcessorRegistrationDelegate就是集中来管理这些的),最后得到的结果就由BeanFactory来展示咯。
接下来要介绍的一个行为设计模式是Visitor:抽象一点就是通过另一种类型的对象来使一个对象访问。在这个简短定义中,使用这个设计模式中的对象将被视为访问者或对象可被访问。第一个访问者要有可访问支持。这个模式的一个现实的例子可以是一个汽车质检员,他们检查一些汽车零件,比如轮子,制动器和发动机,以判断汽车质量是否合格。我们来做个JUnit测试用例:
public class VisitorTest { |
在这个例子中,我们可以看到他们有两个机制(访问者,其实就是免检和不免检):合格和不合格。暴露于他们的可见对象是汽车。通过其接受方式,决定哪个角色应该适用于被访问者(通过代码mechanic.getName().equals("qualified")来判断)。当访问者合格时,Car让他分析所有组件。如果访问者不合格,Car认为其干预是无用的,并且在方法isBroken()中直接返回false(其实就是为了达到一个免检的效果)。Spring在beans配置中实现了访问者设计模式。为了观察,我们可以看看org.springframework.beans.factory.config.BeanDefinitionVisitor对象,该对象用于解析bean元数据并将其解析为String(例如:具有作用域或工厂方法名称的XML属性)或Object(例如:构造函数定义中的参数)。已解析的值在与分析的bean关联的BeanDefinition实例中进行判断设置。具体的源码请看BeanDefinitionVisitor的代码片段:
/** |
在这种情况下,他们只是访问方式,没有对访问者做任何补充的控制(在Demo里对car的质检员做了控制)。这里访问包括分析给定BeanDefinition的参数,并将其替换为已解析对象。
在最后一篇关于Spring中设计模式的文章中,我们发现了2种行为模式:用于处理bean工厂的后置处理的命令模式和用于将定义的bean参数转换为面向对象(String或Object的实例)参数的访问者模式。