设计模式学习笔记 - 开源实战四(下):总结Spring中用到的11种设计模式

概述

上篇文章,讲解了 Spring 中支持扩展功能的两种设计模式:观察者模式和模板模式。这两种模式帮助我们创建扩展点,让框架的使用者在不修改源码的情况下,基于扩展点定制化框架功能。

实际上,Spring 框架中用到的设计模式非常多,不下十几种。今天就总结罗列下。有些前面已经讲过的或者比较简单的,就点到为止。


适配器模式在 Spring 中的应用

在 Spring MVC 中,定义一个 Controller 最常用的方式,是通过 @Controller 注解来标记某个类是 Controller 类。通过 @RequestMapping 注解来标记函数对应的 URL。不过,定义一个 Controller 远不止这一种方法。我们还可以通过类实现 Controller 接口或者 Servlet 接口,来定义一个 Controller。针对者三种定义方式,下面写了三段示例代码。

// 方法一:通过@Controller、@RequestMapping来定义
@Controller
public class DemoController {@RequestMapping("/employName")public ModelAndView getEmployName() {ModelAndView model = new ModelAndView("Greeting");model.addObject("message", "Dinesh");return model;}
}// 方法二:通过实现Controller接口 + XML配置文件:配置DemoController与URL的对应关系
public class DemoController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) {ModelAndView model = new ModelAndView("Greeting");model.addObject("message", "Dinesh Madhwal");return model;}
}// 方法三:通过实现Servlet接口 + XML配置文件:配置DemoController与URL的对应关系
public class DemoController extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.getWriter().write("Hello World.");}
}

在应用启动时,Spring 容器会加载这些 Controller 类,并且解析出 URL 对应地处理函数,封装成 Handler 对象,存储到 HandlerMapping 对象中。当有请求到来时,DispatcherServletHandlerMapping 中,查找请求 URL 对应地 Handler ,然后调用执行 Handler 对应地函数代码,最后将执行结果返回给客户端。

但是,不同方式定义的 Controller,其函数的定义(函数名、入参、返回值等)是不统一的。如上示例低吗,方法一中的函数的定义很随意、不固定,方法二中的函数定义是 handleRequest(),方法三中的函数定义是 service() (看似定义了 doGet()doPost(),实际上,这里用到了模板模式,Servlet 中的 service() 调用了 doGet()doPost() 方法,DispatcherServlet 调用的是 service() 方法)。DispatcherServlet 需要根据不同类型的 Controller,调用不同的函数。下面是具体的伪代码:

Handler handler = handlerMapping.get(URL);
if(handler instanceof Controller) {((Controller)handler).handleRequest(...);
} else if(handler instanceof Servlet) {((Servlet)handler).service(...);
} else if(handler 对应通过注解来定义的Controller) {通过反射调用方法
} 

从代码中可以看出,这种实现方式会有很多 if-else 分支判断,而且,如果要增加一个新的 Controller 的定义方法,就要在 DispatcherServlet 类代码中,对应地增加一段如上伪代码所表示的 if 逻辑。这显然不符合开闭原则。

利用适配器模式对代码进行改造,让其满足开闭原则,能更好地支持扩展。在适配器模式章节中讲到,适配器其中的一个作用就是 “统一多个类的接口设计”。利用适配器模式,可以将不同方式定义的 Controller 类中的函数,适配为统一的函数定义。这样,就能在 DispatcherServlet 类代码中,移除掉 if-else 逻辑判断,调用统一的函数。

上面讲了大致的设计思路,下面在具体看看 Spring 的代码实现。

Spring 定义了统一接的接口 HandlerAdapter,并且对每种 Controller 定义了对应地适配器类。这些适配器类包括:SimpleControllerHandlerAdapterSimpleServletHandlerAdapter 等。

