GVKun编程网logo

Spring 框架的设计理念与设计模式分析(spring 框架的设计理念与设计模式分析论文)

33

在这篇文章中,我们将为您详细介绍Spring框架的设计理念与设计模式分析的内容,并且讨论关于spring框架的设计理念与设计模式分析论文的相关问题。此外,我们还会涉及一些关于1.SpringMVC设计

在这篇文章中,我们将为您详细介绍Spring 框架的设计理念与设计模式分析的内容,并且讨论关于spring 框架的设计理念与设计模式分析论文的相关问题。此外,我们还会涉及一些关于1.SpringMVC 设计理念与 DispatcherServlet、Flink keyBy 算子源码与设计理念分析、Golang 语言的设计理念与特性解析、golang框架源码中的设计理念的知识,以帮助您更全面地了解这个主题。

本文目录一览:

Spring 框架的设计理念与设计模式分析(spring 框架的设计理念与设计模式分析论文)

Spring 框架的设计理念与设计模式分析(spring 框架的设计理念与设计模式分析论文)

简介: 

Spring 作为现在最优秀的框架之一,已被广泛的使用,并且有很多对其分析的文章。本文将从另外一个视角试图剖析出 Spring 框架的作者设计 Spring 框架的骨骼架构的设计理念,有那几个核心组件?为什么需要这些组件?它们又是如何结合在一起构成 Spring 的骨骼架构? Spring 的 AOP 特性又是如何利用这些基础的骨骼架构来工作的? Spring 中又使用了那些设计模式来完成它的这种设计的?它的这种设计理念对对我们以后的软件设计有何启示?本文将详细解答这些问题。

Spring 的骨骼架构

Spring 总共有十几个组件,但是真正核心的组件只有几个,下面是 Spring 框架的总体架构图:

图 1 .Spring 框架的总体架构图

从上图中可以看出 Spring 框架中的核心组件只有三个:Core、Context 和 Beans。它们构建起了整个 Spring 的骨骼架构。没有它们就不可能有 AOP、Web 等上层的特性功能。下面也将主要从这三个组件入手分析 Spring。

Spring 的设计理念

前面介绍了 Spring 的三个核心组件,如果再在它们三个中选出核心的话,那就非 Beans 组件莫属了,为何这样说,其实 Spring 就是面向 Bean 的编程(BOP,Bean Oriented Programming),Bean 在 Spring 中才是真正的主角。

Bean 在 Spring 中作用就像 Object 对 OOP 的意义一样,没有对象的概念就像没有面向对象编程,Spring 中没有 Bean 也就没有 Spring 存在的意义。就像一次演出舞台都准备好了但是却没有演员一样。为什么要 Bean 这种角色 Bean 或者为何在 Spring 如此重要,这由 Spring 框架的设计目标决定,Spring 为何如此流行,我们用 Spring 的原因是什么,想想你会发现原来 Spring 解决了一个非常关键的问题他可以让你把对象之间的依赖关系转而用配置文件来管理,也就是他的依赖注入机制。而这个注入关系在一个叫 Ioc 容器中管理,那 Ioc 容器中有又是什么就是被 Bean 包裹的对象。Spring 正是通过把对象包装在 Bean 中而达到对这些对象管理以及一些列额外操作的目的。

它这种设计策略完全类似于 Java 实现 OOP 的设计理念,当然了 Java 本身的设计要比 Spring 复杂太多太多,但是都是构建一个数据结构,然后根据这个数据结构设计他的生存环境,并让它在这个环境中按照一定的规律在不停的运动,在它们的不停运动中设 计一系列与环境或者与其他个体完成信息交换。这样想来回过头想想我们用到的其他框架都是大慨类似的设计理念。

核心组件如何协同工作

前面说 Bean 是 Spring 中关键因素,那 Context 和 Core 又有何作用呢?前面吧 Bean 比作一场演出中的演员的话,那 Context 就是这场演出的舞台背景,而 Core 应该就是演出的道具了。只有他们在一起才能具备能演出一场好戏的最基本的条件。当然有最基本的条件还不能使这场演出脱颖而出,还要他表演的节目足够的精 彩,这些节目就是 Spring 能提供的特色功能了。

我们知道 Bean 包装的是 Object,而 Object 必然有数据,如何给这些数据提供生存环境就是 Context 要解决的问题,对 Context 来说他就是要发现每个 Bean 之间的关系,为它们建立这种关系并且要维护好这种关系。所以 Context 就是一个 Bean 关系的集合,这个关系集合又叫 Ioc 容器,一旦建立起这个 Ioc 容器后 Spring 就可以为你工作了。那 Core 组件又有什么用武之地呢?其实 Core 就是发现、建立和维护每个 Bean 之间的关系所需要的一些列的工具,从这个角度看来,Core 这个组件叫 Util 更能让你理解。

它们之间可以用下图来表示:


图 2. 三个组件关系

核心组件详解

这里将详细介绍每个组件内部类的层次关系,以及它们在运行时的时序顺序。我们在使用 Spring 是应该注意的地方。

Bean 组件

前面已经说明了 Bean 组件对 Spring 的重要性,下面看看 Bean 这个组件式怎么设计的。Bean 组件在 Spring 的 org.springframework.beans 包下。这个包下的所有类主要解决了三件事:Bean 的定义、Bean 的创建以及对 Bean 的解析。对 Spring 的使用者来说唯一需要关心的就是 Bean 的创建,其他两个由 Spring 在内部帮你完成了,对你来说是透明的。

Spring Bean 的创建时典型的工厂模式,他的顶级接口是 BeanFactory,下图是这个工厂的继承层次关系:


图 4. Bean 工厂的继承关系

BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。但是从上图中我们可以发现最终的默认实现类是 DefaultListableBeanFactory,他实现了所有的接口。那为何要定义这么多层次的接口呢?查阅这些接口的源码和说明发现,每个接口 都有他使用的场合,它主要是为了区分在 Spring 内部在操作过程中对象的传递和转化过程中,对对象的数据访问所做的限制。例如 ListableBeanFactory 接口表示这些 Bean 是可列表的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这四个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为。

Bean 的定义主要有 BeanDefinition 描述,如下图说明了这些类的层次关系:


图 5. Bean 定义的类层次关系图

Bean 的定义就是完整的描述了在 Spring 的配置文件中你定义的 <bean/> 节点中所有的信息,包括各种子节点。当 Spring 成功解析你定义的一个 <bean/> 节点后,在 Spring 的内部他就被转化成 BeanDefinition 对象。以后所有的操作都是对这个对象完成的。

Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被扩展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:


图 6. Bean 的解析类

当然还有具体对 tag 的解析这里并没有列出。

Context 组件

Context 在 Spring 的 org.springframework.context 包下,前面已经讲解了 Context 组件在 Spring 中的作用,他实际上就是给 Spring 提供一个运行时的环境,用以保存各个对象的状态。下面看一下这个环境是如何构建的。

ApplicationContext 是 Context 的顶级父类,他除了能标识一个应用环境的基本信息外,他还继承了五个接口,这五个接口主要是扩展了 Context 的功能。下面是 Context 的类结构图:


图 7. Context 相关的类结构图

(查看 图 7 的清晰版本。)

从上图中可以看出 ApplicationContext 继承了 BeanFactory,这也说明了 Spring 容器中运行的主体对象是 Bean,另外 ApplicationContext 继承了 ResourceLoader 接口,使得 ApplicationContext 可以访问到任何外部资源,这将在 Core 中详细说明。

ApplicationContext 的子类主要包含两个方面:

  1. ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息,它下面又有多个子类,其中最经常使用的是可更新的 Context,即 AbstractRefreshableApplicationContext 类。
  2. WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用的少。

再往下分就是按照构建 Context 的文件类型,接着就是访问 Context 的方式。这样一级一级构成了完整的 Context 等级层次。

总体来说 ApplicationContext 必须要完成以下几件事:

  • 标识一个应用环境
  • 利用 BeanFactory 创建 Bean 对象
  • 保存对象关系表
  • 能够捕获各种事件

Context 作为 Spring 的 Ioc 容器,基本上整合了 Spring 的大部分功能,或者说是大部分功能的基础。

Core 组件

Core 组件作为 Spring 的核心组件,他其中包含了很多的关键类,其中一个重要组成部分就是定义了资源的访问方式。这种把所有资源都抽象成一个接口的方式很值得在以后的设计中拿来 学习。下面就重要看一下这个部分在 Spring 的作用。

下图是 Resource 相关的类结构图:


图 8. Resource 相关的类结构图

(查看 图 8 的清晰版本。)

