浅述 Spring / SpringMVC 框架中用到的哪些设计模式(上)

简介: Design Patterns(设计模式) 是面向对象软件开发中最好的计算机编程实践。 Spring 框架中广泛使用了不同类型的设计模式,下面我们来看看 Spring 到底有哪些设计模式?

工厂模式

Spring使用工厂模式通过 BeanFactoryApplicationContext 创建 bean 对象。

首先,我们聊下 IOC,IoC(Inversion of Control, 控制翻转) 是 Spring 中一个非常非常重要的概念,它不是什么技术,而是一种解耦的设计思想。它的主要目的是借助于“第三方”(即Spring 中的 IOC 容器) 实现具有依赖关系的对象之间的解耦(通过 IOC 容器管理对象,你只管使用即可),从而降低代码之间的耦合度。IOC 是一个设计原则,而不是一个模式

Spring IOC 容器就是一个大的工厂,把所有的bean实例都给放在了spring容器里,如果你要使用bean,就找spring容器就可以了,自己不用创建对象了,具体流程如下图所示:

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


BeanFactoryApplicationContext的区别

  • BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext来说,会占用更少的内存,程序启动速度更快。
  • ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有的 bean 。

BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能之外还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。

ApplicationContext 的三个实现类

  • ClassPathXmlApplication:把上下文文件当成类路径资源。
  • FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
  • XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。


单例设计模式

Spring 中 bean 的默认作用域就是 singleton (单例)。

Spring通过ConcurrentHashMap实现单例注册表的方式来实现单例模式。

// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "'beanName' must not be null");
        synchronized (this.singletonObjects) {
            // 检查缓存中是否存在实例  
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // 此处省略了很多代码
                try {
                    singletonObject = singletonFactory.getObject();
                }
                // 此处省略了很多代码
                // 如果实例对象在不存在,我们注册到单例注册表中。
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }
    //将对象添加到单例注册表
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
        }
    }
}
复制代码


Spring实现单例的两种方式:

  • xml: <bean id="userService" class="top.snailclimb.UserService" scope="singleton"/>
  • 注解: @Scope(value = "singleton")

实际上,在我们的系统中,有一些对象其实只需要一个示例,比如说:线程池、缓存、注册表、日志对象、数据库连接池、显卡等设备驱动程序的对象。这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。

另外,Spring 中 Bean,除了singleton作用域还有下面几种作用域:

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP request 内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session : 全局 session 作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。


代理模式

Spring AOP 功能就是基于动态代理的实现的。

在Spring中,如果要代理的对象实现了某个接口,那么Spring AOP 会使用 JDK Proxy ,去创建代理对象,而对于没有实现接口的对象,Spring AOP 会使用 Cglib,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理。

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


AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。


模板方法模式

模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。

在 Spring 中, jdbcTemplatehibernateTemplate以 Template 结尾的对数据库操作的类,它们就使用到了模板方法模式。

一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback(回调) 模式与模板方法模式配合,模板模式用于对一些不太变化的流程进行模板化,与回调模式结合,可以将变化的部分出离出来,使用回调模式实现。然后根据不同的情况,向template中注入不同的callback,那些模板代码就没有必要重复写了。这样既达到了代码复用的效果,同时又增加了灵活性。

例如:Hibernate Template 提供了常规的 CRUD 操作,但是Hibernate Template的封装也使程序失去了hibernate中直接使用Session进行操作的灵活性,所以Hibernate Template提供了execute(CallBack action)等系列方法,允许程序员实现自己的HibernateCallBack,实现具体的逻辑。

