深入挖掘Spring系列 -- 从设计模式角度看Spring

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生网关 MSE Higress,422元/月
简介: 深入挖掘Spring系列 -- 从设计模式角度看Spring

Spring的生态演进变化


Spring是一款伟大的框架产品,在发展过程中一直都是靠一家叫做Pivotal的技术公司在背后支撑。Spring真正流行的时间是在2007年11月份,发布了2.5版本的时候。


Spring Source 在3.0升级为了后续的发展所以拆分为了Spring Framework4.0 发布于2013年,随后Spring Boot发布于2014年,和传统的Spring Framework有所不同,SpringBoot是一款完全独立的产品路线,很多设计都是在为了简化对于Spring的使用而发明的。


2014年发布了一个Spring Cloud ,这是业界第一个完整的微服务解决方案。早期时候以Spring Cloud Netflix为代。这款框架在2015年3月开源,2018年12.12日后进入了维护模式。


Netflix公司对外开源其实目的是:


  • 想对外宣传,吸收外界的技术点,学习和完善现有的技术框架。
  • 试探市场中对于这套技术解决方案的接受性。
  • 挣钱。


2017年09月 Spring Framework发布了5.0


2019年08月01日 发布了Spring Cloud Alibaba 1.0


阿里巴巴和pivotal合作创建的一个框架标准


Spring用了哪些设计模式


首先来看下边这段代码案例:


package org.idea.spring.framework.http.util;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
/**
 * @Author linhao
 * @Date created in 9:09 下午 2021/5/22
 */
public class HttpRequestFactoryDemo {
    public static void main(String[] args) throws URISyntaxException, IOException {
        ClientHttpRequestFactory chrf = new SimpleClientHttpRequestFactory();
        ClientHttpRequest clientHttpRequest = chrf.createRequest(new URI("http://www.baidu.com"), HttpMethod.GET);
        ClientHttpResponse clientHttpResponse = clientHttpRequest.execute();
        InputStream inputStream = clientHttpResponse.getBody();
        String response = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
        inputStream.close();
        System.out.println(response);
    }
}
复制代码


一个简单的http发送,这里面主要使用的是Spring框架中的 ClientHttpRequestFactory 对象,这个对象也是RestTemplate中涉及到的一个重要组件,该对象在设计的时候,对http请求封装了一个工厂。例如我们的SimpleClientHttpRequestFactory 就是其中一种实现。从这个角度来看,这里采用了工厂相关的设计模式。


网络异常,图片无法展示
|


关于工厂模式


工厂模式包含了三种类型:


  • 简单工厂模式
  • 工厂方法模式 (这种用得不多,这里我直接略过)
  • 抽象工厂模式


简单工厂模式


这种设计比较好理解,我写了个简单的案例如下:


public class SessionFactory {
    public Session getSession(){
        /** 省略 **/
        return new Session();
    }   
}
复制代码


没有任何的接口定义,就是一个简单的Factory,专门负责生产指定的session。但是这种设计很明显存在扩展性的问题,假设后期需要融合更多种类的Session,就会出现以下情况:


public class SessionFactory {
    public Session getSession(){
        return new Session();
    }
    public RedisSession getRedisSession(){
        return new RedisSession();
    }
    public ZookeeperSession getZookeeperSession(){
        return new ZookeeperSession();
    }
    public WebSocketSession getWebSocketSession(){
        return new WebSocketSession();
    }
}
复制代码


每次新增一个Session的类型都需要对SessionFactory这个父类做修改,这样的设计导致了SessionFactory这个类包含了多种业务职责,代码只会越堆越多,职责变得混乱。


抽象工厂模式


将原先的sessionFactory抽象成为一个接口,然后各种类的session都统一继承一个父类。大体如下:


public class Session {
    String name;
    public Session(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Session{" +
                "name='" + name + '\'' +
                '}';
    }
}
复制代码


public class ZookeeperSession extends Session {
    public ZookeeperSession(String name){
        super(name);
    }
}
public class WebSocketSession extends Session {
    public WebSocketSession(String name) {
        super(name);
    }
}
public class RedisSession extends Session {
    public RedisSession(String name) {
        super(name);
    }
}
复制代码


SessionFactory模块改成:


public interface ISessionFactory {
    Session getSession();
}
复制代码


然后各自的子类进行基础


网络异常,图片无法展示
|


这样能够保证代码结构的设计遵守了开放封闭原则和依赖倒置原则。


好处:

从原先的单一工厂拆分为了多个工厂,不同工厂的含义不一样,满足了设计原则的单一职责。


将session的生成规则进行了封装,调用分不必关心session的生产过程。


不足点:

每次新增一个工厂都需要写一堆的代码。可以结合一些反射来进行优化。


Spring中的观察者模式


Spring内部的监听器使用场景:


package org.idea.spring.framework.event;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * 事件发布器
 *
 * @Author linhao
 * @Date created in 4:47 下午 2021/5/23
 */
public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.register(ApplicationEventPublisherDemo.class);
        annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<MyEvent>() {
            @Override
            public void onApplicationEvent(MyEvent event) {
                System.out.println("application listener 接收到消息:" + event);
            }
        });
        annotationConfigApplicationContext.refresh();
        annotationConfigApplicationContext.close();
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("====");
        applicationEventPublisher.publishEvent(new MyEvent("hello world") {
        });
        applicationEventPublisher.publishEvent("pay load event");
    }
}
复制代码


自定义一套属于自己的事件


package org.idea.spring.framework.event;
import org.springframework.context.ApplicationEvent;
/**
 * @Author linhao
 * @Date created in 6:21 下午 2021/5/23
 */
public class MyEvent extends ApplicationEvent {
    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public MyEvent(Object source) {
        super(source);
    }
}
复制代码


启动之后你会发现有如下消息:


网络异常,图片无法展示
|


其实代码里面发送了两条消息,但是监听器由于只是监听了MyEvent事件,所以这里没有将PayLoad事件打印出来。


这里所参考到的设计模式就是经典的观察者模式。


本质上观察者模式就是声明一个事件,然后观察者们都定于到这个事件中,并且使用一个类似list都数据结构将这些观察者们存放起来,最后通过遍历去触发它们对应的回调函数。类似的这种设计在JDK8中已经有提供标准的api,代码案例如下:


package org.idea.spring.framework.event;
import java.util.EventListener;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;
/**
 * @Author linhao
 * @Date created in 3:52 下午 2021/5/23
 */
public class EventDemo {
    public static void main(String[] arg) {
        EventObservable observable = new EventObservable();
        observable.addObserver(new EventObserver());
        observable.notifyObservers("send message");
    }
    static class EventObservable extends  Observable {
        @Override
        protected synchronized void setChanged() {
            super.setChanged();
        }
        @Override
        public void notifyObservers(Object data){
            this.setChanged();
            super.notifyObservers(new EventObject(data));
            clearChanged();
        }
    }
    static class EventObserver implements Observer, EventListener {
        @Override
        public void update(Observable o, Object msg) {
            EventObject eventObject = (EventObject) msg;
            System.out.println("接收数据:" + eventObject);
        }
    }
}
复制代码


关于事件部分的整体设计如下图所示:


网络异常,图片无法展示
|


Spring中的模版模式


在Spring内部源代码中,模版模式也是使用非常多的一种设计,例如我们的事务管理部分:


org.springframework.transaction.PlatformTransactionManager

org.springframework.transaction.support.AbstractPlatformTransactionManager


网络异常,图片无法展示
|


这里我以我们常用的DataSourceTransactionManager作为讲解案例分析:


网络异常,图片无法展示
|


DataSourceTransactionManager通常会在我们的数据库访问中处理一些和事务有关的会话信息,例如提交,回滚。那么这些统一的提交回滚方法其实都是由其父类所制定的。


网络异常,图片无法展示
|


这三段源代码主要目的就是查询对应url匹配到适配器,从而处理后台的响应逻辑部分代码。


Spring内部的策略模式


在之前我有一篇文章中介绍了关于Spring容器内部加载资源属性的一些技巧使用,其中有提到过Resource这个接口,其实Resource接口就是一层封装,对于不同的资源处理有不同的子类实现。


Spring中获取资源的方式一共有以下四种:


  • 通过Resource接口获取资源
  • 通过ResourceLoader接口获取资源
  • 通过ApplicationContext获取资源
  • 将resource注入到bean中的方式获取资源


实现类 描述
ClassPathResource 通过类路径获取资源文件
FileSystemResource 通过文件系统获取资源
UrlResource 通过URL地址获取资源
ByteArrayResource 获取字节数组封装的资源
ServletContextResource 获取ServletContext环境下的资源
InputStreamResource 获取输入流封装的资源


