Spring中使用的设计模式,你都能说全吗?[上]

3,161 阅读11分钟

这是 Spring 面试题系列的第二篇,本文的主题:Spring 中涉及到的设计模式,如何在面试中回答的尽可能全面、准确、有深度。

本篇只回答一个问题:

Spring 中使用了哪些设计模式?分别都是如何实现的?

首先作一个概述,从整体上讲,SpringFramework 中使用了 11 种设计模式:

  • 单例模式+原型模式
  • 工厂模式
  • 代理模式
  • 策略模式
  • 模板方法模式
  • 观察者模式
  • 适配器模式
  • 装饰者模式
  • 外观模式
  • 委派模式(不属于GoF23)

当然,如果只是这么回答,面试官会怎么想:你这。。。不会是在背答案吧!随便揪出一个来细问,可能就翻皮水了 ~ ~ 所以咱不光要知道用了啥,而且还要知道如何用的,在哪里用的,这样才能用自己真正的技术储备征服面试官。

下面咱详细的介绍 11 种设计模式的设计场景和原理。

由于 11 种设计模式全部展开篇幅过长,会分成两篇专栏介绍。

单例模式+原型模式

SpringFramework 的 IOC 容器中放了很多很多的 Bean ,默认情况下,Bean 的作用域( Scope )是 singleton ,就是单实例的;如果显式声明作用域为 prototype ,那 Bean 的作用域就会变为每次获取都是一个新的,即原型 Bean 。这个知识点本应该在 SpringFramework 最基础阶段就应该知道的,咱也不多啰嗦。关键的问题是,如果我定义的 Bean 声明了 prototype ,那 SpringFramework 肯定知道我要搞原型 Bean ;但我定义 Bean 的时候没声明 Scope ,它怎么就给我默认成单实例的呢?

下面咱先从最熟悉的 Bean 注册场景出发。(原型模式相对简单,内容已穿插在解释单例模式之中)

Bean的注册

xml方式注册Bean
<bean class="com.example.demo.bean.Person" scope="singleton"/>

这是最最简单的 Bean 的注册了,这里面如果显式声明了 scope="singleton" 后,IDE 会报黄(警告):

很明显它提示了默认值就是 singleton ,咱没必要再主动声明了。这个提示是 IDEA 智能识别的,咱不好找出处,不过咱可以点进去这个 scope ,看一眼 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), ......

很明显文档注释的第一句就说了:通常它是 singleton

注解驱动注册Bean

注解驱动的方式,都是使用一个 @Scope 注解来声明作用域的:

@Scope
@Component
public class Person {
    
}

点开 @Scope 注解看源码,可以发现只标注 @Scope 注解,不声明作用域,默认值是空字符串(不是 singleton ):

public @interface Scope {

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

这个地方可能就会有疑惑了,它声明的是空字符串,可是在 xml 中我配置的是 singleton 啊,这怎么不一样呢?莫慌,下面咱来解析这其中的原因。

默认的作用域标注

对 SpringFramework 有一些深入了解的小伙伴应该能意识到我接下来要说什么了:BeanDefinition 。所有 Bean 的定义信息都被 SpringFramework 封装到 BeanDefinition 中了,而作用域的定义就在 BeanDefinition 的抽象实现类 AbstractBeanDefinition 中:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {

	public static final String SCOPE_DEFAULT = "";

这里面一上来就声明了默认的作用域就是空字符串,不是 singleton

这个时候可能有的小伙伴就更迷惑了,这里面它都声明了单实例 Bean 是空字符串,那 singleton 还有个卵用呢?判断单实例 Bean 不是应该看作用域是否为 singleton 吗?

哎,说到这里了,那咱就看看 BeanDefinition 中是如何获取作用域的:

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

获取作用域的方式非常简单,这个没啥看的。但是!!!注意继续往下翻,紧跟着下面有一个方法叫 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);
}

看这里面的判断,它分为了两个部分:**是否是 singleton ,或者是否为空串!**那这就说得过去了吧,人家设置成空串,意义上也是单实例 Bean 。

Bean的实例化

