Design Patterns in Spring Framework (1)

Design patterns help developers follow programming best practices. As the most popular web framework, Spring also uses design patterns.

This article will explain which design patterns the Spring framework uses and how to use them. This topic article is divided into 5 parts, this is the first part. In this part we will cover the four design patterns used by the Spring framework: interpreter interpreted, generator builder, factory method factory methodand abstract factory abstract factory. Each part will first explain the basic principles of the pattern, and then give an example of use in Spring.

Spring Design Patterns - Interpreterinterpreter

In the real world we humans need to interpret gestures. Gestures have different meanings depending on the culture. Our interpretation gives them specific meanings. In programming we also need to interpret a thing and decide what it means. This is where the interpreterinterpreted design pattern .

The pattern builds on expressions ( expression) and evaluators ( evaluator). The former expression ( expression) represents a thing that needs to be analyzed. The latter evaluator ( evaluator) knows the meaning of the characters that make up the expression ( expression) and then analyzes it. This understanding process takes place in a context ( context).

Spring mainly uses the interpreter pattern in the Spring Expression Language (SpEL).

As a quick reminder here, SpEL is an expression language, org.springframework.expression.ExpressionParserand expressions are parsed and executed by implementations in Spring. The implementation translates the SpEL expression in string format as input to get the org.springframework.expression.Expressioninstance. The context instance org.springframework.expression.EvaluationContextis represented by the implementation, such as: StandardEvaluationContext.

A SpEL example that might look like this:

Writer writer = new Writer();
writer.setName("Writer's name");
StandardEvaluationContext modifierContext = 
    new StandardEvaluationContext(subscriberContext);
modifierContext.setVariable("name", "Overriden writer's name");
parser.parseExpression("name = #name").getValue(modifierContext);
System.out.println("writer's name is : " + writer.getName());
// 这里控制台上的输出会是 Overriden writer's name

The output will print "Overriden writer's name". As you can see, the properties of the object are modified based on the provided context (in the example above ) via an ExpressionParserunderstandable expression .name = #namemodifierContext

Spring Design Patterns - Generatorsbuilder

The generator pattern Builder design patternis the first pattern in the object creation pattern. This pattern is used to simplify the construction of complex objects. To understand this concept, imagine an object that describes a programmer's resume. In this object we want to store personal information (name, address, etc.) and technical ability information (language mastered, projects implemented, etc.). The construction of the object might look like this:

// 方式1:使用构造函数
Programmer programmer = new Programmer(
                            "first name", "last name", 
                            "address Street 39", "ZIP code", 
                            "City", "Country", birthDateObject, 
                            new String[] {"Java", "PHP", "Perl", "SQL"}, 
                            new String[] {"CRM system", "CMS system for government"});
// 或者 方式2 : 使用 setXXX 方法
Programmer programmer = new Programmer();
programmer.setName("first name");
programmer.setLastName("last name");
// ... 很多行setXXX 方法调用之后,添加项目信息
programmer.setProjects(new String[] {"CRM system", "CMS system for government"});

Generators, on the other hand, allow us to clearly decompose the object construction process using an internal generator object that passes values ​​to the parent class. Like this:

public class BuilderTest {

  @Test
  public void test() {
    Programmer programmer = new Programmer.ProgrammerBuilder()
            .setFirstName("F").setLastName("L")
            .setCity("City").setZipCode("0000A").setAddress("Street 39")
            .setLanguages(new String[] {"bash", "Perl"})
            .setProjects(new String[] {"Linux kernel"})
            .build();
    assertTrue("Programmer should be 'F L' but was '"+ programmer+"'", 
                programmer.toString().equals("F L"));
  }

}

class Programmer {
  private String firstName;
  private String lastName;
  private String address;
  private String zipCode;
  private String city;
  private String[] languages;
  private String[] projects;

  private Programmer(String fName, String lName, 
                      String addr, String zip, String city, 
                      String[] langs, String[] projects) {
    this.firstName = fName;
    this.lastName = lName;
    this.address = addr;
    this.zipCode = zip;
    this.city = city;
    this.languages = langs;
    this.projects = projects;
  }

  public static class ProgrammerBuilder {
    private String firstName;
    private String lastName;
    private String address;
    private String zipCode;
    private String city;
    private String[] languages;
    private String[] projects;

    public ProgrammerBuilder setFirstName(String firstName) {
      this.firstName = firstName;
      return this;
    }

    public ProgrammerBuilder setLastName(String lastName) {
      this.lastName = lastName;
      return this;
    }

    public ProgrammerBuilder setAddress(String address) {
      this.address = address;
      return this;
    }

    public ProgrammerBuilder setZipCode(String zipCode) {
      this.zipCode = zipCode;
      return this;
    }

    public ProgrammerBuilder setCity(String city) {
      this.city = city;
      return this;
    }

    public ProgrammerBuilder setLanguages(String[] languages) {
      this.languages = languages;
      return this;
    }
    public ProgrammerBuilder setProjects(String[] projects) {
      this.projects = projects;
      return this;
    }