这部分我们可以通过一个代码案例来实践理解下:


package org.idea.spring.resource;
import org.springframework.core.io.*;
/**
 * @Author linhao
 * @Date created in 7:38 下午 2021/5/23
 */
public class ResourceLoaderDemo {
    public static void main(String[] args) {
        ResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource("http://www.baidu.com");
        System.out.println(resource instanceof UrlResource);
        Resource textResource = loader.getResource("classpath:test.txt");
        System.out.println(textResource instanceof ClassPathResource);
    }
}
复制代码


从这段代码的执行结果可以看出ResourceLoader会根据我们输入到字符串规则来匹配不同的资源加载规则。


网络异常,图片无法展示
|


而实际上呢,当我们进入源代码去debug查阅到时候也会发现确实逻辑是这么实现的。

在实际应用中,我们也可以从ApplicationContext中去获取Resource对象,代码如下:


public class MyResource implements ApplicationContextAware {
        private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void resource() throws IOException {
        Resource resource = applicationContext.getResource("file:D:\\test.txt");
        System.out.println(resource.getFilename());
        System.out.println(resource.contentLength());
    }
}
复制代码


Spring内部的代理模式


代理模式这块比较代表性的就是aop这部分了,解决代码复用,公共函数抽取,简化开发,业务之间的解耦;例如日志功能,因为日志功能往往横跨系统中的每个业务模块,使用 AOP 可以很好的将日志功能抽离出来。


关于Spring内部的AOP的代理实现主要有cglib和JDK两种方式,源代码核心部分如下:


网络异常,图片无法展示
|


这部分代码可能有不少读者会有疑惑:


为什么代码中那么推崇JDK代理,而不是CGLIB代理呢,不是说CGLIB代理要比JDK代理效率更高吗?


误区解释:我在初期学习CGLIB和JDK代理的时候在网上看了不少的资料都说CGLIB代理的效率要比JDK代理高很多,但是通过实战之后发现,高版本的JDK(如JDK8)已经对其自身的代理机制做了优化,性能要比CGLIB更佳。


另外在Spring的官网上也看到了这么一段话:


网络异常,图片无法展示
|


关于代理部分的总结


1.默认使用 JDK 动态代理,这样便可以代理所有的接口类型(interface)


2.Spring AOP也支持CGLIB的代理方式。如果我们被代理对象没有实现任何接口,则默认是CGLIB


3.我们可以强制使用CGLIB,指定proxy-target-class = “true” 或者 基于注解@EnableAspectJAutoProxy(proxyTargetClass = true)

其实关于Spring内部的设计模式使用场景还有很多,大部分都是多种设计模式的混合使用,例如BeanFactory模块的设计。希望今天的这篇文章能够对你有所帮助。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
设计模式 SQL Java
Spring中的设计模式
Spring中的设计模式
|
2月前
|
设计模式 缓存 Java
面试题:谈谈Spring用到了哪些设计模式?
面试题:谈谈Spring用到了哪些设计模式?
|
3月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
4月前
|
设计模式 SQL Java
一探到底!Spring团队使用的那些设计模式,快来看看你用过哪几个
该文章主要介绍了Spring框架中使用的设计模式,并列举了一些常见的设计模式及其在Spring框架中的应用。
一探到底!Spring团队使用的那些设计模式,快来看看你用过哪几个
|
4月前
|
设计模式 缓存 Java
深入Spring Boot启动过程:揭秘设计模式与代码优化秘籍
深入Spring Boot启动过程:揭秘设计模式与代码优化秘籍
|
6月前
|
设计模式 Java 程序员
Spring用到了哪些设计模式?
Spring用到了哪些设计模式?
48 1
|
7月前
|
设计模式 安全 Java
【初学者慎入】Spring源码中的16种设计模式实现
以上是威哥给大家整理了16种常见的设计模式在 Spring 源码中的运用,学习 Spring 源码成为了 Java 程序员的标配,你还知道Spring 中哪些源码中运用了设计模式,欢迎留言与威哥交流。
382 3
|
7月前
|
设计模式 Java 数据库连接
Spring Framework 6 中的设计模式
Spring Framework 6 中的设计模式
48 1
|
7月前
|
设计模式 Java 数据库连接
Spring 中经典的 9 种设计模式
Spring 中经典的 9 种设计模式
109 2
下一篇
DataWorks