@FunctionalInterface
public interface HibernateCallback<T> {
   /**
    * Gets called by {@code HibernateTemplate.execute} with an active
    * Hibernate {@code Session}. Does not need to care about activating
    * or closing the {@code Session}, or handling transactions.
    * <p>Allows for returning a result object created within the callback,
    * i.e. a domain object or a collection of domain objects.
    * A thrown custom RuntimeException is treated as an application exception:
    * It gets propagated to the caller of the template.
    *
    * @param session active Hibernate session
    * @return a result object, or {@code null} if none
    * @throws HibernateException if thrown by the Hibernate API
    * @see HibernateTemplate#execute
    */
   @Nullable
   T doInHibernate(Session session) throws HibernateException;
}
public interface HibernateOperations {
    ...
    @Nullable
    <T> T execute(HibernateCallback<T> action) throws DataAccessException;
    ...
}
public class HibernateTemplate implements HibernateOperations, InitializingBean {
    @Override
    @Nullable
    public <T> T execute(HibernateCallback<T> action) throws DataAccessException {
       return doExecute(action, false);
    }
    @Nullable
    protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException {
       Assert.notNull(action, "Callback object must not be null");
       Session session = null;
       boolean isNew = false;
       ...
       try {
          enableFilters(session);
          Session sessionToExpose = (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
          return action.doInHibernate(sessionToExpose);
       }
       ...
       finally {
          if (isNew) {
             SessionFactoryUtils.closeSession(session);
          }
          else {
             disableFilters(session);
          }
       }
    }
    ...
}
复制代码


装饰者模式

Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。

通常,Spring 中用到的装饰器模式,在类名上大都含有 Wrapper 或者 Decorator,这些类基本都是动态地给对象添加额外的职责。

例如,DataSource接口继承了Wrapper接口:

# 抽象角色
public interface DataSource extends CommonDataSource, Wrapper {
    // 尝试与此数据源对象表示的数据源建立连接。
    Connection getConnection() throws SQLException;
    Connection getConnection(String username, String password) throws SQLException;
}
# 装饰类
public class DelegatingDataSource implements DataSource, InitializingBean {
    @Nullable
    private DataSource targetDataSource;
    public DelegatingDataSource() {
    }
    public DelegatingDataSource(DataSource targetDataSource) {
        this.setTargetDataSource(targetDataSource);
    }
    public void setTargetDataSource(@Nullable DataSource targetDataSource) {
        this.targetDataSource = targetDataSource;
    }
    @Nullable
    public DataSource getTargetDataSource() {
        return this.targetDataSource;
    }
    protected DataSource obtainTargetDataSource() {
        DataSource dataSource = this.getTargetDataSource();
        Assert.state(dataSource != null, "No 'targetDataSource' set");
        return dataSource;
    }
    public void afterPropertiesSet() {
        if (this.getTargetDataSource() == null) {
            throw new IllegalArgumentException("Property 'targetDataSource' is required");
        }
    }
    public Connection getConnection() throws SQLException {
        return this.obtainTargetDataSource().getConnection();
    }
    public Connection getConnection(String username, String password) throws SQLException {
        return this.obtainTargetDataSource().getConnection(username, password);
    }
    public PrintWriter getLogWriter() throws SQLException {
        return this.obtainTargetDataSource().getLogWriter();
    }
    public void setLogWriter(PrintWriter out) throws SQLException {
        this.obtainTargetDataSource().setLogWriter(out);
    }
    public int getLoginTimeout() throws SQLException {
        return this.obtainTargetDataSource().getLoginTimeout();
    }
    public void setLoginTimeout(int seconds) throws SQLException {
        this.obtainTargetDataSource().setLoginTimeout(seconds);
    }
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return iface.isInstance(this) ? this : this.obtainTargetDataSource().unwrap(iface);
    }
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return iface.isInstance(this) || this.obtainTargetDataSource().isWrapperFor(iface);
    }
    public Logger getParentLogger() {
        return Logger.getLogger("global");
    }
}
复制代码


还有,ServerHttpResponse具有ServerHttpResponseDecorator实现类。

