浅述 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来广播出去的。

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

相关文章
|
1天前
|
前端开发 Java 数据库连接
Spring框架初识
Spring 是一个分层的轻量级开源框架,核心功能包括控制反转(IOC)和面向切面编程(AOP)。主要模块有核心容器、Spring 上下文、AOP、DAO、ORM、Web 模块和 MVC 框架。它通过 IOC 将配置与代码分离,简化开发;AOP 提供了声明性事务管理等增强功能。
33 21
Spring框架初识
|
8天前
|
XML Java 开发者
通过springboot框架创建对象(一)
在Spring Boot中,对象创建依赖于Spring框架的核心特性——控制反转(IoC)和依赖注入(DI)。IoC将对象的创建和管理交由Spring应用上下文负责,开发者只需定义依赖关系。DI通过构造函数、setter方法或字段注入实现依赖对象的传递。Spring Boot的自动配置机制基于类路径和配置文件,自动为应用程序配置Spring容器,简化开发过程。Bean的生命周期包括定义扫描、实例化、依赖注入、初始化和销毁回调,均由Spring容器管理。这些特性提高了开发效率并简化了代码维护。
|
19天前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
110 29
|
1月前
|
开发框架 运维 监控
Spring Boot中的日志框架选择
在Spring Boot开发中,日志管理至关重要。常见的日志框架有Logback、Log4j2、Java Util Logging和Slf4j。选择合适的日志框架需考虑性能、灵活性、社区支持及集成配置。本文以Logback为例,演示了如何记录不同级别的日志消息,并强调合理配置日志框架对提升系统可靠性和开发效率的重要性。
|
2月前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
128 13
|
2月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
29天前
|
XML Java 应用服务中间件
Spring Boot 两种部署到服务器的方式
本文介绍了Spring Boot项目的两种部署方式:jar包和war包。Jar包方式使用内置Tomcat,只需配置JDK 1.8及以上环境,通过`nohup java -jar`命令后台运行,并开放服务器端口即可访问。War包则需将项目打包后放入外部Tomcat的webapps目录,修改启动类继承`SpringBootServletInitializer`并调整pom.xml中的打包类型为war,最后启动Tomcat访问应用。两者各有优劣,jar包更简单便捷,而war包适合传统部署场景。需要注意的是,war包部署时,内置Tomcat的端口配置不会生效。
233 17
Spring Boot 两种部署到服务器的方式
|
29天前
|
Dart 前端开发 JavaScript
springboot自动配置原理
Spring Boot 自动配置原理:通过 `@EnableAutoConfiguration` 开启自动配置,扫描 `META-INF/spring.factories` 下的配置类,省去手动编写配置文件。使用 `@ConditionalXXX` 注解判断配置类是否生效,导入对应的 starter 后自动配置生效。通过 `@EnableConfigurationProperties` 加载配置属性,默认值与配置文件中的值结合使用。总结来说,Spring Boot 通过这些机制简化了开发配置流程,提升了开发效率。
61 17
springboot自动配置原理
|
1月前
|
XML JavaScript Java
SpringBoot集成Shiro权限+Jwt认证
本文主要描述如何快速基于SpringBoot 2.5.X版本集成Shiro+JWT框架,让大家快速实现无状态登陆和接口权限认证主体框架,具体业务细节未实现,大家按照实际项目补充。
87 11

热门文章

最新文章