Can you say all the design patterns used in Spring?

Posted by AcidCool19 on Sat, 29 Jan 2022 05:41:07 +0100

This is the first article in the Spring interview question series. The theme of this article is: the design patterns involved in Spring and how to answer in the interview as comprehensively, accurately and deeply as possible.

This article only answers one question:

What design patterns are used in Spring? How are the differences realized?

First, make an overview. On the whole, 11 design patterns are used in the spring framework:

  • Single case mode + prototype mode
  • Factory mode
  • proxy pattern
  • Strategy mode
  • Template method mode
  • Observer mode
  • Adapter mode
  • Decorator mode
  • Appearance mode
  • Delegation mode (not part of GoF23)

Of course, if you just answer like this, what will the interviewer think: you... I'm not reciting the answer! Just pick out one and ask it in detail, it may turn the skin ~ ~ so we should not only know what to use, but also know how to use it and where to use it, so as to conquer the interviewer with our real technical reserves.

Let's introduce the design scenarios and principles of 11 design modes in detail.

As all the 11 design patterns are too long, they will be introduced in two columns.

Single case mode + prototype mode

There are many beans in the IOC container of the spring framework. By default, the Scope of a Bean is a singleton, which is a single instance; If the Scope is explicitly declared as prototype, the Scope of the Bean will become a new one every time, that is, the prototype Bean. This knowledge point should have been known in the most basic stage of the spring framework. I won't be too verbose. The key problem is that if the Bean I define declares prototype, the spring framework must know that I want to make prototype beans; But I didn't declare Scope when I defined Bean. How can it default to single instance for me?

Let's start with the most familiar Bean registration scenario. (the prototype mode is relatively simple, and the content has been interspersed in the interpretation singleton mode)

Bean registration

Register beans in xml
<bean class="com.example.demo.bean.Person" scope="singleton"/> 

This is the simplest Bean registration. If the scope="singleton" is explicitly declared, the IDE will report yellow (warning):

Obviously, it indicates that the default value is singleton. We don't need to take the initiative to declare it again. This prompt is intelligently recognized by IDEA. It's hard for us to find the source, but we can click into this scope and take a look at the notes in xsd:

<xsd:attribute name="scope" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation><![CDATA[
	The scope of this bean: typically "singleton" (one shared instance,
	which will be returned by all calls to getBean with the given id), ...... 

Obviously, the first sentence of the document comment says: usually it's singleton.

Annotation driven registered Bean

Annotation driven methods all use a @ Scope annotation to declare the Scope:

@Scope
@Component
public class Person {
    
} 

Click the @ Scope annotation to see the source code. You can find that only the @ Scope annotation is marked, and the Scope is not declared. The default value is an empty string (not a singleton):

public @interface Scope {

	@AliasFor("scopeName")
	String value() default ""; 

This place may be confused. It declares an empty string, but I configure a singleton in xml. Why is it different? Don't panic. Let's analyze the reasons.

If you feel that your learning efficiency is low and you lack correct guidance, you can join the technology circle with rich resources and strong learning atmosphere to learn and communicate together!
[Java architecture group]
There are many technological giants from the front line in the group, as well as code farmers struggling in small factories or outsourcing companies. We are committed to building an equal and high-quality JAVA Communication circle, which may not make everyone's technology advance by leaps and bounds in the short term, but in the long run, vision, pattern and long-term development direction are the most important.

Default scope dimensions

Small partners who have some in-depth knowledge of the spring framework should be able to realize what I'm going to say next: BeanDefinition. All Bean definition information is encapsulated into BeanDefinition by the spring framework, and the definition of scope is in the abstract implementation class AbstractBeanDefinition of BeanDefinition:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {

	public static final String SCOPE_DEFAULT = ""; 

It is declared that the default scope is an empty string, not a singleton.

At this time, some small partners may be even more confused. They all declare that the single instance Bean is an empty string. What about singleton? Shouldn't a single instance Bean be judged by whether the scope is a singleton?

Hey, having said that, let's take a look at how to obtain the scope in BeanDefinition:

public String getScope() {
    return this.scope;
} 

The way to obtain the scope is very simple. It's nothing to see. But!!! Pay attention to continue to turn down, followed by a method called isSingleton:

/**
 * Return whether this a <b>Singleton</b>, with a single shared instance
 * returned from all calls.
 * @see #SCOPE_SINGLETON
 */
@Override
public boolean isSingleton() {
    return SCOPE_SINGLETON.equals(this.scope) || SCOPE_DEFAULT.equals(this.scope);
} 

Look at the judgment here. It is divided into two parts: * * whether it is a singleton or an empty string** That makes sense. People set it as an empty string, which is also a single instance Bean in the sense.

Instantiation of Bean

We also know that beans are single instance by default. How does the spring framework know whether these beans are single instance, initialized and saved at the same time when the IOC container is initialized? Let's take a look at the underlying initialization logic.

This section only briefly introduces the initialization process of Bean. For detailed analysis, please refer to Chapter 14 of my SpringBoot source code booklet for details.

In AbstractBeanFactory, the getBean method will call doGetBean. The length of this method is very long. Only the frame is cut out here:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
        @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    // First, check whether there are corresponding bean s in the single instance object cache pool
    Object sharedInstance = getSingleton(beanName);
    // ......
    else {
        // ......
        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            // Check

            // Create bean instance.
            if (mbd.isSingleton()) {
                // Single instance Bean
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    } // catch ......
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }

            else if (mbd.isPrototype()) {
                // Prototype Bean
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    // New objects must be created
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

            else {
                // Custom scope
                String scopeName = mbd.getScope();
                final Scope scope = this.scopes.get(scopeName);
                // ......
            }
        } // catch ......
    }
    // ......
    return (T) bean;
} 

Read this frame process carefully. As soon as it comes up, it must first check whether there are ready-made beans in the cache pool of single instance objects, and there is no further going down. Let's talk about creating the process. In the try part of else block, it will take out the BeanDefinition of the current Bean to judge the scope: if it is a singleton single instance, execute getSingleton method to create a single instance object (the createBean method in lambda expression at the bottom); If it is a prototype Bean, execute the creation process of the prototype Bean (create directly); If none of these is true, it can be identified as a custom scope and a special initialization process can be used.

So from this point of view, the core method of creating a single instance Bean is getSingleton. Let's take a look at it: (it's still a process with only a large frame)

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // inspect
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } // catch finally ......
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
} 