public interface HandlerAdapter {boolean supports(Object handler);@NullableModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;long getLastModified(HttpServletRequest request, Object handler);
}// 对应实现Conroller接口的Controller
public class SimpleControllerHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof Controller);}@Override@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);}@Overridepublic long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified) {return ((LastModified) handler).getLastModified(request);}return -1L;}
}// 对应实现Servlet接口的Controller
public class SimpleServletHandlerAdapter implements HandlerAdapter {@Overridepublic boolean supports(Object handler) {return (handler instanceof Servlet);}@Override@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {((Servlet) handler).service(request, response);return null;}@Overridepublic long getLastModified(HttpServletRequest request, Object handler) {return -1;}
}

DispatcherServlet 类中,就需要区分不同 Controller 对象了,统一调用 HandlerAdapterhandler() 函数就可以了。按照这个实现思路实现的伪代码如下所示。

Handler handler = handlerMapping.get(URL);
if(handler instanceof Controller) {((Controller)handler).handleRequest(...);
} else if(handler instanceof Servlet) {((Servlet)handler).service(...);
} else if(handler 对应通过注解来定义的Controller) {通过反射调用方法
} // 现在的实现方式
HandlerAdapter handlerAdapter = handlerMapping.get(URL);
handlerAdapter.handle(...);

策略模式在 Spring 中的应用

在代理模式章节讲过,Spring AOP 是通过动态代理来实现的。熟悉 Java 的同学应该知道,具体到代码实现,Spring 支持两种动态代理实现方式,一种是 JDK 提供的动态代理实现方式,另一种是 Cglib 提供的动态代理实现方式。

前者要被代理类有抽象的接口定义,而后者不需要。针对不同的被代理类,Spring 会在运行时动态地选择不同的代理实现方式。这个应用场景实际上就是策略模式的经典应用场景。

前面章节讲过,策略模式包含三部分,策略的定义、创建和使用。接下来,具体看下,这三部分如何体现在 Spring 源码中。

在策略模式中,策略定义这一部分很简单。我们只需要定义一个策略接口,让不同的策略类都实现一个策略接口。对应到 Spring 源码,AopProxy 是策略接口,JdkDynamicAopProxyCglibAopProxy 是两个实现了策略接口的实现类。其中, AopProxy 的接口定义如下所示:

public interface AopProxy {Object getProxy();Object getProxy(@Nullable ClassLoader classLoader);
}

在策略模式中,策略的创建一般共同工厂方法来实现。对应到 Spring 源码,AopProxyFactory 是一个工厂接口,DefaultAopProxyFactory 是一个默认的工厂类,用来创建 AopProxy 对象。两种的源码如下所示:

public interface AopProxyFactory {AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {@Overridepublic 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);}}private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {Class<?>[] ifcs = config.getProxiedInterfaces();return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));}
}

策略模式的典型应用场景,一般是通过环境变量、状态值、计算结果等动态决定使用哪个策略。对应到 Spring 源码中,DefaultAopProxyFactory 类中的 createAopProxy() 函数的代码实现。其中,if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) 是动态选择哪种策略的判断条件。

组合模式在 Spring 中的应用

《剖析Spring框架中蕴含的经典设计原则》讲到 Spring “再封装、再抽象” 设计思想时,我们提到了 Spring Cache。Spring Cache 提供了一套 Cache 接口。使用它我们能够统一不同缓存的实现(Redis、Google Guava…)的不同访问方式。Spring 中针对不同缓存实现的不同缓存访问类,都依赖这个接口,比如:EhCacheCacheGuavaCacheNoOpCacheRedisCacheJCacheCacheConcurrentMapCacheCaffeineCacheCache 接口的源码如下所示:

public interface Cache {String getName();Object getNativeCache();@NullableValueWrapper get(Object key);@Nullable<T> T get(Object key, @Nullable Class<T> type);@Nullable<T> T get(Object key, Callable<T> valueLoader);void put(Object key, @Nullable Object value);@NullableValueWrapper putIfAbsent(Object key, @Nullable Object value);void evict(Object key);void clear();@FunctionalInterfaceinterface ValueWrapper {@NullableObject get();}@SuppressWarnings("serial")class ValueRetrievalException extends RuntimeException {@Nullableprivate final Object key;public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);this.key = key;}@Nullablepublic Object getKey() {return this.key;}}
}

