这是 Spring 面试题系列的第二篇,本文的主题:Spring 中涉及到的设计模式,如何在面试中回答的尽量全面、准确、有深度。java
本篇只回答一个问题:面试
首先做一个概述,从总体上讲,SpringFramework 中使用了 11 种设计模式:设计模式
固然,若是只是这么回答,面试官会怎么想:你这。。。不会是在背答案吧!随便揪出一个来细问,可能就翻皮水了 ~ ~ 因此咱不光要知道用了啥,并且还要知道如何用的,在哪里用的,这样才能用本身真正的技术储备征服面试官。缓存
下面咱详细的介绍 11 种设计模式的设计场景和原理。markdown
因为 11 种设计模式所有展开篇幅过长,会分红两篇专栏介绍。框架
SpringFramework 的 IOC 容器中放了不少不少的 Bean ,默认状况下,Bean 的做用域( Scope )是 singleton
,就是单实例的;若是显式声明做用域为 prototype
,那 Bean 的做用域就会变为每次获取都是一个新的,即原型 Bean 。这个知识点本应该在 SpringFramework 最基础阶段就应该知道的,咱也很少啰嗦。关键的问题是,若是我定义的 Bean 声明了 prototype ,那 SpringFramework 确定知道我要搞原型 Bean ;但我定义 Bean 的时候没声明 Scope ,它怎么就给我默认成单实例的呢?ide
下面咱先从最熟悉的 Bean 注册场景出发。(原型模式相对简单,内容已穿插在解释单例模式之中)post
<bean class="com.example.demo.bean.Person" scope="singleton"/> 复制代码
这是最最简单的 Bean 的注册了,这里面若是显式声明了 scope="singleton"
后,IDE 会报黄(警告):学习
很明显它提示了默认值就是 singleton
,咱不必再主动声明了。这个提示是 IDEA 智能识别的,咱很差找出处,不过咱能够点进去这个 scope ,看一眼 xsd 中的注释:ui
<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
的。
注解驱动的方式,都是使用一个 @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 是单实例的,那 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
接口,则它自己将再也不是一个普通的 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"/> 复制代码
这个类型可能有些小伙伴会感受有些陌生,因此我放到了最后写。
@FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; } 复制代码
结构比 FactoryBean
简单,固然也能够简单地将其理解为 FactoryBean
,但又与其不一样。ObjectFactory
通常状况下会做为一个 Bean 注入到其余 Bean 中,在须要用对应的 bean 时主动调用 ObjectFactory
的 getObject
方法获取真正须要的 Bean ;FactoryBean
的 getObject
方法是在 SpringFramework 初始化 Bean 时调用的,因此由此也能够知道二者的调用时机也不同。
其实这个接口在上面看 Bean 的实例化过程中就遇到过了,在 getSingleton
的两参数方法中,第二个参数就是 ObjectFactory
类型,由它就能够调用 createBean
建立出单实例对象。
SpringFramework 中的工厂模式包括内置的 FactoryBean
、ObjectFactory
,以及自定义声明的静态工厂、实例工厂。
咱都知道,SpringFramework 的两大核心:IOC 、AOP ,AOP 就是体现了代理模式的使用。不过若是只说 AOP 体现了代理模式,那这个也太没水准了,咱要回答的更多更全,才能让面试官意识到你真的有研究过,你真的很懂!
SpringFramework 中对 Bean 进行 AOP 加强生成代理对象,核心是一个 BeanPostProcessor
:AnnotationAwareAspectJAutoProxyCreator
,这个名字很长,不过很好记:
这样一拆分,是否是感受容易理解多了呢?
它的核心做用方法是父类 AbstractAutoProxyCreator
的 postProcessAfterInitialization
方法,底层会调用 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 动态代理的建立,在 CglibAopProxy
的 createProxyClassAndInstance
方法中有建立代理对象的实现:
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 个设计模式的体现会放在下篇介绍 ~ 小伙伴们记得关注点赞呀,有源码学习须要的能够看我小册 ~ 奥利给】