Design patterns in Spring Framework

This article is the 4th about design patterns used in Spring framework. It‘ll present new 3 patterns implemented in this framework.

At the begin, we‘ll discover 2 patterns belonging to the family of structural patterns: adapter and decorator. At the 3rd and the last part, we‘ll talk about creational design pattern which is singleton.

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);
  }
}

Spring design pattern - adapter

Adapter design pattern used when we need to adapt the interface to given situation without modifying its behavior. It means that we‘ll change used object without changing mechanism before invoking this object. To illustrate it in real world, imagine the situation when you want to make a hole with a drill. To make a small hole, you‘ll use small drill bit and to make a big one, big drill bit. You can see it in below code:

This code will print:

Small hole is made byt SmallDrillBit
Small hole is made byt SmallDrillBit
Big hole is made byt BigDrillBit
Big hole is made byt BigDrillBit

As you can see, the hole is made with adapted DrillBit object. If the hole‘s diameter is smaller than 10, we use SmallDrillBit. If it‘s bigger, we use BigDrillBit.

Spring uses adapter design pattern to handle load-time-weaving in different servlet containers. Load-time-weaving is used in Aspect-Oriented Programming (AOP) to inject AspectJ‘s aspects to byte code during class loading. Another ways to inject these aspects are compile-time injection or static injection on already compiled classes.

A good illustration of this is the case of JBoss, included in packageorg.springframework.instrument.classloading.jboss. We retrieve there JBossLoadTimeWeaver class responsible for weaving management for JBoss container. However, the class loader is different for JBoss 6 (uses JBossMCAdapter instance) and JBoss 7/8 (uses JBossModulesAdapter instance). Depending to JBoss version, we initialize corresponding adapter in JBossLoadTimeWeaver constructor (exactly as for Drill‘s constructor in our example):

  1. public JBossLoadTimeWeaver(ClassLoader classLoader) {
  2. private final JBossClassLoaderAdapter adapter;
  3. Assert.notNull(classLoader, "ClassLoader must not be null");
  4. if (classLoader.getClass().getName().startsWith("org.jboss.modules")) {
  5. // JBoss AS 7 or WildFly 8
  6. this.adapter = new JBossModulesAdapter(classLoader);
  7. }
  8. else {
  9. // JBoss AS 6
  10. this.adapter = new JBossMCAdapter(classLoader);
  11. }
  12. }

Further, this adapter instance is used to make weaving operations depending on running servlet container version:

  1. @Override
  2. public void addTransformer(ClassFileTransformer transformer) {
  3. this.adapter.addTransformer(transformer);
  4. }
  5. @Override
  6. public ClassLoader getInstrumentableClassLoader() {
  7. return this.adapter.getInstrumentableClassLoader();
  8. }

Spring design pattern - decorator

The second design pattern described here looks similar to adapter. It‘s decorator. The main role if this design pattern is to add supplementary role to given object. In the real world, the illustration of this pattern should be a coffee. Usually black and strong, you can add ("decorate" it with) sugar and milk to get coffee less stronger. Coffee is here decorated object and sugar with milk are the decorators. Below you can find an example showing this coffee decoration:

  1. public class DecoratorSample {
  2. @Test
  3. public void test() {
  4. Coffee sugarMilkCoffee=new MilkDecorator(new SugarDecorator(new BlackCoffee()));
  5. assertEquals(sugarMilkCoffee.getPrice(), 6d, 0d);
  6. }
  7. }
  8. // decorated
  9. abstract class Coffee{
  10. protected int candied=0;
  11. protected double price=2d;
  12. public abstract int makeMoreCandied();
  13. public double getPrice(){
  14. return this.price;
  15. }
  16. public void setPrice(double price){
  17. this.price+=price;
  18. }
  19. }
  20. class BlackCoffee extends Coffee{
  21. @Override
  22. public int makeMoreCandied(){
  23. return 0;
  24. }
  25. @Override
  26. public double getPrice(){
  27. return this.price;
  28. }
  29. }
  30. // abstract decorator
  31. abstract class CoffeeDecorator extends Coffee{
  32. protected Coffee coffee;
  33. public CoffeeDecorator(Coffee coffee){
  34. this.coffee=coffee;
  35. }
  36. @Override
  37. public double getPrice(){
  38. return this.coffee.getPrice();
  39. }
  40. @Override
  41. public int makeMoreCandied(){
  42. return this.coffee.makeMoreCandied();
  43. }
  44. }
  45. // concrete decorators
  46. class MilkDecorator extends CoffeeDecorator{
  47. public MilkDecorator(Coffee coffee){
  48. super(coffee);
  49. }
  50. @Override
  51. public double getPrice(){
  52. return super.getPrice()+1d;
  53. }
  54. @Override
  55. public int makeMoreCandied(){
  56. return super.makeMoreCandied()+1;
  57. }
  58. }
  59. class SugarDecorator extends CoffeeDecorator{
  60. public SugarDecorator(Coffee coffee){
  61. super(coffee);
  62. }
  63. @Override
  64. public double getPrice(){
  65. return super.getPrice()+3d;
  66. }
  67. @Override
  68. public int makeMoreCandied(){
  69. return super.makeMoreCandied()+1;
  70. }
  71. }