从上图可以看出 Resource 接口封装了各种可能的资源类型,也就是对使用者来说屏蔽了文件类型的不同。对资源的提供者来说,如何把资源包装起来交给其他人用这也是一个问题,我们看到 Resource 接口继承了 InputStreamSource 接口,这个接口中有个 getInputStream 方法,返回的是 InputStream 类。这样所有的资源都被可以通过 InputStream 这个类来获取,所以也屏蔽了资源的提供者。另外还有一个问题就是加载资源的问题,也就是资源的加载者要统一,从上图中可以看出这个任务是由 ResourceLoader 接口完成,他屏蔽了所有的资源加载者的差异,只需要实现这个接口就可以加载所有的资源,他的默认实现是 DefaultResourceLoader。

下面看一下 Context 和 Resource 是如何建立关系的?首先看一下他们的类关系图:


图 9. Context 和 Resource 的类关系图

从上图可以看出,Context 是把资源的加载、解析和描述工作委托给了 ResourcePatternResolver 类来完成,他相当于一个接头人,他把资源的加载、解析和资源的定义整合在一起便于其他组件使用。Core 组件中还有很多类似的方式。

Ioc 容器如何工作

前面介绍了 Core 组件、Bean 组件和 Context 组件的结构与相互关系,下面这里从使用者角度看一下他们是如何运行的,以及我们如何让 Spring 完成各种功能,Spring 到底能有那些功能,这些功能是如何得来的,下面介绍。

如何创建 BeanFactory 工厂

正如图 2 描述的那样,Ioc 容器实际上就是 Context 组件结合其他两个组件共同构建了一个 Bean 关系网,如何构建这个关系网?构建的入口就在 AbstractApplicationContext 类的 refresh 方法中。这个方法的代码如下:


清单 1. AbstractApplicationContext.refresh


public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset ''active'' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}

 

这个方法就是构建整个 Ioc 容器过程的完整的代码,了解了里面的每一行代码基本上就了解大部分 Spring 的原理和功能了。

这段代码主要包含这样几个步骤:

  • 构建 BeanFactory,以便于产生所需的“演员”
  • 注册可能感兴趣的事件
  • 创建 Bean 实例对象
  • 触发被监听的事件

下面就结合代码分析这几个过程。

第二三句就是在创建和配置 BeanFactory。这里是 refresh 也就是刷新配置,前面介绍了 Context 有可更新的子类,这里正是实现这个功能,当 BeanFactory 已存在是就更新,如果没有就新创建。下面是更新 BeanFactory 的方法代码:


清单 2. AbstractRefreshableApplicationContext. refreshBeanFactory


protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException(
"I/O error parsing bean definition source for "
+ getDisplayName(), ex);
}
}

 

这个方法实现了 AbstractApplicationContext 的抽象方法 refreshBeanFactory,这段代码清楚的说明了 BeanFactory 的创建过程。注意 BeanFactory 对象的类型的变化,前面介绍了他有很多子类,在什么情况下使用不同的子类这非常关键。BeanFactory 的原始对象是 DefaultListableBeanFactory,这个非常关键,因为他设计到后面对这个对象的多种操作,下面看一下这个类的继承层次类图:


图 10. DefaultListableBeanFactory 类继承关系图

(查看 图 10 的清晰版本。)

从这个图中发现除了 BeanFactory 相关的类外,还发现了与 Bean 的 register 相关。这在 refreshBeanFactory 方法中有一行 loadBeanDefinitions(beanFactory) 将找到答案,这个方法将开始加载、解析 Bean 的定义,也就是把用户定义的数据结构转化为 Ioc 容器中的特定数据结构。

这个过程可以用下面时序图解释:


图 11. 创建 BeanFactory 时序图

(查看 图 11 的清晰版本。)

Bean 的解析和登记流程时序图如下:


图 12. 解析和登记 Bean 对象时序图

(查看 图 12 的清晰版本。)

创建好 BeanFactory 后,接下去添加一些 Spring 本身需要的一些工具类,这个操作在 AbstractApplicationContext 的 prepareBeanFactory 方法完成。

AbstractApplicationContext 中接下来的三行代码对 Spring 的功能扩展性起了至关重要的作用。前两行主要是让你现在可以对已经构建的 BeanFactory 的配置做修改,后面一行就是让你可以对以后再创建 Bean 的实例对象时添加一些自定义的操作。所以他们都是扩展了 Spring 的功能,所以我们要学习使用 Spring 必须对这一部分搞清楚。

其中在 invokeBeanFactoryPostProcessors 方法中主要是获取实现 BeanFactoryPostProcessor 接口的子类。并执行它的 postProcessBeanFactory 方法,这个方法的声明如下:


清单 3. BeanFactoryPostProcessor.postProcessBeanFactory


void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException;

 

它的参数是 beanFactory,说明可以对 beanFactory 做修改,这里注意这个 beanFactory 是 ConfigurableListableBeanFactory 类型的,这也印证了前面介绍的不同 BeanFactory 所使用的场合不同,这里只能是可配置的 BeanFactory,防止一些数据被用户随意修改。

registerBeanPostProcessors 方法也是可以获取用户定义的实现了 BeanPostProcessor 接口的子类,并执行把它们注册到 BeanFactory 对象中的 beanPostProcessors 变量中。BeanPostProcessor 中声明了两个方法:postProcessBeforeInitialization、postProcessAfterInitialization 分别用于在 Bean 对象初始化时执行。可以执行用户自定义的操作。

后面的几行代码是初始化监听事件和对系统的其他监听者的注册,监听者必须是 ApplicationListener 的子类。

如何创建 Bean 实例并构建 Bean 的关系网

下面就是 Bean 的实例化代码,是从 finishBeanFactoryInitialization 方法开始的。


清单 4. AbstractApplicationContext.finishBeanFactoryInitialization