Pay attention to the design here: it will first go to the single instance object cache pool to find out whether there is a corresponding bean. If not, it will execute the action of creating a bean. After the creation is completed, it will also put the bean into the cache pool, so that it will not be created again when fetching later.

Summary

So the core logic can be summarized:

The singleton mode implemented in the spring framework is to configure the scope of singleton by default in the BeanDefinition. In the IOC container initialization stage, the Bean is created and put into the singleton objects cache pool to realize the single instance of the Bean.

Factory mode

When it comes to factory pattern, FactoryBean is the easiest thing to think of in spring framework! But in fact, there are not only factories in the spring framework, but also many others. Let's list them below.

FactoryBean

FactoryBean itself is an interface, which itself is a factory for creating objects. If a class implements the FactoryBean interface, it will no longer be an ordinary bean object and will not work in the actual business logic, but by the created object.

The FactoryBean interface has three methods:

public interface FactoryBean<T> {
    // Returns the created object
    @Nullable
    T getObject() throws Exception;

    // Returns the type of the created object (that is, the generic type)
    @Nullable
    Class<?> getObjectType();

    // Whether the created object is a single instance Bean or a prototype Bean, the default is a single instance Bean
    default boolean isSingleton() {
        return true;
    }
} 

Static factory

This method is very similar to the core factory I saw in learning the simple factory mode at the beginning, for example:

public class CalculatorFactory {
    // Simple factory
    public static Calculator getCalculator(String operationType) {
        switch (operationType) {
            case "+": 
                return new AddCalculator();
            case "-":
                return new SubtractCalculator();
            default: 
                return null;
        }
    }
    
    // Static factory
    public static Calculator getAddCalculator() {
        return new AddCalculator();
    }
} 

If you use a static factory in the spring framework, there is no argument. You only need to declare the factory class and method (so I wrote an additional method in the factory above):

<bean id="addCalculator" class="com.example.demo.bean.CalculatorFactory" factory-method="getAddCalculator"/> 

The bean obtained after this registration is of type AddCalculator.

Example factory

The use of instance factories is very similar to that of static factories, except that the static factories themselves will not be registered in the IOC container, but the instance factories will be registered in the IOC container together.

By adjusting the above code, the Bean registration of the instance factory can be realized:

public class CalculatorFactory {
    // Factory method
    public Calculator getAddCalculator() {
        return new AddCalculator();
    }
} 
<bean id="calculatorFactory" class="com.example.demo.bean.CalculatorFactory"/>
<bean id="addCalculator" factory-bean="calculatorFactory" factory-method="getAddCalculator"/> 

ObjectFactory

Some friends of this type may feel strange, so I put it at the end.