在实际开发中,一个项目可能会用到多种不同的缓存,比如既用到 Google Guava 缓存,也用到 Redis 缓存。此外,同一个缓存实例,也可以根据业务的不同,分割成多个小的逻辑缓存单元(或者叫做命名空间)。

为了管理缓存,Spring 还提供了缓存管理功能。不过,它包含的功能很简单,主要有哲理两部分:一个是根据名字(创建 Cache 对象时要设置 name 属性)获取 Cache 对象;另一个是获取管理器管理的所有缓存的名字列表。对应地 Spring 源码如下所示:

public interface CacheManager {@NullableCache getCache(String name);Collection<String> getCacheNames();
}

如何来实现这两个接口呢?实际上,这就用到了之前讲过的组合模式。

前面章节讲过,组合模式主要应用在能表示成树形结构的一组数据上。树中的结点分为叶子结点和中间节点两类。对应到 Spring 源码,EhCacheManagerSimpleCacheManagerNoOpCacheManagerRedisCacheManager 等表示叶子结点,CompositeCacheManager 表示中间节点。

叶子结点包含的是它所管理的 Cache 对象,中间结点包含的是其他 CacheManager 管理器,既可以是 CompositeCacheManager,也可以是具体的管理器,比如 EhCacheManagerRedisCacheManager 等。

下面展示了 CompositeCacheManager 的代码。其中,getCache()getCacheNames() 两个函数的试下都用到的递归。这正式属性结构最能发挥优势的地方。

public class CompositeCacheManager implements CacheManager, InitializingBean {private final List<CacheManager> cacheManagers = new ArrayList<>();private boolean fallbackToNoOpCache = false;public CompositeCacheManager() {}public CompositeCacheManager(CacheManager... cacheManagers) {setCacheManagers(Arrays.asList(cacheManagers));}public void setCacheManagers(Collection<CacheManager> cacheManagers) {this.cacheManagers.addAll(cacheManagers);}public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) {this.fallbackToNoOpCache = fallbackToNoOpCache;}@Overridepublic void afterPropertiesSet() {if (this.fallbackToNoOpCache) {this.cacheManagers.add(new NoOpCacheManager());}}@Override@Nullablepublic Cache getCache(String name) {for (CacheManager cacheManager : this.cacheManagers) {Cache cache = cacheManager.getCache(name);if (cache != null) {return cache;}}return null;}@Overridepublic Collection<String> getCacheNames() {Set<String> names = new LinkedHashSet<>();for (CacheManager manager : this.cacheManagers) {names.addAll(manager.getCacheNames());}return Collections.unmodifiableSet(names);}
}

装饰器模式在 Spring 中的应用

缓存一般都是配合数据库来使用的。如果写缓存成功,但数据库事务回滚了,那缓存中就会有脏数据。为了解决这个问题,我们需要将缓存的写操作和数据库的写操作,放到同一个事务中,要么都成功,要么都失败。

实现这样一个功能,Spring 使用到了装饰器模式。TransactionAwareCacheDecorator 增加了对事务的支持,在事务提交、回滚时分别对 Cache 的数据进行了处理。

TransactionAwareCacheDecorator 实现 Cache 接口,并且将所有的操作都委托给 targetCache 来实现,对其中的写操作添加了事务功能。这是典型的装饰器模式的应用场景和代码实现。