protected void finishBeanFactoryInitialization(
ConfigurableListableBeanFactory beanFactory) {

// Stop using the temporary ClassLoader for type matching.
beanFactory.setTempClassLoader(null);

// Allow for caching all bean definition metadata, not expecting further changes.
beanFactory.freezeConfiguration();

// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}

 

从上面代码中可以发现 Bean 的实例化是在 BeanFactory 中发生的。preInstantiateSingletons 方法的代码如下:


清单 5. DefaultListableBeanFactory.preInstantiateSingletons


public void preInstantiateSingletons() throws BeansException {
if (this.logger.isInfoEnabled()) {
this.logger.info("Pre-instantiating singletons in " + this);
}
synchronized (this.beanDefinitionMap) {
for (String beanName : this.beanDefinitionNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton()
&& !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean factory =
(FactoryBean) getBean(FACTORY_BEAN_PREFIX+ beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null
&& factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
new PrivilegedAction<Boolean>() {
public Boolean run() {
return ((SmartFactoryBean) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = factory instanceof SmartFactoryBean
&& ((SmartFactoryBean) factory).isEagerInit();
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
}
}

 

这里出现了一个非常重要的 Bean —— FactoryBean,可以说 Spring 一大半的扩展的功能都与这个 Bean 有关,这是个特殊的 Bean 他是个工厂 Bean,可以产生 Bean 的 Bean,这里的产生 Bean 是指 Bean 的实例,如果一个类继承 FactoryBean 用户可以自己定义产生实例对象的方法只要实现他的 getObject 方法。然而在 Spring 内部这个 Bean 的实例对象是 FactoryBean,通过调用这个对象的 getObject 方法就能获取用户自定义产生的对象,从而为 Spring 提供了很好的扩展性。Spring 获取 FactoryBean 本身的对象是在前面加上 & 来完成的。

如何创建 Bean 的实例对象以及如何构建 Bean 实例对象之间的关联关系式 Spring 中的一个核心关键,下面是这个过程的流程图。


图 13.Bean 实例创建流程图

(查看 图 13 的清晰版本。)

如果是普通的 Bean 就直接创建他的实例,是通过调用 getBean 方法。下面是创建 Bean 实例的时序图:


图 14.Bean 实例创建时序图

(查看 图 14 的清晰版本。)

还有一个非常重要的部分就是建立 Bean 对象实例之间的关系,这也是 Spring 框架的核心竞争力,何时、如何建立他们之间的关系请看下面的时序图:


图 15.Bean 对象关系建立

(查看 图 15 的清晰版本。)

Ioc 容器的扩展点

现在还有一个问题就是如何让这些 Bean 对象有一定的扩展性,就是可以加入用户的一些操作。那么有哪些扩展点呢? Spring 又是如何调用到这些扩展点的?

对 Spring 的 Ioc 容器来说,主要有这么几个。BeanFactoryPostProcessor, BeanPostProcessor。他们分别是在构建 BeanFactory 和构建 Bean 对象时调用。还有就是 InitializingBean 和 DisposableBean 他们分别是在 Bean 实例创建和销毁时被调用。用户可以实现这些接口中定义的方法,Spring 就会在适当的时候调用他们。还有一个是 FactoryBean 他是个特殊的 Bean,这个 Bean 可以被用户更多的控制。

这些扩展点通常也是我们使用 Spring 来完成我们特定任务的地方,如何精通 Spring 就看你有没有掌握好 Spring 有哪些扩展点,并且如何使用他们,要知道如何使用他们就必须了解他们内在的机理。可以用下面一个比喻来解释。

我们把 Ioc 容器比作一个箱子,这个箱子里有若干个球的模子,可以用这些模子来造很多种不同的球,还有一个造这些球模的机器,这个机器可以产生球模。那么他们的对应关 系就是 BeanFactory 就是那个造球模的机器,球模就是 Bean,而球模造出来的球就是 Bean 的实例。那前面所说的几个扩展点又在什么地方呢? BeanFactoryPostProcessor 对应到当造球模被造出来时,你将有机会可以对其做出设当的修正,也就是他可以帮你修改球模。而 InitializingBean 和 DisposableBean 是在球模造球的开始和结束阶段,你可以完成一些预备和扫尾工作。BeanPostProcessor 就可以让你对球模造出来的球做出适当的修正。最后还有一个 FactoryBean,它可是一个神奇的球模。这个球模不是预先就定型了,而是由你来给他确定它的形状,既然你可以确定这个球模型的形状,当然他造出来 的球肯定就是你想要的球了,这样在这个箱子里尼可以发现所有你想要的球

Ioc 容器如何为我所用

前面的介绍了 Spring 容器的构建过程,那 Spring 能为我们做什么,Spring 的 Ioc 容器又能做什么呢?我们使用 Spring 必须要首先构建 Ioc 容器,没有它 Spring 无法工作,ApplicatonContext.xml 就是 Ioc 容器的默认配置文件,Spring 的所有特性功能都是基于这个 Ioc 容器工作的,比如后面要介绍的 AOP。

Ioc 它实际上就是为你构建了一个魔方,Spring 为你搭好了骨骼架构,这个魔方到底能变出什么好的东西出来,这必须要有你的参与。那我们怎么参与?这就是前面说的要了解 Spring 中那有些扩展点,我们通过实现那些扩展点来改变 Spring 的通用行为。至于如何实现扩展点来得到我们想要的个性结果,Spring 中有很多例子,其中 AOP 的实现就是 Spring 本身实现了其扩展点来达到了它想要的特性功能,可以拿来参考。


回页首

Spring 中 AOP 特性详解

动态代理的实现原理

要了解 Spring 的 AOP 就必须先了解的动态代理的原理,因为 AOP 就是基于动态代理实现的。动态代理还要从 JDK 本身说起。

在 Jdk 的 java.lang.reflect 包下有个 Proxy 类,它正是构造代理类的入口。这个类的结构入下:


图 16. Proxy 类结构

从上图发现最后面四个是公有方法。而最后一个方法 newProxyInstance 就是创建代理对象的方法。这个方法的源码如下:


清单 6. Proxy. newProxyInstance


public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {

if (h == null) {
throw new NullPointerException();
}
Class cl = getProxyClass(loader, interfaces);
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}

 

这个方法需要三个参数:ClassLoader,用于加载代理类的 Loader 类,通常这个 Loader 和被代理的类是同一个 Loader 类。Interfaces,是要被代理的那些那些接口。InvocationHandler,就是用于执行除了被代理接口中方法之外的用户自定义的操作, 他也是用户需要代理的最终目的。用户调用目标方法都被代理到 InvocationHandler 类中定义的唯一方法 invoke 中。这在后面再详解。

下面还是看看 Proxy 如何产生代理类的过程,他构造出来的代理类到底是什么样子?下面揭晓啦。


图 17. 创建代理对象时序图

其实从上图中可以发现正在构造代理类的是在 ProxyGenerator 的 generateProxyClass 的方法中。ProxyGenerator 类在 sun.misc 包下,感兴趣的话可以看看他的源码。

假如有这样一个接口,如下:


清单 7. SimpleProxy 类


public interface SimpleProxy {

public void simpleMethod1();

public void simpleMethod2();

}

 

代理来生成的类结构如下:


清单 8. $Proxy2 类


public class $Proxy2 extends java.lang.reflect.Proxy implements SimpleProxy{
java.lang.reflect.Method m0;
java.lang.reflect.Method m1;
java.lang.reflect.Method m2;
java.lang.reflect.Method m3;
java.lang.reflect.Method m4;

int hashCode();
boolean equals(java.lang.Object);
java.lang.String toString();
void simpleMethod1();
void simpleMethod2();
}

 

这个类中的方法里面将会是调用 InvocationHandler 的 invoke 方法,而每个方法也将对应一个属性变量,这个属性变量 m 也将传给 invoke 方法中的 Method 参数。整个代理就是这样实现的。

Spring AOP 如何实现

从前面代理的原理我们知道,代理的目的是调用目标方法时我们可以转而执行 InvocationHandler 类的 invoke 方法,所以如何在 InvocationHandler 上做文章就是 Spring 实现 Aop 的关键所在。

Spring 的 Aop 实现是遵守 Aop 联盟的约定。同时 Spring 又扩展了它,增加了如 Pointcut、Advisor 等一些接口使得更加灵活。

下面是 Jdk 动态代理的类图:


图 18. Jdk 动态代理的类图

上图清楚的显示了 Spring 引用了 Aop Alliance 定义的接口。姑且不讨论 Spring 如何扩展 Aop Alliance,先看看 Spring 如何实现代理类的,要实现代理类在 Spring 的配置文件中通常是这样定一个 Bean 的,如下:


清单 9. 配置代理类 Bean


<bean id="testBeanSingleton"
>
<property name="proxyInterfaces">
<value>
org.springframework.aop.framework.PrototypeTargetTests$TestBean
</value>
</property>
<property name="target"><ref local="testBeanTarget"></ref> </property>
<property name="singleton"><value>true</value></property>
<property name="interceptorNames">
<list>
<value>testInterceptor</value>
<value>testInterceptor2</value>
</list>
</property>
</bean>

 

配置上看到要设置被代理的接口,和接口的实现类也就是目标类,以及拦截器也就在执行目标方法之前被调用,这里 Spring 中定义的各种各样的拦截器,可以选择使用。

下面看看 Spring 如何完成了代理以及是如何调用拦截器的。

前面提到 Spring Aop 也是实现其自身的扩展点来完成这个特性的,从这个代理类可以看出它正是继承了 FactoryBean 的 ProxyFactoryBean,FactoryBean 之所以特别就在它可以让你自定义对象的创建方法。当然代理对象要通过 Proxy 类来动态生成。

下面是 Spring 创建的代理对象的时序图:


图 19.Spring 代理对象的产生

Spring 创建了代理对象后,当你调用目标对象上的方法时,将都会被代理到 InvocationHandler 类的 invoke 方法中执行,这在前面已经解释。在这里 JdkDynamicAopProxy 类实现了 InvocationHandler 接口。

下面再看看 Spring 是如何调用拦截器的,下面是这个过程的时序图:


图 20.Spring 调用拦截器

以上所说的都是 Jdk 动态代理,Spring 还支持一种 CGLIB 类代理,感兴趣自己看吧。


回页首

Spring 中设计模式分析

Spring 中使用的设计模式也很多,比如工厂模式、单例模式、模版模式等,在《 Webx 框架的系统架构与设计模式》、《 Tomcat 的系统架构与模式设计分析》已经有介绍,这里就不赘述了。这里主要介绍代理模式和策略模式。

代理模式

代理模式原理

代理模式就是给某一个对象创建一个代理对象,而由这个代理对象控制对原对象的引用,而创建这个代理对象就是可以在调用原对象是可以增加一些额 外的操作。下面是代理模式的结构:


图 21. 代理模式的结构

  • Subject:抽象主题,它是代理对象的真实对象要实现的接口,当然这可以是多个接口组成。
  • ProxySubject:代理类除了实现抽象主题定义的接口外,还必须持有所代理对象的引用
  • RealSubject:被代理的类,是目标对象。

Spring 中如何实现代理模式

Spring Aop 中 Jdk 动态代理就是利用代理模式技术实现的。在 Spring 中除了实现被代理对象的接口外,还会有 org.springframework.aop.SpringProxy 和 org.springframework.aop.framework.Advised 两个接口。Spring 中使用代理模式的结构图如下:


图 22. Spring 中使用代理模式的结构图

$Proxy 就是创建的代理对象,而 Subject 是抽象主题,代理对象是通过 InvocationHandler 来持有对目标对象的引用的。

Spring 中一个真实的代理对象结构如下:


清单 10 代理对象 $Proxy4


public class $Proxy4 extends java.lang.reflect.Proxy implements
org.springframework.aop.framework.PrototypeTargetTests$TestBean
org.springframework.aop.SpringProxy
org.springframework.aop.framework.Advised
{
java.lang.reflect.Method m16;
java.lang.reflect.Method m9;
java.lang.reflect.Method m25;
java.lang.reflect.Method m5;
java.lang.reflect.Method m2;
java.lang.reflect.Method m23;
java.lang.reflect.Method m18;
java.lang.reflect.Method m26;
java.lang.reflect.Method m6;
java.lang.reflect.Method m28;
java.lang.reflect.Method m14;
java.lang.reflect.Method m12;
java.lang.reflect.Method m27;
java.lang.reflect.Method m11;
java.lang.reflect.Method m22;
java.lang.reflect.Method m3;
java.lang.reflect.Method m8;
java.lang.reflect.Method m4;
java.lang.reflect.Method m19;
java.lang.reflect.Method m7;
java.lang.reflect.Method m15;
java.lang.reflect.Method m20;
java.lang.reflect.Method m10;
java.lang.reflect.Method m1;
java.lang.reflect.Method m17;
java.lang.reflect.Method m21;
java.lang.reflect.Method m0;
java.lang.reflect.Method m13;
java.lang.reflect.Method m24;

int hashCode();
int indexOf(org.springframework.aop.Advisor);
int indexOf(org.aopalliance.aop.Advice);
boolean equals(java.lang.Object);
java.lang.String toString();
void sayhello();
void doSomething();
void doSomething2();
java.lang.Class getProxiedInterfaces();
java.lang.Class getTargetClass();
boolean isProxyTargetClass();
org.springframework.aop.Advisor; getAdvisors();
void addAdvisor(int, org.springframework.aop.Advisor)
throws org.springframework.aop.framework.AopConfigException;
void addAdvisor(org.springframework.aop.Advisor)
throws org.springframework.aop.framework.AopConfigException;
void setTargetSource(org.springframework.aop.TargetSource);
org.springframework.aop.TargetSource getTargetSource();
void setPreFiltered(boolean);
boolean isPreFiltered();
boolean isInterfaceProxied(java.lang.Class);
boolean removeAdvisor(org.springframework.aop.Advisor);
void removeAdvisor(int)throws org.springframework.aop.framework.AopConfigException;
boolean replaceAdvisor(org.springframework.aop.Advisor,
org.springframework.aop.Advisor)
throws org.springframework.aop.framework.AopConfigException;
void addAdvice(org.aopalliance.aop.Advice)
throws org.springframework.aop.framework.AopConfigException;
void addAdvice(int, org.aopalliance.aop.Advice)
throws org.springframework.aop.framework.AopConfigException;
boolean removeAdvice(org.aopalliance.aop.Advice);
java.lang.String toProxyConfigString();
boolean isFrozen();
void setExposeProxy(boolean);
boolean isExposeProxy();
}

 

策略模式

策略模式原理

策略模式顾名思义就是做某事的策略,这在编程上通常是指完成某个操作可能有多种方法,这些方法各有千秋,可能有不同的适应的场合,然而这些操 作方法都有可能用到。各一个操作方法都当作一个实现策略,使用者可能根据需要选择合适的策略。

下面是策略模式的结构:


图 23. 策略模式的结构

  • Context:使用不同策略的环境,它可以根据自身的条件选择不同的策略实现类来完成所要的操作。它持有一个策略实例的引用。创建具体 策略对象的方法也可以由他完成。
  • Strategy:抽象策略,定义每个策略都要实现的策略方法
  • ConcreteStrategy:具体策略实现类,实现抽象策略中定义的策略方法

Spring 中策略模式的实现

Spring 中策略模式使用有多个地方,如 Bean 定义对象的创建以及代理对象的创建等。这里主要看一下代理对象创建的策略模式的实现。

前面已经了解 Spring 的代理方式有两个 Jdk 动态代理和 CGLIB 代理。这两个代理方式的使用正是使用了策略模式。它的结构图如下所示:


图 24. Spring 中策略模式结构图

在上面结构图中与标准的策略模式结构稍微有点不同,这里抽象策略是 AopProxy 接口,Cglib2AopProxy 和 JdkDynamicAopProxy 分别代表两种策略的实现方式,ProxyFactoryBean 就是代表 Context 角色,它根据条件选择使用 Jdk 代理方式还是 CGLIB 方式,而另外三个类主要是来负责创建具体策略对象,ProxyFactoryBean 是通过依赖的方法来关联具体策略对象的,它是通过调用策略对象的 getProxy(ClassLoader classLoader) 方法来完成操作。

1.SpringMVC 设计理念与 DispatcherServlet

1.SpringMVC 设计理念与 DispatcherServlet

 

SpringMVC 作为 Struts2 之后异军突起的一个表现层框架,正越来越流行,相信 javaee 的开发者们就算没使用过 SpringMVC,也应该对其略有耳闻。我试图通过对 SpringMVC 的设计思想和源码实现的剖析,从抽象意义上的设计层面和实现意义上的代码层面两个方面,逐一揭开 SpringMVC 神秘的面纱,本文的代码,都是基于 Spring 的 3.1.3RELEASE 版本。

任何一个框架,都有自己特定的适用领域,框架的设计和实现,必定是为了应付该领域内许多通用的,烦琐的、基础的工作而生。SpringMVC 作为一个表现层框架,也必须直面 Web 开发领域中表现层中的几大课题,并给出自己的回答:

  • URL 到框架的映射。

  • http 请求参数绑定

  • http 响应的生成和输出

这三大课题,组成一个完整的 web 请求流程,每一个部分都具有非常广阔的外延。SpringMVC 框架对这些课题的回答又是什么呢?

学习一个框架,首要的是要先领会它的设计思想。从抽象、从全局上来审视这个框架。其中最具有参考价值的,就是这个框架所定义的核心接口。核心接口定义了框架的骨架,也在最抽象的意义上表达了框架的设计思想。

下面我以一个 web 请求流程为载体,依次介绍 SpringMVC 的核心接口和类。

用户在浏览器中,输入了 http://www.xxxx.com/aaa/bbb.ccc 的地址,回车后,浏览器发起一个 http 请求。请求到达你的服务器后,首先会被 SpringMVC 注册在 web.xml 中的前端转发器 DispatcherServlet 接收,DispatcherServlet 是一个标准的 Servlet,它的作用是接受和转发 web 请求到内部框架处理单元。

下面看一下第一个出现在你面前的核心接口,它是在 org.springframework.web.servlet 包中定义的 HandlerMapping 接口:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;

public interface HandlerMapping {

String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

为了阅读方便,我去掉了源码中的注释,但是我强烈建议你一定要记得去阅读它,这样你才能从框架的设计者口中得到最准确的关于这个类或者接口的设计说明。类中定义的几个常量,我们先不去管它。关键在于这个接口中唯一的方法:

HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

这个方法就算对于一个 java 初学者来说,也很容易理解:它只有一个类型为 HttpServletRequest 的参数,throws Exception 的声明表示它不处理任何类型的异常,HandlerExecutionChain 是它的返回类型。

回到 DispatcherServlet 的处理流程,当 DispatcherServlet 接收到 web 请求后,由标准 Servlet 类处理方法 doGet 或者 doPost,经过几次转发后,最终注册在 DispatcherServlet 类中的 HandlerMapping 实现类组成的一个 List(有点拗口)会在一个循环中被遍历。以该 web 请求的 HttpServletRequest 对象为参数,依次调用其 getHandler 方法,第一个不为 null 的调用结果,将被返回。DispatcherServlet 类中的这个遍历方法不长,贴一下,让大家有更直观的了解。

/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or <code>null</code> if no handler could be found
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//遍历处理器映射器
       for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name ''" + getServletName() + "''");
}
           //获取HandlerExecutionChain(处理执行链),并返回
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

是的,第一步处理就这么简单的完成了。一个 web 请求经过处理后,会得到一个 HandlerExecutionChain 对象,这就是 SpringMVC 对 URl 映射给出的回答。需要留意的是,HandlerMapping 接口的 getHandler 方法参数是 HttpServletRequest,这意味着,HandlerMapping 的实现类可以利用 HttpServletRequest 中的 所有信息来做出这个 HandlerExecutionChain 对象的生成” 决策 “。这包括,请求头、url 路径、cookie、session、参数等等一切你从一个 web 请求中可以得到的任何东西(最常用的是 url 路径)。

SpirngMVC 的第一个扩展点,就出现在这里。我们可以编写任意的 HandlerMapping 实现类,依据任何策略来决定一个 web 请求到 HandlerExecutionChain 对象的生成。可以说,从第一个核心接口的声明开始,SpringMVC 就把自己的灵活性和野心暴露无疑:哥玩的就是”Open-Closed“。

HandlerExecutionChain 这个类,就是我们下一个要了解的核心类。从名字可以直观的看得出,这个对象是一个执行链的封装。熟悉 Struts2 的都知道,Action 对象也是被层层拦截器包装,这里可以做个类比,说明 SpringMVC 确实是吸收了 Struts2 的部分设计思想。

HandlerExecutionChain 类的代码不长,它定义在 org.springframework.web.servlet 包中,为了更直观的理解,先上代码。

package org.springframework.web.servlet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.util.CollectionUtils;

public class HandlerExecutionChain {

private final Object handler;

private HandlerInterceptor[] interceptors;

private List<HandlerInterceptor> interceptorList;

public HandlerExecutionChain(Object handler) {
this(handler, null);
}

public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
if (handler instanceof HandlerExecutionChain) {
HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
this.handler = originalChain.getHandler();
this.interceptorList = new ArrayList<HandlerInterceptor>();
CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
}
else {
this.handler = handler;
this.interceptors = interceptors;
}
}

public Object getHandler() {
return this.handler;
}

public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList();
this.interceptorList.add(interceptor);
}

public void addInterceptors(HandlerInterceptor[] interceptors) {
if (interceptors != null) {
initInterceptorList();
this.interceptorList.addAll(Arrays.asList(interceptors));
}
}

private void initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<HandlerInterceptor>();
}
if (this.interceptors != null) {
this.interceptorList.addAll(Arrays.asList(this.interceptors));
this.interceptors = null;
}
}

public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList != null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
}
return this.interceptors;
}

@Override
public String toString() {
if (this.handler == null) {
return "HandlerExecutionChain with no handler";
}
StringBuilder sb = new StringBuilder();
sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
if (!CollectionUtils.isEmpty(this.interceptorList)) {
sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
if (this.interceptorList.size() > 1) {
sb.append("s");
}
}
return sb.toString();
}

}

