设计模式学习笔记 - 开源实战四(下):总结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 {
    @Override
    public 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 {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected 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);

	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	
	long getLastModified(HttpServletRequest request, Object handler);
}

// 对应实现Conroller接口的Controller
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}
}

// 对应实现Servlet接口的Controller
public class SimpleServletHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Servlet);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((Servlet) handler).service(request, response);
		return null;
	}

	@Override
	public 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 {

	@Override
	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);
		}
	}

	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();
	@Nullable
	ValueWrapper 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);
	@Nullable
	ValueWrapper putIfAbsent(Object key, @Nullable Object value);
	void evict(Object key);
	void clear();


	@FunctionalInterface
	interface ValueWrapper {
		@Nullable
		Object get();
	}

	@SuppressWarnings("serial")
	class ValueRetrievalException extends RuntimeException {

		@Nullable
		private 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;
		}

		@Nullable
		public Object getKey() {
			return this.key;
		}
	}
}

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

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

public interface CacheManager {
	@Nullable
	Cache 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;
	}

	@Override
	public void afterPropertiesSet() {
		if (this.fallbackToNoOpCache) {
			this.cacheManagers.add(new NoOpCacheManager());
		}
	}


	@Override
	@Nullable
	public Cache getCache(String name) {
		for (CacheManager cacheManager : this.cacheManagers) {
			Cache cache = cacheManager.getCache(name);
			if (cache != null) {
				return cache;
			}
		}
		return null;
	}

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

    @Nullable
    public 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);
    }

    @Nullable
    public <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);
        }

    }

    @Nullable
    public 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.mfbz.cn/a/579570.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

pyaibote--安卓自动化环境配置与基础的使用方法

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 pyaibote介绍 pyaibote是一个全新&#xff0c;强大的办公自动化库。 支持找图&#xff0c;识别像素等操作。 比appium快十倍。 文章介绍 有大佬给我提到这个库后&#xff0c;我来查看。然后发现这个库太新了&am…

Coursera: An Introduction to American Law 学习笔记 Week 04: Constitutional Law

An Introduction to American Law 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors Week 04: Constitutional LawKey Constitutional Law TermsSup…

redission原理笔记

加锁成功的线程&#xff0c;将UUID和线程id和key绑定&#xff0c; 加锁成功后&#xff0c;内部有一个看门狗机制&#xff0c;每隔十秒看下当前线程是否还持有锁&#xff0c;延长生存时间。 没有获取锁的就一直自旋等待&#xff0c;直到超时。 如果redis是主从同步的&#xff0…

Android Studio gradle 默认sourceSets配置

一. AS默认的sourceSets配置 sourceSets在Android插件中如何使用的&#xff1a;android {sourceSets {main {manifest.srcFile AndroidManifest.xmljava.srcDirs [src]resources.srcDirs [src]aidl.srcDirs [src]renderscript.srcDirs [src]res.srcDirs [res]assets.srcD…

Anti Rookit -- 检测隐藏进程

Anti Rookit 一&#xff1a;检测隐藏进程 引言 检测隐藏进程除了众所周知的枚举进程ID之外&#xff0c;还有枚举句柄表的方式。不过今天给大家带来的是第三种方法。 探究 应用层通过接口 C r e a t e P r o c e s s \textcolor{cornflowerblue}{CreateProcess} CreateProcess…

现代信号处理7_最小二乘(CSDN_20240428)

最小二乘法最早由高斯在18世纪提出&#xff0c;几百年以来&#xff0c;这种方法一直被广泛应用。 最小二乘简介 这里是研究最小二乘的起点。随机变量只能存在与理论计算中&#xff0c;我们在工程实践中对随机变量的认识与理论计算中得到的关于随机变量的各种性质相比&#xff…

Penpad 再获 Animoca Brands 投资,全新生态历程

Penpad是Scroll生态的LaunchPad & Yield Aggregator平台&#xff0c;该平台近日在融资上取得了系列进展。据悉&#xff0c;Penpad在前不久率先获得了来自于Gate Labs以及Scroll联合创始人Sandy Peng的融资&#xff0c;并且在近日&#xff0c;其又获得了来自于知名加密投资机…

Coursera: An Introduction to American Law 学习笔记 Week 01: Tort Law

An Introduction to American Law 本文是 https://www.coursera.org/programs/career-training-for-nevadans-k7yhc/learn/american-law 这门课的学习笔记。 文章目录 An Introduction to American LawInstructors SyllabusWeek 01: Tort LawKey Tort Law TermsTort Law: Part …

2024.阳光能源追光计划暨大陆考察团交流分享会