public class TransactionAwareCacheDecorator implements Cache {private final Cache targetCache;public TransactionAwareCacheDecorator(Cache targetCache) {Assert.notNull(targetCache, "Target Cache must not be null");this.targetCache = targetCache;}public Cache getTargetCache() {return this.targetCache;}public String getName() {return this.targetCache.getName();}public Object getNativeCache() {return this.targetCache.getNativeCache();}@Nullablepublic ValueWrapper get(Object key) {return this.targetCache.get(key);}public <T> T get(Object key, @Nullable Class<T> type) {return this.targetCache.get(key, type);}@Nullablepublic <T> T get(Object key, Callable<T> valueLoader) {return this.targetCache.get(key, valueLoader);}public void put(final Object key, @Nullable final Object value) {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {public void afterCommit() {TransactionAwareCacheDecorator.this.targetCache.put(key, value);}});} else {this.targetCache.put(key, value);}}@Nullablepublic ValueWrapper putIfAbsent(Object key, @Nullable Object value) {return this.targetCache.putIfAbsent(key, value);}public void evict(final Object key) {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {public void afterCommit() {TransactionAwareCacheDecorator.this.targetCache.evict(key);}});} else {this.targetCache.evict(key);}}public void clear() {if (TransactionSynchronizationManager.isSynchronizationActive()) {TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {public void afterCommit() {TransactionAwareCacheDecorator.this.targetCache.clear();}});} else {this.targetCache.clear();}}
}

工厂模式在 Spring 中的应用

在 Spring 中,工厂模式最经典的应用莫过于实现 IOC 容器,对应的 Spring 源码主要是 BeanFactory 类和 ApplicationContext 相关类(ClassPathXmlApplicationContextFileSystemXmlApplicationContext、…)。此外,在理论部分,我还带你实现了一个简单的 IOC 容器,你可以回过头去看下。

在 Spring 中,创建 Bean 的方式有很多种,比如前面提到的纯构造函数、无参构造函数加 setter 方法。我写了个例子来说明这两种创建方式,代码如下所示。

public class Student {private long id;private String name;public Student() {}public Student(long id, String name) {this.id = id;this.name = name;}public void setId(long id) {this.id = id;}public void setName(String name) {this.name = name;}
}// 使用构造函数来创建Bean
<bean id="student" class="com.example.Student"><constructor-arg name="id" value="1"/><constructor-arg name="name" value="xiaoming"/>
</bean>// 使用无参构造函数+setter方法来创建Bean
<bean id="student" class="com.example.Student"><property name="id" value="1"></property ><property  name="name" value="xiaoming"></property >
</bean>

实际上,除了这两种创建 Bean 的方式外,还可以通过工厂方法来创建 Bean。还是刚刚那个里子,用这种方式来创建 Bean 的话就是下面这个样子:

public class StudentFactory {private static Map<Long, Student> students = new HashMap<>();static {students.put(1L, new Student(1, "zhangsan"));students.put(2L, new Student(1, "lisi"));students.put(3L, new Student(1, "wangwu"));}public static Student getStudent(long id) {return students.get(id);}
}// 通过工厂方法getStudent(2)来创建BeanId="zhangsan"的Bean
<bean id="zhangsan" class="com.example.StudentFactory" factory-method="getStudent"><constructor-arg value="2"></constructor-arg>
</bean>

其他模式在 Spring 中的应用

接下来的几个模式,大部分都是之前讲过的,这里只是简单总结下。

SpEL,全称 Spring Expression Language,是 Spring 中常用来编写配置的表达式语言。它定义了一系列的语法规则。只要按照这些语法规则来编写表达式,Spring 就能解析出来表达式的含义。实际上,这就是我们前面讲过的解释器模式的典型应用场景。

因为解释权模式没有一个固定的代码实现结构,而且 Spring 中 SpEL 相关的代码也比较堵,所以这里就不展示源码了。如果感兴趣或者项目中正好要实现类似的功能时,可以再去阅读、借鉴它的代码实现。代码主要集中在 spring-expression 这个模块下面。

前面讲到单例模式时,我提到过单例模式有很多的弊端,比如单元测试不友好等。应对策略就是通过 IOC 容器来管理对象,通过 IOC 容器来实现对象的唯一性的控制。实际上,这样实现的单例并非真正的单例,它的唯一性的作用范围仅仅在同一个 IOC 容器内。