乱七八糟一大堆,相信你也没全看完,也没必要全看。其实只需要看两行足矣。

private final Object handler; //处理器,真正处理业务

private HandlerInterceptor[] interceptors; //拦截器数组

不出我们所料,一个实质执行对象,还有一堆拦截器。这不就是 Struts2 中的实现么,SpringMVC 没有避嫌,还是采用了这种封装。得到 HandlerExecutionChain 这个执行链(execution chain)之后,下一步的处理将围绕其展开。

HandlerInterceptor 也是 SpringMVC 的核心接口,定义如下:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerInterceptor {

boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
   throws Exception;

void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception;

void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception;

}

至此,HandlerExecutionChain 整个执行脉络也就清楚了:在真正调用其 handler 对象前,HandlerInterceptor 接口实现类组成的数组将会被遍历,其 preHandle 方法会被依次调用,然后真正的 handler 对象将被调用。

handler 对象被调用后,就生成了需要的响应数据,在将处理结果写到 HttpServletResponse 对象之前(SpringMVC 称为渲染视图),其 postHandle 方法会被依次调用。视图渲染完成后,最后 afterCompletion 方法会被依次调用,整个 web 请求的处理过程就结束了。

在一个处理对象执行之前,之后利用拦截器做文章,这已经成为一种经典的框架设计套路。Struts2 中的拦截器会做诸如参数绑定这类复杂的工作,那么 SpringMVC 的拦截器具体做些什么呢?我们暂且不关心,虽然这是很重要的细节,但细节毕竟是细节,我们先来理解更重要的东西。

