@ -5,50 +5,56 @@ tag:
- Spring
---
JDK 中用到了哪些设计模式?Spring 中用到了哪些设计模式?这两个问题,在面试中比较常见。我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下,由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。
“ JDK 中用到了哪些设计模式? Spring 中用到了哪些设计模式? ” 这两个问题,在面试中比较常见。
Design Patterns(设计模式) 表示面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看到底有哪些设计模式?
我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远。所以,花了几天时间自己总结了一下。
由于我的个人能力有限,文中如有任何错误各位都可以指出。另外,文章篇幅有限,对于设计模式以及一些源码的解读我只是一笔带过,这篇文章的主要目的是回顾一下 Spring 中的设计模式。
## 控制反转(IoC)和依赖注入(DI)
**IoC(Inversion of Control,控制反转)** 是Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(IOC容器管理对象, 你只管使用即可),从而降低代码之间的耦合度。**IOC 是一个原则, 而不是一个模式, 以下模式( 但不限于) 实现了IoC原则。**
**IoC(Inversion of Control,控制反转)** 是 Spring 中一个非常非常重要的概念, 它不是什么技术, 而是一种解耦的设计思想。IoC 的主要目的是借助于“第三方”(Spring 中的 IoC 容器) 实现具有依赖关系的对象之间的解耦(IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。
**IoC 是一个原则,而不是一个模式,以下模式(但不限于)实现了 IoC 原则。**

**Spring IO C 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** IO C 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。
**Spring Io C 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** Io C 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。
在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service, 你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。关于Spring IOC 的理解,推荐看这一下知乎的一个回答:< https: / / www . zhihu . com / question / 23277575 / answer / 169698662 > ,非常不错。
在实际项目中一个 Service 类如果有几百甚至上千个类作为它的底层,我们需要实例化这个 Service, 你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IOC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
**控制反转怎么理解呢?** 举个例子:"对象a 依赖了对象 b, 当对象 a 需要使用 对象 b的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b的时候, 我们可以指定 IOC 容器去创建一个对象b注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转,这就是控制反转名字的由来。
> 关于 Spring IOC 的理解,推荐看这一下知乎的一个回答:< https: / / www . zhihu . com / question / 23277575 / answer / 169698662 > ,非常不错。
**控制反转怎么理解呢?** 举个例子:"对象 a 依赖了对象 b, 当对象 a 需要使用 对象 b 的时候必须自己去创建。但是当系统引入了 IOC 容器后, 对象 a 和对象 b 之前就失去了直接的联系。这个时候,当对象 a 需要使用 对象 b 的时候, 我们可以指定 IOC 容器去创建一个对象 b 注入到对象 a 中"。 对象 a 获得依赖对象 b 的过程,由主动行为变为了被动行为,控制权反转,这就是控制反转名字的由来。
**DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。**
## 工厂设计模式
Spring使用工厂模式可以通过 `BeanFactory` 或 `ApplicationContext` 创建 bean 对象。
Spring 使用工厂模式可以通过 `BeanFactory` 或 `ApplicationContext` 创建 bean 对象。
**两者对比:**
- `BeanFactory` :延迟注入(使用到某个 bean 的时候才会注入),相比于`ApplicationContext` 来说会占用更少的内存,程序启动速度更快。
- `ApplicationContext` :容器启动的时候,不管你用没用到,一次性创建所有 bean 。`BeanFactory` 仅提供了最基本的依赖注入支持,` ApplicationContext` 扩展了 `BeanFactory` ,除了有`BeanFactory` 的功能还有额外更多功能,所以一般开发人员使用` ApplicationContext`会更多。
- `BeanFactory` :延迟注入(使用到某个 bean 的时候才会注入),相比于`ApplicationContext` 来说会占用更少的内存,程序启动速度更快。
- `ApplicationContext` :容器启动的时候,不管你用没用到,一次性创建所有 bean 。`BeanFactory` 仅提供了最基本的依赖注入支持,` ApplicationContext` 扩展了 `BeanFactory` ,除了有`BeanFactory` 的功能还有额外更多功能,所以一般开发人员使用` ApplicationContext`会更多。
ApplicationContext的三个实现类:
` ApplicationContext` 的三个实现类:
1. `ClassPathXmlApplication` :把上下文文件当成类路径资源。
2. `FileSystemXmlApplication` :从文件系统中的 XML 文件载入上下文定义信息。
3. `XmlWebApplicationContext` : 从Web系统中的XML文件载入上下文定义信息。
2. `FileSystemXmlApplication` :从文件系统中的 XML 文件载入上下文定义信息。
3. `XmlWebApplicationContext` :从 Web 系统中的 XML 文件载入上下文定义信息。
Example:
```java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
obj.getMsg();
}
@ -59,24 +65,22 @@ public class App {
在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
**使用单例模式的好处: **
**使用单例模式的好处** :
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
**Spring 中 bean 的默认作用域就是 singleton(单例)的。** 除了 singleton 作用域, Spring 中 bean 还有下面几种作用域:
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean, 该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean, 该bean仅在当前 HTTP session 内有效。
- global-session: 全局session作用域, 仅仅在基于portlet的web应用中才有意义, Spring5已经没有了。Portlet是能够生成语义代码(例如: HTML)片段的小型Java Web插件。它们基于portlet容器, 可以像servlet一样处理HTTP请求。但是, 与 servlet 不同,每个 portlet 都有不同的会话
- **prototype** : 每次获取都会创建一个新的 bean 实例。也就是说,连续 `getBean()` 两次,得到的是不同的 Bean 实例。
- **request** (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean( 请求 bean) , 该 bean 仅在当前 HTTP request 内有效。
- **session** (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean( 会话 bean) , 该 bean 仅在当前 HTTP session 内有效。
- **application/global-session** (仅 Web 应用可用): 每个 Web 应用在启动时创建一个 Bean( 应用 Bean) , , 该 bean 仅在当前应用启动时间内有效。
- **websocket** (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
**Spring 实现单例的方式:**
Spring 通过 `ConcurrentHashMap` 实现单例注册表的特殊方式实现单例模式。
- xml : `<bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>`
- 注解:`@Scope(value = "singleton")`
**Spring 通过 `ConcurrentHashMap` 实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下**
Spring 实现单例的核心代码如下:
```java
// 通过 ConcurrentHashMap( 线程安全) 实现单例注册表
@ -85,7 +89,7 @@ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<Strin
public Object getSingleton(String beanName, ObjectFactory< ?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代码
@ -109,17 +113,28 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
}
```
**单例 Bean 存在线程安全问题吗?**
大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
常见的有两种解决办法:
1. 在 Bean 中尽量避免定义可变的成员变量。
2. 在类中定义一个 `ThreadLocal` 成员变量,将需要的可变成员变量保存在 `ThreadLocal` 中(推荐的一种方式)。
不过,大部分 Bean 实际都是无状态(没有实例变量)的(比如 Dao、Service) , 这种情况下, Bean 是线程安全的。
## 代理设计模式
### 代理模式在 AOP 中的应用
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**, **降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。
**AOP(Aspect-Oriented Programming, 面向切面编程)** 能够将那些与业务无关, 却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
**Spring AOP 就是基于动态代理的**, 如果要代理的对象, 实现了某个接口, 那么Spring AOP会使用**JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了, 这时候Spring AOP会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示:
**Spring AOP 就是基于动态代理的**,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy** 去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示:

当然你也可以使用 AspectJ ,Spring AOP 已经集成了AspectJ , AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
当然, 你也可以使用 AspectJ ,Spring AOP 已经集成了 AspectJ , AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。
@ -127,9 +142,9 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ , AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
Spring AOP 已经集成了 AspectJ , AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ , 它比Spring AOP 快很多。
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
## 模板方法
@ -139,7 +154,7 @@ AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无
public abstract class Template {
//这是我们的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
@ -147,7 +162,7 @@ public abstract class Template {
protected void PrimitiveOperation1(){
//当前类实现
}
//被子类实现的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
@ -159,7 +174,7 @@ public class TemplateImpl extends Template {
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
@ -168,7 +183,7 @@ public class TemplateImpl extends Template {
```
Spring 中 ` jdbcTemplate`、`h ibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式, 而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
Spring 中 ` JdbcTemplate`、`H ibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用 Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
## 观察者模式
@ -178,7 +193,7 @@ Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数
#### 事件角色
`ApplicationEvent` (`org.springframework.context` 包下)充当事件的角色,这是一个抽象类,它继承了`java.util.EventObject` 并实现了 `java.io.Serializable` 接口。
`ApplicationEvent` (`org.springframework.context` 包下)充当事件的角色,这是一个抽象类,它继承了`java.util.EventObject` 并实现了 `java.io.Serializable` 接口。
Spring 中默认存在以下事件,他们都是对 `ApplicationContextEvent` 的实现(继承自`ApplicationContextEvent` ):
@ -191,7 +206,7 @@ Spring 中默认存在以下事件,他们都是对 `ApplicationContextEvent`
#### 事件监听者角色
`ApplicationListener` 充当了事件监听者角色,它是一个接口,里面只定义了一个 `onApplicationEvent( ) ` 方法来处理`ApplicationEvent` 。`ApplicationListener` 接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 `ApplicationEvent` 就可以了。所以,在 Spring中我们只要实现 `ApplicationListener` 接口的 `onApplicationEvent()` 方法即可完成监听事件
`ApplicationListener` 充当了事件监听者角色,它是一个接口,里面只定义了一个 `onApplicationEvent( ) ` 方法来处理`ApplicationEvent` 。`ApplicationListener` 接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 `ApplicationEvent` 就可以了。所以,在 Spring 中我们只要实现 `ApplicationListener` 接口的 `onApplicationEvent()` 方法即可完成监听事件
```java
package org.springframework.context;
@ -224,7 +239,7 @@ public interface ApplicationEventPublisher {
1. 定义一个事件: 实现一个继承自 `ApplicationEvent` ,并且写相应的构造函数;
2. 定义一个事件监听者:实现 `ApplicationListener` 接口,重写 `onApplicationEvent()` 方法;
3. 使用事件发布者发布消息: 可以通过 `ApplicationEventPublisher ` 的 `publishEvent()` 方法发布消息。
3. 使用事件发布者发布消息: 可以通过 `ApplicationEventPublisher ` 的 `publishEvent()` 方法发布消息。
Example:
@ -244,7 +259,7 @@ public class DemoEvent extends ApplicationEvent{
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口, 重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener< DemoEvent > {
@ -272,41 +287,43 @@ public class DemoPublisher {
```
当调用 `DemoPublisher ` 的 `publish()` 方法的时候,比如 `demoPublisher.publish("你好")` ,控制台就会打印出:`接收到的信息是:你好` 。
当调用 `DemoPublisher ` 的 `publish()` 方法的时候,比如 `demoPublisher.publish("你好")` ,控制台就会打印出:`接收到的信息是:你好` 。
## 适配器模式
适配器模式(Adapter Pattern) 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作。
### Spring AOP中的适配器模式
### Spring AOP 中的适配器模式
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是`AdvisorAdapter ` 。
我们知道 Spring AOP 的实现是基于代理模式,但是 Spring AOP 的增强或通知(Advice)使用到了适配器模式,与之相关的接口是`AdvisorAdapter ` 。
Advice 常用的类型有:`BeforeAdvice` (目标方法调用前,前置通知)、`AfterAdvice` (目标方法调用后,后置通知)、`AfterReturningAdvice` (目标方法执行结束后, return之前)等等。每个类型Advice( 通知) 都有对应的拦截器:`MethodBeforeAdviceInterceptor` 、`AfterReturningAdviceInterceptor` 、`ThrowsAdviceInterceptor` 等等。
Advice 常用的类型有:`BeforeAdvice` (目标方法调用前,前置通知)、`AfterAdvice` (目标方法调用后,后置通知)、`AfterReturningAdvice` (目标方法执行结束后, return 之前)等等。每个类型 Advice( 通知) 都有对应的拦截器:`MethodBeforeAdviceInterceptor` 、`AfterReturningAdviceInterceptor` 、`ThrowsAdviceInterceptor` 等等。
Spring 预定义的通知要通过对应的适配器,适配成 `MethodInterceptor` 接口(方法拦截器)类型的对象(如:`MethodBeforeAdviceAdapter` 通过调用 `getInterceptor` 方法,将 `MethodBeforeAdvice` 适配成 `MethodBeforeAdviceInterceptor` )。
### Spring MVC中的适配器模式
### Spring MVC 中的适配器模式
在Spring MVC中, `DispatcherServlet` 根据请求信息调用 `HandlerMapping` ,解析请求对应的 `Handler` 。解析到对应的 `Handler` (也就是我们平常说的 `Controller` 控制器)后,开始由`HandlerAdapter` 适配器处理。`HandlerAdapter` 作为期望接口,具体的适配器实现类用于对目标类进行适配,`Controller` 作为需要适配的类。
在 Spring MVC 中,`DispatcherServlet` 根据请求信息调用 `HandlerMapping` ,解析请求对应的 `Handler` 。解析到对应的 `Handler` (也就是我们平常说的 `Controller` 控制器)后,开始由`HandlerAdapter` 适配器处理。`HandlerAdapter` 作为期望接口,具体的适配器实现类用于对目标类进行适配,`Controller` 作为需要适配的类。
**为什么要在 Spring MVC 中使用适配器模式?** Spring MVC 中的 `Controller` 种类众多,不同类型的 `Controller` 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,`DispatcherServlet` 直接获取对应类型的 `Controller` ,需要的自行来判断,像下面这段代码一样:
**为什么要在 Spring MVC 中使用适配器模式?**
Spring MVC 中的 `Controller` 种类众多,不同类型的 `Controller` 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,`DispatcherServlet` 直接获取对应类型的 `Controller` ,需要的自行来判断,像下面这段代码一样:
```java
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
```
假如我们再增加一个 `Controller` 类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
## 装饰者模式
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承, 装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能, 但我们又不愿直接去修改原有的代码时, 设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 `InputStream` 家族,`InputStream` 类下有 `FileInputStream` (读取文件)、`BufferedInputStream` (增加缓存,使读取文件速度大大提升)等子类都在不修改`InputStream` 代码的情况下扩展了它的功能。
装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个 Decorator 套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 `InputStream` 家族,`InputStream` 类下有 `FileInputStream` (读取文件)、`BufferedInputStream` (增加缓存,使读取文件速度大大提升)等子类都在不修改`InputStream` 代码的情况下扩展了它的功能。

@ -316,7 +333,7 @@ Spring 中配置 DataSource 的时候, DataSource 可能是不同的数据库
Spring 框架中用到了哪些设计模式?
- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory` 、`ApplicationContext` 创建 bean 对象。
- **工厂设计模式** : Spring 使用工厂模式通过 `BeanFactory` 、`ApplicationContext` 创建 bean 对象。
- **代理设计模式** : Spring AOP 功能的实现。
- **单例设计模式** : Spring 中的 Bean 默认都是单例的。
- **模板方法模式** : Spring 中 `jdbcTemplate` 、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
@ -327,20 +344,10 @@ Spring 框架中用到了哪些设计模式?
## 参考
- 《Spring技术内幕》
- 《Spring 技术内幕》
- < https: / / blog . eduonix . com / java-programming-2 / learn-design-patterns-used-spring-framework / >
- < http: / / blog . yeamin . top / 2018 / 03 / 27 / 单例模式-Spring单例实现原理分析 / >
- < https: / / www . tutorialsteacher . com / ioc / inversion-of-control >
- < https: / / www . tutorialsteacher . com / ioc / inversion-of-control >
- < https: / / design-patterns . readthedocs . io / zh_CN / latest / behavioral_patterns / observer . html >
- < https: / / juejin . im / post / 5a8eb261f265da4e9e307230 >
- < https: / / juejin . im / post / 5ba28986f265da0abc2b6084 >
## 公众号
如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号 ](#公众号 )后台回复 ** "Java面试突击"** 即可免费领取!
**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 ** “1”** 即可免费无套路获取。