此外,Spring 还用到了观察者模式、模板模式、职责链模式、代理模式。其中,观察者模式和模板模式在上篇文章已经讲过了。

实际上,在 Spring 中,只要后缀带有 Template 的类,基本上都是模板类,而且大部分都是用 Callback 来实现,比如 JdbcTemplateRedisTemplate 等。剩下的两个模式在 Spring 中的应用应该人尽皆知了。职责链模式在 Spring 中的应用是拦截器(Interceptor),代理模式的经典应用是 AOP。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/2991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue项目中定位组件来源的查找思路

vue项目中定位组件来源的查找思路 先去【package.json】里面看看有没有看【a】开头或者【a-】开头的插件名 例如&#xff1a;如果我不知道【el-tree】&#xff0c;先去【package.json】里面找【el】或者【el-】开头的插件名&#xff0c;结果知道了【element-ui】这样就可以直接…

更新至2022年上市公司数字化转型数据合集(四份数据合集)

更新至2022年上市公司数字化转型数据合集&#xff08;四份数据合集&#xff09; 一、2000-2022年上市公司数字化转型数据&#xff08;年报词频、文本统计&#xff09; 二、2007-2022年上市公司数字化转型数据&#xff08;年报和管理层讨论&#xff09;&#xff08;含原始数据…

微前端是如何实现作用域隔离的?

微前端是如何实现作用域隔离的&#xff1f; 一、前言 沙箱&#xff08;Sandbox&#xff09;是一种安全机制&#xff0c;目的是让程序运行在一个相对独立的隔离环境&#xff0c;使其不对外界的程序造成影响&#xff0c;保障系统的安全。作为开发人员&#xff0c;我们经常会同沙…

UE5 GAS开发P35,36,37,38,39 将药水修改为AbilitySystem效果

这几节课都是将药水修改成更方便使用的AbilitySystem效果的Actor,分别为增加血量,增加蓝量,暂时获得最大生命值上限 AuraEffectActor.h // Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #…

设计模式- 策略模式(Strategy Pattern)结构|原理|优缺点|场景|示例

设计模式&#xff08;分类&#xff09; 设计模式&#xff08;六大原则&#xff09; 创建型&#xff08;5种&#xff09; 工厂方法 抽象工厂模式 单例模式 建造者模式 原型模式 结构型&#xff08;7种&#xff09; 适配器…

前端vue+xgVIdeo集成rstp流播放

注意&#xff1a;rstp流需要对应的西瓜视频插件 项目&#xff1a; petition-manager 代码概览&#xff1a; 1. video-player 子 组件 <template><div id"video-player" class"video-player"></div> </template> <script&g…

Java面试之封装、继承和多态(简洁易懂版)

一、封装&#xff1a; 1.1、什么是封装&#xff1f; 封装是指将类的某些信息隐藏在类内部&#xff0c;不允许外部直接访问&#xff0c;而是通过类提供的方法来实现对隐藏信息的操作和访问。通过封装&#xff0c;可以提高代码的安全性和可靠性。在Java中&#xff0c;使用访问修…

介绍一个开源IOT组态项目

项目介绍 金合可视化平台是一款强大而操作简便的低代码平台&#xff0c;专为满足物联网领域的可视化开发需求而设计。通过该平台&#xff0c;用户可以利用拖拽配置的方式&#xff0c;轻松创建个性化的可视化大屏&#xff0c;无需熟练的编程技能&#xff0c;大幅提高了开发效率。…

图搜索的经典启发式算法A星(A*、A Star)算法详解

文章目录 1. 引言2. 广度优先搜索3. Dijkstra 算法4. 启发式优先搜索&#xff08;Heuristic&#xff09;4.1 贪心最佳优先搜索4.2 A*搜索 1. 引言 在许多场景中&#xff0c;我们常会遇到一类问题&#xff0c;即“找到一个位置到另一个位置的距离最短&#xff08;用时最少&…

抽象类和接口有什么区别?