HandlerInterceptor,是 SpringMVC 的第二个扩展点的暴露,通过自定义拦截器,我们可以在一个请求被真正处理之前、请求被处理但还没输出到响应中、请求已经被输出到响应中之后这三个时间点去做任何我们想要做的事情。Struts2 框架的成功,就是源于这种拦截器的设计,SpringMVC 吸收了这种设计思想,并推陈出新,更合理的划分了三个不同的时间点,从而给 web 请求处理这个流程,提供了更大的扩展性。

这个 HandlerExecutionChain 类中以 Object 引用所声明的 handler 对象,到底是个什么东东?它是怎么被调用的?

回答这些问题之前,先看 SpringMVC 中的又一个核心接口,HandlerAdapter:

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean supports(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

long getLastModified(HttpServletRequest request, Object handler);

}

在 DispatcherServlet 中,除了 HandlerMapping 实现类的列表,同样也注册了一个 HandlerAdapter 实现类组成的列表,有代码为证。

/** List of HandlerMappings used by this servlet */
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet */
private List<HandlerAdapter> handlerAdapters;

接下来,我们再以 DispatcherServlet 类中另外一段代码来回答上述的问题:

/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
			if (logger.isTraceEnabled()) {
				logger.trace("Testing handler adapter [" + ha + "]");
			}
			if (ha.supports(handler)) {
				return ha;
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: Does your handler implement a supported interface like Controller?");
	}

这段代码已经很明显了,HandlerExecutionChain 中的 handler 对象会被作为参数传递进去,在 DispatcherServlet 类中注册的 HandlerAdapter 实现类列表会被遍历,然后返回第一个 supports 方法返回 true 的 HandlerAdapter 对象,用这个 HandlerAdapter 实现类中的 handle 方法处理 handler 对象,并返回 ModelAndView 这个包含了视图和数据的对象。HandlerAdapter 就是 SpringMVC 提供的第三个扩展点,你可以提供自己的实现类来处理 handler 对象。

ModelAndView 对象的代码就不贴了,它是 SpringMVC 中对视图和数据的一个聚合类。其中的视图,就是由 SpringMVC 的最后一个核心接口 View 所抽象:

package org.springframework.web.servlet;
 
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public interface View {
 
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
 
	String PATH_VARIABLES = View.class.getName() + ".pathVariables";
 
	String getContentType();
 
	void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
 
}

所有的数据,最后会作为一个 Map 对象传递到 View 实现类中的 render 方法,调用这个 render 方法,就完成了视图到响应的渲染。这个 View 实现类,就是来自 HandlerAdapter 中的 handle 方法的返回结果。当然从 ModelAndView 到真正的 View 实现类有一个解析的过程,ModelAndView 中可以有真正的视图对象,也可以只是有一个视图的名字,SpringMVC 会负责将视图名称解析为真正的视图对象。

至此,我们了解了一个典型的完整的 web 请求在 SpringMVC 中的处理过程和其中涉及到的核心类和接口。

在一个典型的 SpringMVC 调用中,HandlerExecutionChain 中封装 handler 对象就是用 @Controller 注解标识的类的一个实例,根据类级别和方法级别的 @RequestMapping 注解,由默认注册的 DefaultAnnotationHandlerMapping(3.1.3 中更新为 RequestMappingHandlerMapping 类,但是为了向后兼容,DefaultAnnotationHandlerMapping 也可以使用)生成 HandlerExecutionChain 对象,再由 AnnotationMethodHandlerAdapter(3.1.3 中更新为 RequestMappingHandlerAdapter 类,但是为了向后兼容,AnnotationMethodHandlerAdapter 也可以使用)来执行这个 HandlerExecutionChain 对象,生成最终的 ModelAndView 对象后,再由具体的 View 对象的 render 方法渲染视图。

可以看到,作为一个表现层框架,SpringMVC 没有像 Struts2 那样激进,并没有采用和 Web 容器完全解耦的设计思想,而是以原生的 Servlet 框架对象为依托,通过合理的抽象,制定了严谨的的处理流程。这样做的结果是,执行效率比 Struts2 要高,灵活性也上升了一个层次。

上一篇文章《SpringMVC 源码剖析(一)- 从抽象和接口说起》中,我介绍了一次典型的 SpringMVC 请求处理过程中,相继粉墨登场的各种核心类和接口。我刻意忽略了源码中的处理细节,只列出最简单的类甚至是接口类,目的就是让大家先从最高层次的抽象意义上来审视 SpringMVC 这个框架;我也刻意将 SpringMVC 和 Struts2 做对比,目的是让大家看到,SpringMVC 究竟吸取了 Sturts2 设计思想中的哪些精华,又弥补了它的哪些遗憾。

DispatcherServlet 作为 SpringMVC 的核心之中的核心类,再怎么强调它的重要性也不为过。SpringMVC 所有的核心类和接口,都密集地出现在 DispatcherServlet 的源码中,SpringMVC 源码剖析,很大程度上可以说也是在剖析 DispatcherServlet 这一个类。这一篇文章里,我先说几点关于 DispatcherServlet 的前世今生,希望能帮助你更好的理解它。

