这篇文章是该系列关于spring框架设计模式文章的第四篇,会继续介绍3个框架使用的设计模式。
最开始,我们会介绍两个结构设计模式:适配器模式adapter
和装饰模式decorator
。第三个,也就是本文最后一部分,我们会介绍一个对象创建设计模式:单例singleton
。
Spring设计模式 - 适配器adapter
适配器模式应用在当我们需要将接口适配到特定的环境而且不需要修改接口自身行为的场合。这意味着在调用一个对象之前我们采用了一个非修改机制修改了所使用的对象。要在现实世界中描述这一点,想像一下你要用钻打一个洞的场景。要打一个小洞,你要使用小钻头,而大洞需要大钻头。用下面的代码演示这一点:
public class AdapterTest {
public static void main(String[] args) {
HoleMaker maker = new HoleMakerImpl();
maker.makeHole(1);
maker.makeHole(2);
maker.makeHole(30);
maker.makeHole(40);
}
}
interface HoleMaker {
public void makeHole(int diameter);
}
interface DrillBit {
public void makeSmallHole();
public void makeBigHole();
}
// Two adaptee objects
class BigDrillBit implements DrillBit {
@Override
public void makeSmallHole() {
// do nothing
}
@Override
public void makeBigHole() {
System.out.println("Big hole is made byt WallBigHoleMaker");
}
}
class SmallDrillBit implements DrillBit {
@Override
public void makeSmallHole() {
System.out.println("Small hole is made byt WallSmallHoleMaker");
}
@Override
public void makeBigHole() {
// do nothing
}
}
// Adapter class
class Drill implements HoleMaker {
private DrillBit drillBit;
public Drill(int diameter) {
drillBit = getMakerByDiameter(diameter);
}
@Override
public void makeHole(int diameter) {
if (isSmallDiameter(diameter)) {
drillBit.makeSmallHole();
} else {
drillBit.makeBigHole();
}
}
private DrillBit getMakerByDiameter(int diameter) {
if (isSmallDiameter(diameter)) {
return new SmallDrillBit();
}
return new BigDrillBit();
}
private boolean isSmallDiameter(int diameter) {
return diameter < 10;
}
}
// Client class
class HoleMakerImpl implements HoleMaker {
@Override
public void makeHole(int diameter) {
HoleMaker maker = new Drill(diameter);
maker.makeHole(diameter);
}
}
这段代码会输出:
Small hole is made byt SmallDrillBit
Small hole is made byt SmallDrillBit
Big hole is made byt BigDrillBit
Big hole is made byt BigDrillBit
你可以看到,打洞使用了适配对象DrillBit
。如果要打的洞的直径小于10,使用SmallDrillBit
,否则我们使用BigDrillBit
。
Spring使用了适配器模式来处理不同的servlet容器中加载时织入( load-time-weaving)。加载时织入被用在AOP中在类加载时用来注入AspectJ方面到字节码。其他的方面注入方法有编译时注入或者编译后类上的静态注入。
描述这种情况的一个比较好的例子是JBoss,在包org.springframework.instrument.classloading.jboss
里面。我们在这里抽取了JBossLoadTimeWeaver
类用于负责JBoss容器的织入管理。然而,JBoss6(使用JBossMCAdapter 实例)和JBoss7/8(使用JBossModulesAdapter 实例)的类加载器是不同的。根据JBoss版本的不同,我们在JBossLoadTimeWeaver
构造函数中初始化相应的适配器(就像我们上面的例子中选钻头的例子一样):
public JBossLoadTimeWeaver(ClassLoader classLoader) {
private final JBossClassLoaderAdapter adapter;
Assert.notNull(classLoader, "ClassLoader must not be null");
if (classLoader.getClass().getName().startsWith("org.jboss.modules")) {
// JBoss AS 7 or WildFly 8
this.adapter = new JBossModulesAdapter(classLoader);
}
else {
// JBoss AS 6
this.adapter = new JBossMCAdapter(classLoader);
}
}
更进一步,这个适配器实例根据所运行的servlet容器的版本被用来做织入操作:
@Override
public void addTransformer(ClassFileTransformer transformer) {
this.adapter.addTransformer(transformer);
}
@Override
public ClassLoader getInstrumentableClassLoader() {
return this.adapter.getInstrumentableClassLoader();
}
Spring设计模式 - 装饰器decorator
这里我们要看的第二种设计模式跟适配器模式看起来很像。它是装饰器模式。该模式的主要角色是向指定对象补充增加功能。现实世界中,该模式可以用咖啡来描述。通常黑咖啡口感很浓烈,你可以加(装饰)点糖和牛奶来淡化咖啡口感。这里咖啡就是被装饰的对象,而糖和牛奶就是装饰器。下面你可以看到这个咖啡装饰的例子:
public class DecoratorSample {
@Test
public void test() {
Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
}
}
// decorated
abstract class Coffee{
protected int candied=0;
protected double price=2d;
public abstract int makeMoreCandied();
public double getPrice(){
return this.price;
}
public void setPrice(double price){
this.price+=price;
}
}
class BlackCoffee extends Coffee{
@Override
public int makeMoreCandied(){
return 0;
}
@Override
public double getPrice(){
return this.price;
}
}
// abstract decorator
abstract class CoffeeDecorator extends Coffee{
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee){
this.coffee=coffee;
}
@Override
public double getPrice(){
return this.coffee.getPrice();
}
@Override
public int makeMoreCandied(){
return this.coffee.makeMoreCandied();
}
}
// concrete decorators
class MilkDecorator extends CoffeeDecorator{
public MilkDecorator(Coffee coffee){
super(coffee);
}
@Override
public double getPrice(){
return super.getPrice()+1d;
}
@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}
class SugarDecorator extends CoffeeDecorator{
public SugarDecorator(Coffee coffee){
super(coffee);
}
@Override
public double getPrice(){
return super.getPrice()+3d;
}
@Override
public int makeMoreCandied(){
return super.makeMoreCandied()+1;
}
}
这个装饰器例子的基础是调用父类不同的用于调整最终属性的方法(这里是价格和糖水平)。Spring中,我们把装饰器设计模式抽取到了一个处理Spring事务管理的缓存同步的类。这个类是org.springframework.cache.transaction.TransactionAwareCacheDecorator
。
这个类的哪些特点证明了它是org.springframework.cache.Cache
对象的装饰器呢?首先,跟在我们的咖啡例子里面的一样,TransactionAwareCacheDecorator
的构造方法在参数中接收被装饰对象(Cache):
private final Cache targetCache;
/**
* Create a new TransactionAwareCache for the given target Cache.
* @param targetCache the target Cache to decorate
*/
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
接下来,一个新的行为被添加到了被装饰的Cache上。正如我们在TransactionAwareCacheDecorator
的注释上所看到的,它的主要目的是提供缓存和Spring事物之间的同步级别。要达到此目的,要感谢org.springframework.transaction.support.TransactionSynchronizationManager
的两个Cache方法:put和evict :
@Override
public void put(final Object key, final Object value) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
targetCache.put(key, value);
}
});
}
else {
this.targetCache.put(key, value);
}
}
@Override
public void evict(final Object key) {
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
targetCache.evict(key);
}
});
}
else {
this.targetCache.evict(key);
}
}
这个模式跟适配器模式很像是不是?然而,它们是不同的。正如我们所看到的,适配器适配对象到运行时环境,比如,假如我们在JBoss 6中运行,我们会使用和JBoss 7不同的类加载器。而装饰器每次都和同一个主对象(Cache)一起工作而且仅向它增加新的行为(在我们的例子中,就像是同步和Spring事务的关系)。
Spring设计模式 - 单例singleton
现在,轮到非常流行的设计模式单例了。正如我们在Spring框架有关单例和原型bean的文章中所解释的,单例被看作是几种bean作用域的一种。这个作用域在应用上下文中对给定的bean仅创建一个实例。跟单例设计模式不同,Spring在应用上下文中限制了实例的数量。而Java应用中的单例设计模式是在整个类加载器的整个管理空间内限制这些实例的数量。这意味着你可以使用同一个类加载器管理两个应用上下文,每个上下文中有某个bean的一个实例,加起来也就是在这个类加载器空间中有两个这样的单例bean实例。
在深入Spring单例之前,我们来看一个Java单例的例子:
public class SingletonTest {
@Test
public void test() {
President president1 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
President president2 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
assertTrue("Both references of President should point to the same object",
president1 == president2);
System.out.println("president1 = "+president1+" and president2 = "+president2);
// 例子输出 :
// president1 = com.waitingforcode.test.President@17414c8
// and president2 = com.waitingforcode.test.President@17414c8
}
}
enum SingletonsHolder {
PRESIDENT(new President());
private Object holdedObject;
private SingletonsHolder(Object o) {
this.holdedObject = o;
}
public Object getHoldedObject() {
return this.holdedObject;
}
}
class President {
}
这个测试用例证明了SingletonsHolder
持有的President
实例一共只有一个。在Spring中,我们可以在bean工厂中发现单例bean的概念(比如在org.springframework.beans.factory.config.AbstractFactoryBean
中):
/**
* Expose the singleton instance or create a new prototype instance.
* @see #createInstance()
* @see #getEarlySingletonInterfaces()
*/
@Override
public final T getObject() throws Exception {
if (isSingleton()) {
return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
}
else {
return createInstance();
}
}
可以看到如果被请求的bean是单例bean,它仅会被初始化一次而随后每次对该bean的访问都返回同样的对象。在所给的例子中,我们可以看到这一点,跟我们之前看到的President的例子很类似。测试bean定义如下:
<bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" />
测试用例如下:
public class SingletonSpringTest {
@Test
public void test() {
// retreive two different contexts
ApplicationContext firstContext =
new FileSystemXmlApplicationContext("applicationContext-test.xml");
ApplicationContext secondContext
= new FileSystemXmlApplicationContext("applicationContext-test.xml");
// prove that both contexts are loaded by the same class loader
assertTrue("Class loaders for both contexts should be the same",
firstContext.getClassLoader() == secondContext.getClassLoader());
// compare the objects from different contexts
ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart");
ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart");
assertFalse("ShoppingCart instances got from different application
context shouldn't be the same",
firstShoppingCart == secondShoppingCart);
// compare the objects from the same context
ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart");
assertTrue("ShoppingCart instances got from the same application context should
be the same",
firstShoppingCart == firstShoppingCartBis);
}
}
这个测试用例展示了Spring单例和纯粹的Java单例设计模式的主要不同。尽管使用了同一个类加载器加载了两个应用上下文,ShoppingCart
的实例却不同。但当我们从同一个应用上下文中两次获取同一个bean类的实例作比较,我们能认识到它们是同一个。
总结
这篇文章中描述的设计模式被Spring用于处理bean的创建。通过单例模式,Spring可以控制每个应用上下文中某个特定类型的bean仅有一个实例。通过适配器模式,Spring决定哪一层会被用于处理JBoss servlet容器的加载时织入。第三种设计模式,装饰器,被用来向缓存Cache对象增加同步功能。
英文原文
该系列文章目录
Spring框架中的设计模式(五)
Spring框架中的设计模式(四)
Spring框架中的设计模式(三)
Spring框架中的设计模式(二)
Spring框架中的设计模式(一)