1.抽象类&#xff08;abstract&#xff09;是事物的共有&#xff0c;主要是继承 接口&#xff08;interface&#xff09;是定义一组规范&#xff0c;主要是实现 2.抽象类是有构造方法 接口没有构造方法 3.抽象类有抽象方法&#xff0c;也有非抽象方法 接口自从jdk8之后&#xf…

使用 Rust 后,我​​使用 Python 的方式发生了变化

使用 Rust 后&#xff0c;我​​使用 Python 的方式发生了变化 Using type hints where possible, and sticking to the classic “make illegal state unrepresentable” principle. 尽可能使用类型提示&#xff0c;并坚持经典的“使非法状态不可表示”原则。 近年来&#xff…

【Pytorch】(十三)PyTorch模型部署: TorchScript

文章目录 &#xff08;十三&#xff09;PyTorch模型部署Pytorch动态图的优缺点TorchScriptPytorch模型转换为TorchScripttorch.jit.tracetorch.jit.scripttrace和script的区别总结script 和 trace 混合使用保存和加载模型 &#xff08;十三&#xff09;PyTorch模型部署 Pytorc…

科学高效备考AMC8和AMC10竞赛,吃透2000-2024年1850道真题和解析

如何科学、有效地备考AMC8、AMC10美国数学竞赛&#xff1f;多做真题&#xff0c;吃透真题是科学有效的方法之一&#xff0c;通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c;可以通过真题查漏补缺&#xff0c;更有针对性的…

jni 写日志

jni 写日志&#xff0c;每隔一分钟写一个日志文件 // 全局变量用于存储日志文件的日期和路径 std::string currentLogFile;// 获取当前日期时间的函数 std::string getCurrentDateTime() {time_t now time(0);struct tm *timeinfo localtime(&now);char buffer[80];strf…

Leetcode30-最小展台数量(66)

1、题目 力扣嘉年华将举办一系列展览活动&#xff0c;后勤部将负责为每场展览提供所需要的展台。 已知后勤部得到了一份需求清单&#xff0c;记录了近期展览所需要的展台类型&#xff0c; demand[i][j] 表示第 i 天展览时第 j 个展台的类型。 在满足每一天展台需求的基础上&am…

成功解决ImportError: cannot import name ‘builder‘ from ‘google.protobuf.internal

成功解决ImportError: cannot import name builder from google.protobuf.internal 目录 解决问题 解决思路 解决方法 解决问题 ImportError: cannot import name builder from google.protobuf.internal 解决思路 导入错误:无法从“google.protobuf.internal”导入名称“…

在React函数组件中使用错误边界和errorElement进行错误处理

在React 18中,函数组件可以使用两种方式来处理错误: 使用 ErrorBoundary ErrorBoundary 是一种基于类的组件,可以捕获其子组件树中的任何 JavaScript 错误,并记录这些错误、渲染备用 UI 而不是冻结的组件树。 在函数组件中使用 ErrorBoundary,需要先创建一个基于类的 ErrorB…

三高架构是什么

三高架构&#xff0c;也称为三高模型&#xff0c;是指高并发、高可用、高性能的系统架构模型。它是在互联网时代应运而生的一种新型的软件架构&#xff0c;主要用于解决互联网系统架构中需要面对的关键问题。 高并发&#xff1a;指系统能够处理大量并发请求的能力。在高并发场…

课时105:正则表达式_进阶知识_扩展符号

1.1.1 扩展符号 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 字母模式匹配[:alnum:] 字母和数字[:alpha:] 代表任何英文大小写字符&#xff0c;亦即 A-Z, a-z[:lower:] 小写字母,示例:[[:lower:]],相当于[a-z][:upper:] 大…

VS使用技巧

VS使用技巧 1、展开和缩进代码2、代码注释和取消注释 1、展开和缩进代码 缩进&#xff1a;ctrlmo 展开&#xff1a;ctrlml 2、代码注释和取消注释 注释&#xff1a;ctrlkc 取消注释&#xff1a;ctrlku