近日大陆考察团抵达香港&#xff0c;受到了本司热情接待和安排。公司于4月27日下午举办了阳光能源追光计划主题交流会。 会上公司营销部总监张超&#xff0c;分享了阳光能源近几年的能源发展之路及公司新推出的追光计划&#xff0c;得到了大陆考察交流团团长杨国均先生的高度赞…

CSS3(响应式布局)

#过渡# 属性连写&#xff1a; transition: width 2s linear 1s; //前一个时间用于表示过渡效果持续时间&#xff0c;后一个时间用于表示过渡效果的延迟。 #转换# #2D转换# 和 #3D转换# 注意&#xff1a;其中angle对应单位为&#xff1a;deg #圆角# #边框# …

java中的泛型(三)——通配符

在前面的文章中我们简要介绍了泛型的概念以及泛型类和泛型方法的使用。在介绍泛型时我们说过在在java中一般用E、T、K、V、N、?这几个字母和符号来表示泛型&#xff0c;对于前面的几个字符它们的使用没有区别&#xff0c;只要注意它们所代表的类型就好。而对于最后一个&#x…

Oracle集群-常用查询及操作(工作日常整理)

1.Oracle集群状态 select * from gv$instance; 示例结果&#xff1a; 2.Oracle集群-增大表空间 常见问题&#xff1a; 导入时或使用时&#xff0c;提示无法extend table ,增加表空间即可 常用操作&#xff1a; 1&#xff09;查询表空间 select * from dba_tablespaces; --…

微信小程序[黑马笔记]

简介 常用组件 视图组件 <!--pages/list/list.wxml--><scroll-view class"container1" scroll-y><view>A</view><view>B</view><view>A</view></scroll-view><!--pages/list2/list.wxml--><swiper …

【禅道客户案例】同方智慧能源数智化转型新实践 禅道助力前行

同方智慧能源是同方股份有限公司的骨干企业。依托中核集团、清华大学的科技优势&#xff0c;坚持技术和资源双核驱动&#xff0c;基于30多年行业积淀&#xff0c;面向建筑、交通、工业、北方供热、数据中心等主要用能场景提供设计咨询、产品技术、投资建设、运营服务&#xff0…

设计模式学习笔记 - 项目实战一:设计实现一个支持各种算法的限流框架(实现)

概述 上篇文章&#xff0c;我们介绍了如何通过合理的设计&#xff0c;来实现框架的功能性需求的同时&#xff0c;满足易用、易扩展、灵活、低延迟、高容错等非功能性需求。在设计的过程中&#xff0c;我们也借鉴了之前讲过的一些开源项目的设计思想。比如 Spring 的低侵入松耦…

细致讲解——不同类型LSA是作用以及相互之间的联系

目录 一.常见的LSA类型 二.OSPF特殊区域 1.区域类型 2.stub区域和totally stub区域 &#xff08;1&#xff09;stub区域 &#xff08;2&#xff09;totally stub区域 3.nssa区域和totally nssa区域 &#xff08;1&#xff09;nssa区域 &#xff08;2&#xff09;totall…

【Android】SharedPreferences阻塞问题深度分析

前言 Android中SharedPreferences已经广为诟病&#xff0c;它虽然是Android SDK中自带的数据存储API&#xff0c;但是因为存在设计上的缺陷&#xff0c;在处理大量数据时很容易导致UI线程阻塞或者ANR&#xff0c;Android官方最终在Jetpack库中提供了DataStore解决方案&#xf…

微信小程序使用echarts实现条形统计图功能

微信小程序使用echarts组件实现条形统计图功能 使用echarts实现在微信小程序中统计图的功能&#xff0c;其实很简单&#xff0c;只需要简单的两步就可以实现啦&#xff0c;具体思路如下&#xff1a; 引入echarts组件调用相应的函数方法 由于需要引入echarts组件&#xff0c;代…

.net报错异常及常用功能处理总结(持续更新)

.net报错异常及常用功能处理总结---持续更新 1. WebApi dynamic传参解析结果中ValueKind Object处理方法问题描述方案1&#xff1a;(推荐&#xff0c;改动很小)方案2&#xff1a; 2.C# .net多层循环嵌套结构数据对象如何写对象动态属性赋值问题描述JavaScript动态属性赋值.net…

WebSocket通信协议

WebSocket是一种网络通信协议.RFC6455定义了它的通信标准 WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双向通信的协议 HTTP协议是一种无状态的,无连接的,单向的应用层协议.它采用了请求,响应的模式.通信请求只能由客户端发起,服务端对请求做出应答处理. 这种模型有…