// 装饰类
public class ServerHttpResponseDecorator implements ServerHttpResponse {
   private final ServerHttpResponse delegate;
   public ServerHttpResponseDecorator(ServerHttpResponse delegate) {
      Assert.notNull(delegate, "Delegate is required");
      this.delegate = delegate;
   }
   public ServerHttpResponse getDelegate() {
      return this.delegate;
   }
   // ServerHttpResponse delegation methods...
   @Override
   public boolean setStatusCode(@Nullable HttpStatus status) {
      return getDelegate().setStatusCode(status);
   }
   @Override
   public HttpStatus getStatusCode() {
      return getDelegate().getStatusCode();
   }
   @Override
   public HttpHeaders getHeaders() {
      return getDelegate().getHeaders();
   }
   @Override
   public MultiValueMap<String, ResponseCookie> getCookies() {
      return getDelegate().getCookies();
   }
   @Override
   public void addCookie(ResponseCookie cookie) {
      getDelegate().addCookie(cookie);
   }
   @Override
   public DataBufferFactory bufferFactory() {
      return getDelegate().bufferFactory();
   }
   ...
}
复制代码


观察者模式

观察者模式是一种对象行为型模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。

Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。比如,我们每次添加商品的时候都需要重新更新商品索引,这个时候就可以利用观察者模式来解决这个问题。


Spring 事件驱动模型中的三种角色

事件角色

ApplicationEvent充当事件的角色,这是一个抽象类,它继承了java.util.EventObject

Spring 中默认存在以下事件,他们都是对 ApplicationContextEvent 的实现(它继承自ApplicationEvent):

  • ContextStartedEventApplicationContext 启动后触发的事件;
  • ContextStoppedEventApplicationContext 停止后触发的事件;
  • ContextRefreshedEventApplicationContext 初始化或刷新完成后触发的事件;
  • ContextClosedEventApplicationContext 关闭后触发的事件。

事件监听者角色

ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent

ApplicationListener接口类源码如下。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}
复制代码


在 Spring中我们只要实现 ApplicationListener 接口的 onApplicationEvent() 方法即可完成监听事件。

事件发布者角色

ApplicationEventPublisher 充当了事件的发布者,它也是一个接口。

@FunctionalInterface
public interface ApplicationEventPublisher {
    default void publishEvent(ApplicationEvent event) {
        this.publishEvent((Object)event);
    }
    void publishEvent(Object var1);
}
复制代码


ApplicationEventPublisher 接口的publishEvent()这个方法AbstractApplicationContext类中被实现。实际上,事件真正是通过ApplicationEventMulticaster来广播出去的。

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

相关文章
|
22天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
36 0
|
26天前
|
前端开发 Java 数据库连接
Spring 框架:Java 开发者的春天
Spring 框架是一个功能强大的开源框架,主要用于简化 Java 企业级应用的开发,由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立,并由Pivotal团队维护。
43 1
Spring 框架:Java 开发者的春天
|
19天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
37 2
|
18天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
74 1
|
26天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
1月前
|
人工智能 Java API
阿里云开源 AI 应用开发框架:Spring AI Alibaba
近期,阿里云重磅发布了首款面向 Java 开发者的开源 AI 应用开发框架:Spring AI Alibaba(项目 Github 仓库地址:alibaba/spring-ai-alibaba),Spring AI Alibaba 项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。本文将详细介绍 Spring AI Alibaba 的核心特性,并通过「智能机票助手」的示例直观的展示 Spring AI Alibaba 开发 AI 应用的便利性。示例源
|
1月前
|
人工智能 开发框架 Java
总计 30 万奖金,Spring AI Alibaba 应用框架挑战赛开赛
Spring AI Alibaba 应用框架挑战赛邀请广大开发者参与开源项目的共建,助力项目快速发展,掌握 AI 应用开发模式。大赛分为《支持 Spring AI Alibaba 应用可视化调试与追踪本地工具》和《基于 Flow 的 AI 编排机制设计与实现》两个赛道,总计 30 万奖金。
|
1月前
|
Java 数据库连接 数据库
让星星⭐月亮告诉你,SSH框架01、Spring概述
Spring是一个轻量级的Java开发框架,旨在简化企业级应用开发。它通过IoC(控制反转)和DI(依赖注入)降低组件间的耦合度,支持AOP(面向切面编程),简化事务管理和数据库操作,并能与多种第三方框架无缝集成,提供灵活的Web层支持,是开发高性能应用的理想选择。
36 1