This sample of decorator is based on invocations of parent methods which change the final property (price and candied level in our case). In Spring we retrieve decorator design pattern in a class handling cache synchronization with Spring-managed transactions. This class isorg.springframework.cache.transaction.TransactionAwareCacheDecorator.

Which characteristics of this class prove that it‘s a decorator for org.springframework.cache.Cache object ? First of all, exactly as in our coffee example, the TransactionAwareCacheDecorator‘s constructor takes in parameter decorated object (Cache):

  1. private final Cache targetCache;
  2. /**
  3. * Create a new TransactionAwareCache for the given target Cache.
  4. * @param targetCache the target Cache to decorate
  5. */
  6. public TransactionAwareCacheDecorator(Cache targetCache) {
  7. Assert.notNull(targetCache, "Target Cache must not be null");
  8. this.targetCache = targetCache;
  9. }

Secondly, a new behavior is added to decorated Cache. As we can read in the comment of TransactionAwareCacheDecorator, the main purpose of it is to provide synchronization level between cache and Spring transactions. It‘s achieved thanks to org.springframework.transaction.support.TransactionSynchronizationManager in two of Cache methods: put and evict:

  1. @Override
  2. public void put(final Object key, final Object value) {
  3. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  4. TransactionSynchronizationManager.registerSynchronization(
  5. new TransactionSynchronizationAdapter() {
  6. @Override
  7. public void afterCommit() {
  8. targetCache.put(key, value);
  9. }
  10. });
  11. }
  12. else {
  13. this.targetCache.put(key, value);
  14. }
  15. }
  16. @Override
  17. public void evict(final Object key) {
  18. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  19. TransactionSynchronizationManager.registerSynchronization(
  20. new TransactionSynchronizationAdapter() {
  21. @Override
  22. public void afterCommit() {
  23. targetCache.evict(key);
  24. }
  25. });
  26. }
  27. else {
  28. this.targetCache.evict(key);
  29. }
  30. }

This pattern looks similar to adapter, isn‘t it ? However, they‘re both different. As we can see, adapter adapts the object to runtime environment, ie. if we run in JBoss 6, we use different class loader than in JBoss 7. Decorator works every time with the same main object (Cache) and only adds new behaviour to it (as synchronization with Spring transactions in our example).

Spring design pattern - singleton

Now, it‘s the turn of the very popular design pattern, singleton. As we‘ve already explained in the article aboutsingleton and prototype beans in Spring Framework, singleton is considered as one from several bean scopes. This scope creates only one instance of given bean in each application context. Unlike signleton design pattern, Spring limits the number of instances to application context. Singleton‘s design pattern in Java applications limits the quantity of these instances to a whole space managed by given class loader. It means that you can use the same classloader for two Spring‘s context and retreive two singleton-scoped beans.

Before diving to Spring singletons, let‘s take a look on Java‘s singleton example:

  1. public class SingletonTest {
  2. @Test
  3. public void test() {
  4. President president1 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
  5. President president2 = (President) SingletonsHolder.PRESIDENT.getHoldedObject();
  6. assertTrue("Both references of President should point to the same object", president1 == president2);
  7. System.out.println("president1 = "+president1+" and president2 = "+president2);
  8. // sample output
  9. // president1 = [email protected] and president2 = [email protected]
  10. }
  11. }
  12. enum SingletonsHolder {
  13. PRESIDENT(new President());
  14. private Object holdedObject;
  15. private SingletonsHolder(Object o) {
  16. this.holdedObject = o;
  17. }
  18. public Object getHoldedObject() {
  19. return this.holdedObject;
  20. }
  21. }
  22. class President {
  23. }