上面咱也知道了,默认情况下 Bean 是单实例的,那 SpringFramework 在 IOC 容器初始化的时候,是如何知道这些 Bean 是否是单实例的,同时初始化并保存的呢?下面咱跟进底层初始化逻辑中看一眼。

本部分只粗略介绍 Bean 的初始化流程,详细的解析可以参照我的 SpringBoot 源码小册 14 章详细学习。

AbstractBeanFactory 中,getBean 方法会调用到 doGetBean ,这个方法的篇幅非常长,这里只剪出框框:

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.
    // 最开始先检查单实例对象缓存池中是否已经有对应的bean了
    Object sharedInstance = getSingleton(beanName);
    // ......
    else {
        // ......
        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            // 检查 ......

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

            else if (mbd.isPrototype()) {
                // 原型Bean
                // It's a prototype -> create a new instance.
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    // 必定创建全新的对象
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }

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

仔细阅读这个框框流程,一上来它就要先检查单实例对象的缓存池中是否有现成的 Bean 了,没有再往下走。那咱说创建流程的话还是往下走,在 else 块的 try 部分,它会取出当前 Bean 的 BeanDefinition 来判断作用域:如果是 singleton 单实例的,就执行 getSingleton 方法创建单实例对象(底层走 lambda 表达式中的 createBean 方法);如果是 prototype 原型 Bean ,就执行原型 Bean 的创建流程(直接创建);如果这些都不是,那就可以认定为自定义 scope ,使用特殊的初始化流程。

所以由此看下来,单实例 Bean 的创建核心方法还是 getSingleton 了,那咱就进到这里面看一眼:(还是只有大框框的流程哈)

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) {
            // 检查
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } // catch finally ......
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

注意看这里面的设计:它会先去单实例对象缓存池中找是否已经有对应的 bean 了,如果没有,就执行创建 bean 的动作。在创建完成后,它还会将 bean 放入缓存池中,这样以后再取的时候就不会再二次创建了。

小结

所以这里面的核心逻辑也就可以总结出来了:

SpringFramework 中实现的单例模式,是在 BeanDefinition 中默认配置 singleton 的作用域,在 IOC 容器初始化阶段,将 Bean 创建好,放入单实例对象缓存池( singletonObjects )中,实现 Bean 的单实例。

工厂模式

提起工厂模式,在 SpringFramework 中最容易联想到的就是 FactoryBean 了吧!但其实 SpringFramework 中不止这一个是工厂,还有很多种其他的,下面咱来列举。

FactoryBean

FactoryBean 本身是一个接口,它本身就是一个创建对象的工厂。如果一个类实现了 FactoryBean 接口,则它本身将不再是一个普通的 bean 对象,不会在实际的业务逻辑中起作用,而是由创建的对象来起作用。

FactoryBean 接口有三个方法:

public interface FactoryBean<T> {
    // 返回创建的对象
    @Nullable
    T getObject() throws Exception;

    // 返回创建的对象的类型(即泛型类型)
    @Nullable
    Class<?> getObjectType();

    // 创建的对象是单实例Bean还是原型Bean,默认单实例
    default boolean isSingleton() {
        return true;
    }
}

静态工厂

这种方式很像咱在最开始学习简单工厂模式中看到的核心工厂,比方说下面这样:

public class CalculatorFactory {
    // 简单工厂
    public static Calculator getCalculator(String operationType) {
        switch (operationType) {
            case "+": 
                return new AddCalculator();
            case "-":
                return new SubtractCalculator();
            default: 
                return null;
        }
    }
    
    // 静态工厂
    public static Calculator getAddCalculator() {
        return new AddCalculator();
    }
}

在 SpringFramework 中使用静态工厂,就没有参数这个说法了,只需要声明工厂类和方法即可(所以上面的工厂中我额外写了一个方法):

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

这样注册后得到的 bean ,类型是 AddCalculator

实例工厂

实例工厂的使用方式与静态工厂很像,只不过静态工厂本身不会注册到 IOC 容器中,但实例工厂会一起注册到 IOC 容器

调整上面的代码,就可以实现实例工厂的 Bean 注册:

public class CalculatorFactory {
    // 工厂方法
    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

这个类型可能有些小伙伴会感觉有些陌生,所以我放到了最后写。

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

结构比 FactoryBean 简单,当然也可以简单地将其理解为 FactoryBean ,但又与其不同。ObjectFactory 一般情况下会作为一个 Bean 注入到其他 Bean 中,在需要用对应的 bean 时主动调用 ObjectFactorygetObject 方法获取真正需要的 BeanFactoryBeangetObject 方法是在 SpringFramework 初始化 Bean 时调用的,所以由此也可以知道两者的调用时机也不一样

其实这个接口在上面看 Bean 的实例化过程中就遇到过了,在 getSingleton 的两参数方法中,第二个参数就是 ObjectFactory 类型,由它就可以调用 createBean 创建出单实例对象。

小结

SpringFramework 中的工厂模式包括内置的 FactoryBeanObjectFactory ,以及自定义声明的静态工厂、实例工厂。

代理模式

咱都知道,SpringFramework 的两大核心:IOC 、AOP ,AOP 就是体现了代理模式的使用。不过如果只说 AOP 体现了代理模式,那这个也太没水准了,咱要回答的更多更全,才能让面试官意识到你真的有研究过,你真的很懂!

AOP的底层实现

SpringFramework 中对 Bean 进行 AOP 增强生成代理对象,核心是一个 BeanPostProcessorAnnotationAwareAspectJAutoProxyCreator ,这个名字很长,不过很好记:

  • Annotation:注解式,
  • Aware:注入式
  • AspectJ:基于 AspectJ 的 AOP
  • AutoProxy:自动代理
  • Creator:创建器

这样一拆分,是不是感觉容易理解多了呢?

它的核心作用方法是父类 AbstractAutoProxyCreatorpostProcessAfterInitialization 方法,底层会调用 wrapIfNessary 方法创建代理对象:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            // 创建AOP代理对象
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

至于再往底下,这个就麻烦大了,这里简单总结一下吧,详尽的代理对象创建可以参考 SpringBoot 源码小册的 19 章学习。

被 AOP 增强的 Bean ,会在初始化阶段(此时对象已经创建)被 AnnotationAwareAspectJAutoProxyCreator 处理,整合该 Bean 可能被覆盖到的切面,最终根据 Bean 是否有接口实现,采用 jdk 动态代理或者 Cglib 动态代理构建生成代理对象。

代理对象的创建

上面的总结中提到了最终的动态代理创建,这里可以带小伙伴看一眼最底层你们熟悉的创建代理对象的源码。

jdk 动态代理的创建,在 JdkDynamicAopProxy 中,有一个 getProxy 方法,底层实现如下:

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原生方法
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

看最后一句,是不是突然熟悉了!这个地方就可以在面试中拿出来吹一吹,这样面试官可能就真的认为你把这部分原理都搞明白了哦(狗头)。

Cglib 动态代理的创建,在 CglibAopProxycreateProxyClassAndInstance 方法中有创建代理对象的实现:

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

看到这里的 Enhancer#create() 方法,是不是又是熟悉的一幕呢?所以由此也知道,框架也只是在咱学过的基础上层层包装增强罢了,最底层的还是不变的。

小结

SpringFramework 中的代理模式体现在 AOP 上,它通过后置处理器,整合切面(增强器 Advice )的逻辑,将原有的 Bean (目标对象 Target )使用 jdk 或者 Cglib 动态代理增强为代理 Bean 。

策略模式

说起 SpringFramework 中实现的策略模式,其实刚刚就提到了:AOP 生成代理对象时,会根据原始 Bean 是否有接口实现,决定使用 jdk 动态代理还是 Cglib 动态代理,这就是典型的策略模式体现。

直接说原理吧,在 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.");
        }
        // 策略判断
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

中间的这个判断当前要代理的目标对象,类型是否是一个接口,或者目标对象是否为一个代理类。如果是二者之一,则可以直接使用 jdk 的动态代理即可,否则才会使用 Cglib 代理。

【篇幅限制,剩余 6 个设计模式的体现会放在下篇介绍 ~ 小伙伴们记得关注点赞呀,有源码学习需要的可以看我小册 ~ 奥利给】