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

11 篇文章 1 订阅
5 篇文章 1 订阅

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模块的设计。希望今天的这篇文章能够对你有所帮助。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值