1. 对扩展开放,对修改封闭

SpringMVC 是一个基于著名的 Open-Closed,即开闭原则进行设计的框架。在 Spring 官方文档里面关于 SpringMVC 的介绍开宗明义地进行了说明:

A key design principle in Spring Web MVC and in Spring in general is the “Open for extension,closed for modificationprinciple.

开闭原则是一个很宽泛的原则,具体体现到 DispatcherServlet 的源码中,我们可以大致摸得到一些线索:

  • 类中所有的变量声明,几乎都以接口的形式给出,并没有绑定在具体的实现类上。

  • 使用模版方法模式,在父类中对基础行为进行定义,让子类实现模版方法扩展行为。

其中第一点,在一个框架的设计中尤为重要,也是贯彻开闭原则最重要的一点。因为当你通过一些高层次的接口或者抽象类,将一个类完成的逻辑或流程编写完成后(具体点说,是通过一个接口的引用调用接口方法),整个逻辑或流程的功能就被确实的在源码中固定下来了。可是这时,这些接口或抽象类的具体实现者是谁,还没有固定!这就给了你的系统或框架近乎无限的扩展性,因为你可以任意安排和实现这些类。

我认为,面向对象设计的精髓,是对现实世界中 “行为和契约” 的描述。这个 “行为和契约”,体现在接口和抽象类的方法声明中。软件设计师要用面向对象的眼光去观察和抽象这个世界中的事物,这里的事物可以是一些商业逻辑、可以是一些处理流程,然后用高层次的接口去描述这些行为和契约。当你在越抽象的层次上将这些行为和契约描述清楚后,你所设计的系统就是越符合开闭原则的。

SpringMVC 框架在面向对象设计上,做出了绝佳的示范。它通过高度抽象的接口,描述出了一次请求处理的流程,从而让整个框架从一开始就是符合开闭原则的。同时它也提供了这些接口的一系列默认实现类,让你不需要很复杂的配置,就能很好的使用 SpringMVC 进行开发。抽象的确是个利器,但是框架绝不能运行在空中楼阁中,SpringMVC 提供的的这一系列默认实现类必须要有容身之所。聪明的你可能早已想到:Spring IOC 容器。这就引出了我要说的第二点。

2. 配置元素的对象化

所有的框架,都需要有这样一个功能,叫做:配置元素的对象化。因为几乎所有的框架,都将配置元素集中到外部的 xml 配置文件中,然后在框架的初始化流程中,对这些配置文件进行解析,再变成 java 世界中的一个个对象供框架使用,这整个过程,可以被称为配置元素的对象化。为什么要有配置文件呢?这个问题的回答也是很简单,因为没有人会想要使用一个配置散布在框架中各个 java 类源码里面的框架。框架也不允许使用者这样子做,因为框架在发布的时候,提供的是一个个 jar 包文件,jar 包内是已经编译好的 class 文件。配置文件由使用者外部提供,框架对它进行解析,使用者能得到集中配置的好处,框架也乐于这样子,可以说是合情合理。

那么作为 Spring 产品族的新成员,SpringMVC 在设计的时候,相信设计者们不做它想,这一个 “配置元素的对象化” 功能既然不可避免,那么使用 Spring IOC 容器,通过 bean 配置文件来配置 SpringMVC,绝对是不二之选。不可能像 Struts2 一样,内部再搞一个别的容器,因为 Spring 容器本身已经是被高度设计,而且已经在 java 世界获得巨大成功。从推广的角度上来说,如果对 spring 容器的所有知识,都可以完整的应用到 SpringMVC,那么对于开发者无疑是一个极大的吸引力。

剩下的问题就只有:到底该如何将 Spring 容器和 SpringMVC 的初始化过程整合起来呢?

答案就是 WebApplicationContext 接口,更具体点说,是 XmlWebApplicationContext 这个 Spring 上下文实现类。SpringMVC 也使用了这一个为了将 Spring 容器和 Web 环境整合而特意设计的 Spring 上下文类。我们打开 WebApplicationContext 的源码:

package org.springframework.web.context;
import javax.servlet.ServletContext;
import org.springframework.context.ApplicationContext;
public interface WebApplicationContext extends ApplicationContext {
 
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
 
	String SCOPE_REQUEST = "request";
 
	String SCOPE_SESSION = "session";
 
	String SCOPE_GLOBAL_SESSION = "globalSession";
 
	String SCOPE_APPLICATION = "application";
 
	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
 
	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
 
	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";
 
	ServletContext getServletContext();
 
}

发现它是继承于 ApplicationContext 这个普通 Spring 容器所使用的上下文接口类,除了一些常量的声明,只多了一个可以获取到 ServletContext 的 getServletContext () 方法。回到上面提到的 “行为和契约的描述” 上,我们可以大胆的断言,Spring 容器和 Web 环境的整合,是在 ServletContext 上做文章。

打开所有使用了 Spring 的 Web 项目的 web.xml 文件,必定有这样一段配置:

<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

ContextLoaderListener 实现了 ServletContextListener 接口,在 Servlet 容器启动的时候,会初始化一个 WebApplicationContext 的实现类,并将其作为 ServletContext 的一个属性设置到 Servlet 环境中,摘抄源码如下:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 的值,在上面 WebApplicationContext 的源码中的第一个常量中就被声明,是 WebApplicationContext.class.getName () + ".ROOT",更直接一点,它是 “org.springframework.web.context.WebApplicationContext.ROOT”。ContextLoaderListener 所初始化的这个 Spring 容器上下文,被称为根上下文。

SpringMVC 在 DispatcherServlet 的初始化过程中,同样会初始化一个 WebApplicationContext 的实现类,作为自己独有的上下文,这个独有的上下文,会将上面的根上下文作为自己的父上下文,来存放 SpringMVC 的配置元素,然后同样作为 ServletContext 的一个属性,被设置到 ServletContext 中,只不过它的 key 就稍微有点不同,key 和具体的 DispatcherServlet 注册在 web.xml 文件中的名字有关,从这一点也决定了,我们可以在 web.xml 文件中注册多个 DispatcherServlet,因为 Servlet 容器中注册的 Servlet 名字肯定不一样,设置到 Servlet 环境中的 key 也肯定不同。

由于在 Spring 容器中,子上下文可以访问到所有父上下文中的信息,而父上下文访问不到子上下文的信息,这个根上下文,就很适合作为多个子上下文配置的集中点。以官方文档中的图来说明:

3. 前端控制器

前端控制器,即所谓的 Front Controller,体现的是设计模式中的前端控制器模式。前端控制器处理所有从用户过来的请求。所有用户的请求都要通过前端控制器。SpringMVC 框架和其他请求驱动的表示层框架一样,也是围绕一个将请求分发到相应控制器的核心 Servlet 来设计的。DispatcherServlet 和其他框架中的 Servlet 不一样的地方在于,它和 Spring 容器无缝整合在了一起,因此你可以在 SpringMVC 中使用 Spring 容器所有的特性。

DispatcherServlet 这个前端控制器,在 SpringMVC 中的作用,以官方文档中的配图来说明:

整个流程可以被大致描述为:一个 http 请求到达服务器,被 DispatcherServlet 接收。DispatcherServlet 将请求委派给合适的处理器 Controller,此时处理控制权到达 Controller 对象。Controller 内部完成请求的数据模型的创建和业务逻辑的处理,然后再将填充了数据后的模型即 model 和控制权一并交还给 DispatcherServlet,委派 DispatcherServlet 来渲染响应。DispatcherServlet 再将这些数据和适当的数据模版视图结合,向 Response 输出响应。

可以看到 Model-View-Controller 这三样东西协同合作,共同体现出 MVC 的设计理念,三个层次可以分别独立演化,整个系统架构又清晰又简洁。这是 SpringMVC 为我们描述的美好愿景,后面我们也将看到,SpringMVC 为了实现这一承诺,究竟做出了什么样的努力

Flink keyBy 算子源码与设计理念分析

大家好,我是大圣,很高兴又和大家见面。

今天我们来探究一下 Flink 使用 keyBy 算子的时候到底发生了什么,看完这篇文章,你会豁然开朗。

keyBy 算子基本知识

keyBy 会发生什么

专业解释

keyBy 使得相同key 的数据会进入同一个并行子任务,每一个子任务可以处理多个不同的key。这样使数据保证了有序性,并且每个子任务直接相互隔离。

