Design patterns in Spring Framework
Posted 火影@漩涡李洛克
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Design patterns in Spring 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):
- 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);
- }
- }
Further, this adapter instance is used to make weaving operations depending on running servlet container version:
- @Override
- public void addTransformer(ClassFileTransformer transformer) {
- this.adapter.addTransformer(transformer);
- }
- @Override
- public ClassLoader getInstrumentableClassLoader() {
- return this.adapter.getInstrumentableClassLoader();
- }
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:
- 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;
- }
- }
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):
- 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;
- }
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:
- @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);
- }
- }
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:
- 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);
- // sample output
- // president1 = [email protected] and president2 = [email protected]
- }
- }
- enum SingletonsHolder {
- PRESIDENT(new President());
- private Object holdedObject;
- private SingletonsHolder(Object o) {
- this.holdedObject = o;
- }
- public Object getHoldedObject() {
- return this.holdedObject;
- }
- }
- class President {
- }
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):
- /**
- * 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();
- }
- }
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:
- <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);
- }
- }
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.
以上是关于Design patterns in Spring Framework的主要内容,如果未能解决你的问题,请参考以下文章
Prototype design pattern in Java
[Design Pattern] Adapter Design Pattern
The second curriculum design experiment report in spring 2019