    public Programmer build() {
      return new Programmer(firstName, lastName, address, zipCode, city, 
                          languages, projects);
    } 
  }

  @Override
  public String toString() {
    return this.firstName + " "+this.lastName;
  }

}

Here you can see that the complex object construction process is hidden behind a generator, which is an inner static class instance that receives chained method calls. In Spring, we can extract this logic into org.springframework.beans.factory.support.BeanDefinitionBuilderclasses. This class allows us to programmatically define a bean. As we saw in this article on bean factory post processors , there are many methods for setting scopes, factory methods, properties, etc. BeanDefinitionBuilderto the associated beanDefinition (which implements the abstract class) property object. AbstractBeanDefinitionLet's take a look at the implementation of these methods to understand how it works:

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 Design Patterns - Factory Methodfactory method

The second member of the family of object creation patterns is the factory method design patternfactory method design pattern . It is perfectly suited to work with dynamic environments like the Spring framework. In fact, this pattern allows object initialization by exposing a static method called a factory method. In this concept, we need to define an interface for creating objects. But object creation is handled by the class that uses the target object.

Before jumping into the Spring world, let's look at an example from the Java world:

// 该例子没有使用Spring框架的功能,而是基于Java SE
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;
  }

  // 工厂方法例子 -- 根据当前上下文创建不同的对象
  public static Meal valueOf(String ingredient) {
    if (ingredient.equals("banana")) {
      return new Meal("fruit");
    }
    return new Meal("vegetable");
  }
}

In Spring we can create beans using specified factory methods. valueOfThis method is the same as the function in the above example . It is static and can take no or multiple parameters. To understand this better, let's look at a real example. The first is the configuration file:

<bean id="welcomerBean" class="com.mysite.Welcomer" factory-method="createWelcomer">
    <constructor-arg ref="messagesLocator">
    </constructor-arg>
</bean>

<bean id="messagesLocator" class="com.mysite.MessageLocator">
    <property name="messages" value="messages_file.properties">
    </property>
</bean>

Then there are the bean classes that this initialization process is concerned with:

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

When Spring is going to construct a bean instance welcomerBean, it will not go directly through the classic constructor pattern, but instead by defining a static factory method createWelcomerpattern. It's also important to note here that the method accepts some parameters (MessageLocator bean instance, holding all available messages).

Spring Design Patterns - Abstract Factoryabstract factory

The last pattern, the abstract factory design patternabstract factory design pattern , looks a lot like the factory method. The difference is that we can think of an abstract factory as a factory in the real world that will provide some needed items. A factory may have these components: abstract factory, abstract product, product and client. More specifically, an abstract factory defines how objects are constructed. The abstract product is the result of this build process. A product is a concrete output of the same build process. A client is someone who asks an abstract factory to create an item.

As usual, before we get into the details of Spring, let's describe the concept in simple Java code:

public class FactoryTest {

  // 测试方法,可以理解成用于表示客户行为
  @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 class Kitchen {
  public abstract KitchenMeal getMeal(String preferency);
  public abstract KitchenMeal getDessert(String preferency);
}

// 抽象工厂的具体实现
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 class KitchenMeal {
  public abstract String getName();
}

// 具体产品
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";
  }
}

As we can see from this example, the abstract factory encapsulates the creation of objects. The creation of objects here can also be done using the factory method pattern instead of the typical constructor pattern. In Spring, an example of an abstract factory is org.springframework.beans.factory.BeanFactory. Using the implementation of this interface, we can access beans within the Spring container. Depending on the strategy used, the getBeanmethod may return an already created object (shared instance, singleton scope) or create a new object (prototype type). BeanFactoryThere are various implementations: ClassPathXmlApplicationContext, XmlWebApplicationContext, StaticWebApplicationContext, StaticPortletApplicationContext, GenericApplicationContext, StaticApplicationContext. Below you can see an example of using an abstract factory in a Spring web application:

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

In this example, the abstract factory is BeanFactoryrepresented by an interface. The implementation class will be System.outprinted to the console, and its corresponding class is: org.springframework.beans.factory.support.DefaultListableBeanFactory. Abstract products are Object. The specific product, in this example, is an instance Objectof the forced type conversion of the abstract product object.ShoppingCart

This article introduces the particularly interesting world of using design patterns to properly organize program code. Here, we saw the interpreter interpreter, generator builder, factory method factory methodand factory factory(refer to abstract factory) pattern applied in the Spring framework. The first schema interpreter interpreterhelps interpret text expressed in SpEL style. The other three modes are all object creation modes, and the main purpose of all three of them in Sping is to help simplify object creation. They do this by breaking up the construction process of complex objects or by centralizing the initialization process to some common point.

English original

Directory of articles in the series

Design Patterns in Spring Framework (5)
Design Patterns in Spring Framework (4)
Design Patterns in Spring Framework (3)
Design Patterns in Spring Framework (2) Design Patterns
in Spring Framework (1)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325641442&siteId=291194637