我们确保了相同键的数据在逻辑上是有序的。即使在高度并行的环境中,具有相同键的所有数据都会按照其到达的顺序进行处理。这为后续的时间窗口操作、事件时间处理和状态管理提供了基础。

举例说明

假设你有一个Flink应用,该应用从一个数据源读取购物交易事件。每个交易事件都有一个userId和一个amount字段,分别表示发生交易的用户和交易金额。

现在,你希望使用Flink来计算每个用户的总交易金额。

代码可能如下:

DataStream<Tuple2<Long, Double>> totalAmounts = transactions
    .keyBy(transaction -> transaction.userId)
    .sum("amount");

这里,keyBy操作基于userId字段将交易数据分组。

现在,假设我们有以下交易数据流:

User 1: $10
User 2: $5
User 1: $20
User 3: $15

同时,假设我们的Flink并行度为2。这意味着我们有两个并行任务来处理数据。

现在,考虑keyBy后的数据分发情况:

任务A处理: User 1的所有交易
任务B处理: User 2和User 3的所有交易

这是怎么做到的?

当Flink执行keyBy操作时,它使用键(这里是userId)的散列值来确定哪个子任务应该处理该事件。例如,它可能决定将userId=1的所有事件发送给任务A,而userId=2和userId=3的事件发送给任务B。

关键点是,同一个userId的所有事件都被路由到同一个任务。

现在,让我们看看任务A内部是怎么处理的:

由于每个Flink任务在单个线程内运行,所以任务A内部的处理是顺序的。

当任务A首先看到User 1: $10的交易时,它会更新User 1的总金额为$10。接下来,当它看到User 1: $20的交易时,它会更新User 1的总金额为$30。

在这个过程中,由于只有一个线程在处理数据,所以不可能出现两个线程尝试同时更新User 1的金额的情况,因此没有数据竞争。

#### 任务B 工作流程
与任务A的处理方式相同,任务B也在其单个线程中顺序地处理每个事件。但是,任务B需要为User 2和User 3都维护状态,因为它负责这两个用户。

假设我们有以下事件流进入任务B:

User 2: $5
User 3: $15
User 2: $10
User 3: $20

任务B的处理如下:

当任务B首先看到User 2: $5的交易时,它会更新User 2的总金额为$5。

接下来,当任务B看到User 3: $15的交易时,它会为User 3创建一个新的状态(因为这是我们第一次看到User 3的交易)并设置其总金额为$15。

任务B再次看到User 2的交易,这次是User 2: $10。它将User 2的总金额更新为$15($5 + $10)。

最后,任务B看到User 3: $20的交易,它更新User 3的总金额为$35($15 + $20)。

在整个过程中,任务B为每个用户维护了独立的状态。当处理User 2的交易时,它查看并更新User 2的状态;当处理User 3的交易时,它查看并更新User 3的状态。

正如你所看到的,每次处理都是顺序的,因为它在单个线程中进行。因此,即使任务B同时处理两个用户的交易,也不会出现数据竞争,因为每个用户的状态都是独立的,并且每次只处理一个事件。

探究 keyBy 源码

举例说明

大家先看一下下面这个代码:

import org.apache.flink.api.common.serialization.SimpleStringEncoder;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.core.fs.Path;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.filesystem.StreamingFileSink;
import org.apache.flink.streaming.api.functions.sink.filesystem.rollingpolicies.DefaultRollingPolicy;
import java.util.concurrent.TimeUnit;

public class FlinkKeyByTest {

    public static void main(String[] args) throws Exception {
        // 获取流执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        // 示例数据流
        DataStream<String> sourceStream = env.fromElements("Hello", "Flink", "StreamingFileSink");

        // 使用keyBy算子
        KeyedStream<String, String> keyedStream = sourceStream.keyBy(new KeySelector<String, String>() {
            @Override
            public String getKey(String value) throws Exception {
                return value;  // 使用字符串本身作为键
            }
        });

        String outputPath = "D:\\tmp\\test";

        // 创建StreamingFileSink
        StreamingFileSink<String> fileSink = StreamingFileSink
                .forRowFormat(new Path(outputPath), new SimpleStringEncoder<String>("UTF-8"))
                .withRollingPolicy(
                        DefaultRollingPolicy.builder()
                                .withRolloverInterval(TimeUnit.MINUTES.toMillis(15))
                                .withInactivityInterval(TimeUnit.MINUTES.toMillis(5))
                                .withMaxPartSize(1024 * 1024 * 1024)
                                .build()
                )
                .build();

        // 添加sink到数据流
        keyedStream.addSink(fileSink);
        // 执行任务
        env.execute("Flink StreamingFileSink with KeyBy to Local File System Example");
    }
}

这个代码就是加载数据源,然后进行 keyBy,最后 Sink 到本地文件目录。

解释

大家可以在 org.apache.flink.streaming.runtime.partitioner.KeyGroupStreamPartitioner 这个类 的 public int selectChannel(SerializationDelegate<StreamRecord<T>> record) 这个方法里面打个断点,然后 debug 执行上面 Flink 程序就可以了,如下图:

里面最重要的就是下面这两行代码:

keyGroupId = MathUtils.murmurHash(keyHash) % maxParallelism;
keyGroupId * parallelism / maxParallelism;

这几个类的调用关系大家点进去多走几遍就能摸清楚,我在这里就不给大家展示出来了。

上面两行代码,第一行代码我添加了 "keyGroupId = "",是为了让大家更好的理解,第一行代码就是用你传进来的 key,计算 key 的 keyGroupId

第二行代码就是计算你这个 key 被分配到哪个并行的子任务去处理。

看到这,大家估计想问源码我是看了,但是为什么这么设计阿?接下来,我们相信说。

### 源码设计理念
为什么要 keyGroupId * parallelism / maxParallelism; 这样计算
#### 基础知识
##### maxParallelism
这是 Flink 作业的最大并行度。这个值定义了键组的最大数量。即使并行任务的数量在作业的生命周期中变化,这个值保持不变。

parallelism

这是当前 Flink 作业的并行度。这指的是当前有多少个任务并行执行。

keyGroupId

要查询的键组的 ID。

##### 问题1:为什么 keyGroupId * parallelism

##### 专业解释
首先,要理解keyGroupId * parallelism这个乘法的目的,我们需要考虑两个关键点:keyGroupId的范围和parallelism的意义。

keyGroupId的范围:

keyGroupId的范围是从0到maxParallelism-1。这意味着最大的keyGroupId是maxParallelism-1。
parallelism的意义:

parallelism表示我们想要在当前作业中运行的并行任务数。这是我们真正关心的任务数。
现在,考虑keyGroupId * parallelism这个乘法:

当我们乘以parallelism,我们实际上是在“扩展”或“拉伸”keyGroupId的范围。原来的范围是[0, maxParallelism-1],现在它被拉伸到[0, maxParallelism * parallelism-1]。

为什么这么做呢?这是因为我们想要确保在拉伸后的范围内,key groups 被均匀地分布到所有可能的并行任务上。通过乘以parallelism,我们实际上是在确保每个任务处理大约相同数量的key groups

##### 举例理解
假设maxParallelism = 100,也就是说我们有100个可能的key groups (编号从0到99)。而当前的parallelism = 4,也就是说我们希望运行4个并行任务。

###### 如果没有乘以parallelism:
假设我们直接使用keyGroupId / maxParallelism:

对于keyGroupId = 0到24,结果是0。

对于keyGroupId = 25到49,结果是0.25。

对于keyGroupId = 50到74,结果是0.5。

对于keyGroupId = 75到99,结果是0.75。

这些浮点数结果实际上意味着每个任务分别处理以下key groups:

任务0:0-24

任务1:没有key groups!

任务2:50-74

任务3:没有key groups!

这意味着,如果我们仅仅按照keyGroupId除以maxParallelism,我们的key groups分布会非常不均匀。

在这种情况下,只有任务0和任务2得到了key groups,而任务1和任务3一个key group都没有。这不是我们想要的结果,因为它没有有效地利用所有的并行任务。

乘以parallelism

使用公式 (keyGroupId * parallelism) / maxParallelism:

对于keyGroupId = 0到24,结果范围是0-0.96,因此所有这些key groups都分配给任务0。

对于keyGroupId = 25到49,结果范围是1-1.96,所以这些key groups都被分配给任务1。

对于keyGroupId = 50到74,结果范围是2-2.96,所以这些key groups都被分配给任务2。

对于keyGroupId = 75到99,结果范围是3-3.96,所以这些key groups都被分配给任务3。

这意味着每个任务会处理以下key groups:

任务0:0-24

任务1:25-49

任务2:50-74

任务3:75-99