@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
} 

The structure is simpler than FactoryBean. Of course, it can also be simply understood as FactoryBean, but it is different from it. In general, ObjectFactory will be injected into other beans as a bean. When the corresponding bean needs to be used, it will actively call the getObject method of ObjectFactory to obtain the bean that is really needed; The getObject method of FactoryBean is called when the spring framework initializes the bean, so we can know that the call timing of the two is also different.

In fact, this interface has been encountered in the process of Bean instantiation. In the two parameter method of getSingleton, the second parameter is the ObjectFactory type, which can call createBean to create a single instance object.

Summary

The factory pattern in spring framework includes built-in FactoryBean and ObjectFactory, as well as static factory and instance factory of custom declaration.

proxy pattern

As we all know, the two cores of spring framework: IOC and AOP. AOP embodies the use of proxy mode. However, if we only say that AOP embodies the agency model, it's too low-level. We need to answer more and more comprehensively in order to make the interviewer realize that you have really studied it and you really understand it!

Underlying implementation of AOP

In the spring framework, Bean is AOP enhanced to generate proxy objects. The core is a BeanPostProcessor: AnnotationAwareAspectJAutoProxyCreator. The name is very long, but it's easy to remember:

  • Annotation: annotation,
  • Aware: injection
  • AspectJ: AOP based on AspectJ
  • AutoProxy: AutoProxy
  • Creator: Creator

Does this split feel much easier to understand?

Its core function method is the postProcessAfterInitialization method of the parent class AbstractAutoProxyCreator. The bottom layer will call wrapIfNessary method to create proxy object:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // Create AOP proxy object
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
} 

The Bean enhanced by AOP will be processed by AnnotationAwareAspectJAutoProxyCreator in the initialization phase (when the object has been created), integrate the aspects that the Bean may be covered, and finally generate the proxy object by using jdk dynamic proxy or Cglib dynamic proxy according to whether the Bean has an interface implementation.

Creation of proxy objects

The above summary mentioned the final dynamic proxy creation. Here you can take a look at the source code of creating proxy objects that you are familiar with at the bottom.

For the creation of jdk dynamic proxy, there is a getProxy method in JdkDynamicAopProxy, and the underlying implementation is as follows:

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }
    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    // jdk native method
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
} 

Look at the last sentence. Are you suddenly familiar with it! This place can be blown out in the interview, so that the interviewer may really think you have understood this part of the principle (dog head).

For the creation of Cglib dynamic proxy, the creation of proxy object is implemented in the createProxyClassAndInstance method of CglibAopProxy:

protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    enhancer.setInterceptDuringConstruction(false);
    enhancer.setCallbacks(callbacks);
    return (this.constructorArgs != null && this.constructorArgTypes != null ?
            enhancer.create(this.constructorArgTypes, this.constructorArgs) :
            enhancer.create());
} 

Is it another familiar scene to see the Enhancer#create() method here? Therefore, we also know that the framework is only based on the upper layer packaging enhancement we have learned, and the bottom layer is still unchanged.

Summary

The proxy mode in the spring framework is embodied in AOP. Through the post processor, it integrates the logic of aspect (enhancer Advice) and enhances the original Bean (Target object) into a proxy Bean using jdk or Cglib dynamic proxy.

Strategy mode

Speaking of the policy pattern implemented in the spring framework, it has just been mentioned: when AOP generates proxy objects, it will decide whether to use jdk dynamic proxy or Cglib dynamic proxy according to whether the original Bean has an interface implementation. This is a typical embodiment of the policy pattern.

Let's talk about the principle directly. There is a policy mode in DefaultAopProxyFactory:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        // Strategy judgment
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
} 

This one in the middle determines whether the type of the target object to be proxied is an interface or whether the target object is a proxy class. If it is one of the two, you can directly use the dynamic agent of jdk. Otherwise, you can use the Cglib agent.

[space limitation, the embodiment of the remaining 6 design patterns will be introduced in the next article ~ remember the third company ~ Ollie]

last

Bald brother will share with you an immortal document of Java high concurrency core programming compiled by front-line development Daniel, which mainly contains knowledge points: multithreading, thread pool, built-in lock, JMM, CAS, JUC, high concurrency design mode, Java asynchronous callback, completable future class, etc.

Document address: A divine article explains java multithreading, lock, JMM, JUC and high concurrency design pattern clearly

Code words are not easy. If you think this article is useful to you, please give me one button three times! Pay attention to the author, there will be more dry goods to share in the future, please continue to pay attention!

Topics: Java Programming Spring Interview AOP