This test case proves that only one instance of President holded by SingletonsHolder exists. In Spring, we can find the concept of singletons in bean factory (for example inorg.springframework.beans.factory.config.AbstractFactoryBean):

  1. /**
  2. * Expose the singleton instance or create a new prototype instance.
  3. * @see #createInstance()
  4. * @see #getEarlySingletonInterfaces()
  5. */
  6. @Override
  7. public final T getObject() throws Exception {
  8. if (isSingleton()) {
  9. return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
  10. }
  11. else {
  12. return createInstance();
  13. }
  14. }

We see that when demanded object is treated like singleton, it‘s initialized only once and returned after every time with the same instance of bean‘s class. We can see it in given example, similar to our President case seen previously. Tested bean is defined as:

  1. <bean id="shoppingCart" class="com.waitingforcode.data.ShoppingCart" />

And the test case looks like:

  1. public class SingletonSpringTest {
  2. @Test
  3. public void test() {
  4. // retreive two different contexts
  5. ApplicationContext firstContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");
  6. ApplicationContext secondContext = new FileSystemXmlApplicationContext("applicationContext-test.xml");
  7. // prove that both contexts are loaded by the same class loader
  8. assertTrue("Class loaders for both contexts should be the same",
  9. firstContext.getClassLoader() == secondContext.getClassLoader());
  10. // compare the objects from different contexts
  11. ShoppingCart firstShoppingCart = (ShoppingCart) firstContext.getBean("shoppingCart");
  12. ShoppingCart secondShoppingCart = (ShoppingCart) secondContext.getBean("shoppingCart");
  13. assertFalse("ShoppingCart instances got from different application context shouldn‘t be the same",
  14. firstShoppingCart == secondShoppingCart);
  15. // compare the objects from the same context
  16. ShoppingCart firstShoppingCartBis = (ShoppingCart) firstContext.getBean("shoppingCart");
  17. assertTrue("ShoppingCart instances got from the same application context should be the same",
  18. firstShoppingCart == firstShoppingCartBis);
  19. }
  20. }

This test case shows the main difference between singletons in Spring and purely design pattern singletons. Despite the same class loader used to load two application contexts, the instances of ShoppingCart aren‘t the same. But when we compare the instances retrieved twice and belonging to the same context, we perceive that they‘re equal.

Design patterns described in this article are used by Spring to handle bean creation. Thanks to singleton, Spring can control that only one instance of so specified bean is available in each application context. Thanks to adapter, Spring decides which layer should be used to handle load-time weaving in JBoss servlet container. The third design pattern, decorator, is used to add synchronization features to Cache objects.

时间: 2024-08-05 13:48:10

Design patterns in Spring Framework的相关文章

Spring Framework Ecosystem – Introduction to Spring Projects

来自于:http://springtutorials.com/spring-ecosystem/ Hello and Welcome to Spring Tutorials Blog! Is it fair to assume you have at least heard of Spring Framework official website – spring.io? If not, I would recommend that you check it out. There are som

Learning JavaScript Design Patterns The Module Pattern

The Module Pattern Modules Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized. In JavaScript, there are several options for impleme

Design Patterns Example Code (in C++)

Overview Design patterns are ways to reuse design solutions that other software developers have created for common and recurring problems. The design patterns on this page are from the book Design Patterns, Elements of Reusable Object-Oriented Softwa

23中设计模式(Design Patterns)转载

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周

Spring Framework介绍

http://zh.wikipedia.org/wiki/Spring_Framework Spring Framework 是一个开源的Java/Java EE全功能栈(full-stack)的应用程序框架,以Apache许可证形式发布,也有.NET平台上的移植版本.该框架基于 Expert One-on-One Java EE Design and Development(ISBN 0-7645-4385-7)一书中的代码,最初由 Rod Johnson 和 Juergen Hoeller等

Learning JavaScript Design Patterns -- A book by Addy Osmani

Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.6.2 Tweet Copyright © Addy Osmani 2015. Learning JavaScript Design Patterns is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. It

[Design Patterns] 3. Software Pattern Overview

When you're on the way which is unknown and dangerous, just follow your mind and steer the boat. 软件模式: 设计模式.体系结构模式.分析模式.过程模式等. 体系结构模式 ANSIIEEEStd1471一200对体系结构的定义:一个系统的基本组织,表现为系统的组件.组件之间的相互关系.组件和环境之间的相互关系以及设计和进化的原则. 黑板模式 黑板模式是一种常用的架构模式,应用中的多种不同数据处理逻辑相

设计模式(Design Patterns)(转)

设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代 码可靠性. 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样.项目中合理的运用 设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我

Notes on Design Patterns

一些笔记. strategy : facilitates the switch of the different but related algorithms/behaviors observer proxy : controls the access to the real subject ; shares the common interface with the real subject and sometimes provides a subset of the operations i