通过对比,可以清晰地看到,在没有乘以parallelism的情况下,任务的负载分配是不均匀的,只有两个任务在处理数据,而其他两个任务则闲置。而当使用乘以parallelism的方式时,所有任务都被均匀地分配了key groups,从而确保了任务的均匀负载和高效率。

问题2:为什么要除以 最大并行度

##### 专业解释
除以最大并行度(maxParallelism)是为了确保得到的结果位于期望的范围内,从0到所选并行度(parallelism) - 1之间。

Flink的并行度(parallelism)代表了任务或算子实际的并行实例数量,而最大并行度(maxParallelism)是一个上限值,它定义了系统能够处理的键组(key groups)的最大数量。

这里的关键在于理解为什么要使用maxParallelism而不是parallelism。

##### 举例理解
maxParallelism = 100

parallelism 在开始时为 4,随后变为 8

当 parallelism = 4:
我们希望将 100 个可能的键组(key groups)均匀分布在 4 个并行任务中。

使用之前描述的算法 keyGroupId * parallelism / maxParallelism:

对于 keyGroupId 0-24:0-24 * 4 / 100 → 任务 0

对于 keyGroupId 25-49:25-49 * 4 / 100 → 任务 1

对于 keyGroupId 50-74:50-74 * 4 / 100 → 任务 2

对于 keyGroupId 75-99:75-99 * 4 / 100 → 任务 3

这样,每个任务处理大约 25 个键组。

当我们将 parallelism 改为 8:

我们希望将 100 个可能的键组均匀分布在 8 个并行任务中。

对于 keyGroupId 0-12:0-12 * 8 / 100 → 任务 0

对于 keyGroupId 13-24:13-24 * 8 / 100 → 任务 1

对于 keyGroupId 25-37:25-37 * 8 / 100 → 任务 2

对于 keyGroupId 38-49:38-49 * 8 / 100 → 任务 3

对于 keyGroupId 50-62:50-62 * 8 / 100 → 任务 4

对于 keyGroupId 63-74:63-74 * 8 / 100 → 任务 5

对于 keyGroupId 75-87:75-87 * 8 / 100 → 任务 6

对于 keyGroupId 88-99:88-99 * 8 / 100 → 任务 7

这样,每个任务处理大约 12 或 13 个键组。

#### 状态重新分配过程
例如,考虑一个简化的例子,我们有key group 0-99。在并行度为4的设置中,任务0可能处理key group 0-24。

但是,在并行度为8的新设置中,任务0可能只处理key group 0-12,而任务1处理key group 13-24。因此,保存点中与key group 13-24相关的状态将从旧的任务0迁移到新的任务1。

总结

这篇文章我们对 keyBy 源码进行了分析,也教了大家怎么去调试 keyBy 算子的源码,大家可以去尝试一下。

另外也对 keyBy 算子的源码设计理念进行了分析,我敢说这是全网第一家可以把 keyBy 算子说的这么透彻的。

其实 keyBy 算子还有很多东西,比如如何避免数据倾斜等,后面也会说的,还有模板代码提供给大家避免数据倾斜。

最后欢迎大家一起来讨论,可以关注微信公众号:大圣数据星球 进群讨论。

本文由博客一文多发平台 OpenWrite 发布!

Golang 语言的设计理念与特性解析

Golang 语言的设计理念与特性解析

golang 语言的设计理念与特性解析

Golang 语言的设计理念与特性解析

Go语言(也称为Golang)是由谷歌公司开发的一种静态强类型的编程语言,于2009年首次亮相。自问世以来,Go语言在各个领域逐渐崭露头角,广受程序员的喜爱。在这篇文章中,我们将深入探讨Golang语言的设计理念与特性,并结合具体的代码示例进行解析。

1. 并发编程支持

一个显著的特点是Golang天生支持并发编程,这使得编写并发程序变得非常简单。Golang中的goroutine(轻量级线程)和channel(通信机制)是实现并发的两个重要概念。

立即学习“go语言免费学习笔记(深入)”;

package main

import (
    "fmt"
)

func printHello() {
    for i := 0; i < 5; i++ {
        fmt.Println("Hello")
    }
}

func main() {
    go printHello() // 创建goroutine
    for i := 0; i < 5; i++ {
        fmt.Println("World")
    }
}
登录后复制

在上面的代码示例中,通过go printHello()创建了一个新的goroutine来执行printHello()函数,同时main()函数会继续执行。这种并发编程模型让程序员能够轻松地实现并发操作,提高程序性能。

2. 内置的GC(垃圾回收机制)

在Golang中,垃圾回收器会自动管理内存,程序员不需要手动释放内存。这一特性大大减轻了程序员的负担,避免了内存泄漏和指针错误。

package main

import "fmt"

func main() {
    var a = new(int) // 创建一个指针类型的变量
    *a = 10 // 给指针赋值
    fmt.Println(*a)
}
登录后复制

在上面的示例中,我们使用new()函数创建一个int类型的指针变量a,然后通过*a = 10为该指针赋值,而不需要手动管理内存。

3. 高效的静态链接

Golang的编译器可以将所有依赖的库静态链接到生成的可执行文件中,避免了依赖其他库运行的问题。这使得Golang程序的部署非常简单,只需要一个可执行文件即可。

4. 内置的工具集

Golang提供了丰富的标准库和工具集,如测试、性能分析、代码覆盖率分析等,这些工具能够帮助程序员更高效地开发和调试程序。

5. 简洁的语法

Golang语言设计简洁、直观,减少了程序员的认知负担。同时,Golang具有C语言的风格,易于学习和使用。

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3, 4, 5}
    for _, num := range nums {
        fmt.Println(num)
    }
}
登录后复制

在上面的示例中,我们使用了Golang中的range关键字来迭代切片nums,非常简洁高效。

综上所述,Golang语言的设计理念注重高效、简洁、并发,同时具有强大的标准库和工具集,使得它成为一门受欢迎的编程语言。通过本文的解析,相信读者对Golang的特性和设计理念有了更深入的了解。愿你在使用Golang的过程中能够更加得心应手,编写出高效、可靠的程序。

以上就是Golang 语言的设计理念与特性解析的详细内容,更多请关注php中文网其它相关文章!

golang框架源码中的设计理念

golang框架源码中的设计理念

go 框架源码设计理念包括:松耦合和接口:组件通过接口关联,提高灵活性和可扩展性。并发性:利用 go 语言的并发特性,提升性能。模块化:由独立包组成,易于定制和扩展。简洁性:代码清晰易读,依赖关系简单。

golang框架源码中的设计理念

Go 框架源码中的设计理念

Go 语言因其并发性、高性能和简约性而闻名,这些特性也体现在其框架的源码设计中。本文探讨了 Go 框架源码中的一些关键设计理念和实战案例。

松耦合和接口

立即学习“go语言免费学习笔记(深入)”;

Go 框架采用松耦合的设计,组件通过接口进行关联。接口定义了一组方法,而具体实现则可以由不同的类型来提供。这提高了框架的灵活性和可扩展性。

实战案例:Gin 框架通过 HandlerFunc 接口定义了所有路由处理程序。用户可以实现该接口并提供自己的处理逻辑。

并发性

Go 框架充分利用 Go 语言的并发特性来提高性能。goroutine 和 channel 等并发原语被广泛使用,允许并行执行任务。

实战案例:Echo 框架使用 goroutine 并发处理请求,提高吞吐量。

模块化

Go 框架被设计为模块化的,由独立的包组成。这些包可以单独导入和使用,使框架易于定制和扩展。

实战案例:Beego 框架提供了一系列模块,例如 ORM、缓存和日志记录,用户可以根据需要导入它们。

简洁性

Go 语言和框架源码本身都遵循简洁性的原则。代码清晰易读,并且不依赖于复杂的依赖关系。

实战案例:Kubernetes API 服务器源码非常简洁,函数和对象通常只有几十行代码。

实战中的应用

这些设计理念在实际框架开发中得到了广泛应用,例如 Gin、Echo、Beego 和 Kubernetes 等。这些框架都体现了松耦合、并发性、模块化和简洁性的设计原则。

通过遵循这些理念,Go 框架源码实现了高性能、灵活性和易用性。掌握这些设计原则对于构建高效和可维护的 Go 应用程序至关重要。

以上就是golang框架源码中的设计理念的详细内容,更多请关注php中文网其它相关文章!

今天的关于Spring 框架的设计理念与设计模式分析spring 框架的设计理念与设计模式分析论文的分享已经结束,谢谢您的关注,如果想了解更多关于1.SpringMVC 设计理念与 DispatcherServlet、Flink keyBy 算子源码与设计理念分析、Golang 语言的设计理念与特性解析、golang框架源码中的设计理念的相关知识,请在本站进行查询。

本文标签: