从理论到实战:23种设计模式如何在Spring框架中优雅落地

从理论到实战:23种设计模式如何在Spring框架中优雅落地

【免费下载链接】JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 【免费下载链接】JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

你是否还在为设计模式的抽象理论与实际应用脱节而烦恼?是否疑惑Spring这样的企业级框架究竟如何巧妙运用设计模式解决复杂问题?本文将带你深入Spring源码,揭示工厂模式、代理模式等核心设计模式的实战应用,让你彻底掌握从理论到实践的精髓。读完本文,你将能够清晰分辨各类设计模式在Spring中的具体实现,理解其设计初衷,并能在自己的项目中灵活运用这些模式解决实际问题。

控制反转:Spring设计模式的基石

控制反转(IoC, Inversion of Control)是Spring中一个极为重要的概念,它并非一种具体的设计模式,而是一种颠覆传统的解耦设计思想。其核心宗旨是借助"第三方"(也就是Spring中的IoC容器)来实现具有依赖关系的对象之间的解耦,从而显著降低代码之间的耦合度。

Spring IoC容器就如同一个精密运转的工厂,当我们需要创建对象时,只需通过配置文件或注解进行简单配置,无需关心对象创建的复杂过程。IoC容器全权负责对象的创建、依赖关系的连接、对象的配置以及整个生命周期的管理,从对象的创建一直到最终的销毁。

在实际项目开发中,如果一个Service类依赖数百个底层类,手动实例化这个Service将是一场噩梦,开发者需要理清所有底层类的构造函数。而有了IoC,开发者只需进行配置并在需要的地方引用即可,极大地提升了项目的可维护性,同时降低了开发难度。

相关概念可参考Spring IoC容器详解

工厂模式:Bean创建的幕后推手

Spring框架通过工厂模式来创建bean对象,主要借助BeanFactoryApplicationContext这两个核心接口实现。

BeanFactory采用延迟注入的方式,即只有在使用到某个bean的时候才会进行注入。这种方式的优势在于相比ApplicationContext能够占用更少的内存,并且程序启动速度更快。

ApplicationContext则与之不同,它在容器启动时就会一次性创建所有的bean。虽然BeanFactory仅提供了最基本的依赖注入支持,但ApplicationContext对其进行了扩展,除了具备BeanFactory的所有功能外,还提供了更多额外的特性,因此在实际开发中,开发人员更多使用ApplicationContext

ApplicationContext拥有三个常用的实现类:

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

以下是一个使用ApplicationContext创建bean的简单示例:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class App {
  public static void main(String[] args) {
    ApplicationContext context = new FileSystemXmlApplicationContext(
        "C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");

    HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
    obj.getMsg();
  }
}

单例模式:Bean实例的独特存在

在系统中,有些对象只需要一个实例,例如线程池、缓存、日志对象等。如果创建多个实例,可能会导致程序行为异常、资源使用过量或产生不一致的结果。

使用单例模式具有显著的好处:

  • 对于频繁使用的重量级对象,省略对象创建时间可以节省可观的系统开销。
  • 减少new操作的次数,降低系统内存的使用频率,从而减轻GC压力,缩短GC停顿时间。

Spring中bean的默认作用域就是singleton(单例)。除了singleton作用域,Spring中bean还有以下几种作用域:

  • prototype:每次获取都会创建一个新的bean实例。
  • request(仅Web应用可用):每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session(仅Web应用可用):每一次来自新session的HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
  • application/global-session(仅Web应用可用):每个Web应用在启动时创建一个Bean,该bean仅在当前应用启动时间内有效。
  • websocket(仅Web应用可用):每一次WebSocket会话产生一个新的bean。

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

需要注意的是,单例Bean可能存在线程安全问题。当多个线程操作同一个对象时,会存在资源竞争。常见的解决办法有两种:一是在Bean中尽量避免定义可变的成员变量;二是在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中,这是一种推荐的方式。不过,大部分Bean实际上是无状态的(如Dao、Service),这种情况下Bean是线程安全的。

代理模式:AOP功能的实现核心

代理模式在Spring AOP中得到了广泛应用。AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,但却为业务模块所共同调用的逻辑或责任(如事务处理、日志管理、权限控制等)进行封装,从而减少系统的重复代码,降低模块间的耦合度,有利于未来的可扩展性和可维护性。

Spring AOP基于动态代理实现,如果要代理的对象实现了某个接口,Spring AOP会使用JDK Proxy来创建代理对象;而对于没有实现接口的对象,无法使用JDK Proxy进行代理,此时Spring AOP会使用Cglib生成一个被代理对象的子类作为代理。

Spring AOP动态代理流程

当然,也可以使用AspectJ,Spring AOP已经集成了AspectJ,AspectJ是Java生态系统中功能最为完整的AOP框架。

使用AOP后,开发者可以将通用功能抽象出来,在需要用到的地方直接使用,大大简化了代码量。而且,增加新功能时也非常方便,提高了系统的扩展性。日志功能、事务管理等场景都广泛应用了AOP。

Spring AOP和AspectJ AOP存在一定区别:Spring AOP属于运行时增强,而AspectJ是编译时增强;Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)。

模板方法模式:简化数据库操作的利器

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

以下是一个模板方法模式的简单示例代码:

public abstract class Template {
    // 模板方法
    public final void TemplateMethod(){
        PrimitiveOperation1();
        PrimitiveOperation2();
        PrimitiveOperation3();
    }

    protected void PrimitiveOperation1(){
        // 当前类实现
    }

    // 被子类实现的方法
    protected abstract void PrimitiveOperation2();
    protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
    @Override
    public void PrimitiveOperation2() {
        // 当前类实现
    }

    @Override
    public void PrimitiveOperation3() {
        // 当前类实现
    }
}

Spring中的JdbcTemplateHibernateTemplate等以Template结尾的对数据库操作的类,都使用到了模板模式。通常情况下,实现模板模式会采用继承的方式,但Spring并未如此,而是巧妙地将Callback模式与模板方法模式配合使用,既达到了代码复用的效果,又增加了灵活性。

观察者模式:事件驱动模型的设计精髓

观察者模式是一种对象行为型模式,它表示对象与对象之间存在依赖关系,当一个对象发生改变时,依赖这个对象的所有对象都会做出相应的反应。Spring事件驱动模型就是观察者模式的一个经典应用,在很多场景下都可以帮助我们解耦代码。例如,每次添加商品时都需要重新更新商品索引,这时就可以利用观察者模式来解决。

Spring事件驱动模型包含三种角色,分别是事件角色、事件监听者角色和事件发布者角色。

事件角色由ApplicationEvent(位于org.springframework.context包下)充当,它是一个抽象类,继承了java.util.EventObject并实现了java.io.Serializable接口。Spring中默认存在ContextStartedEventContextStoppedEventContextRefreshedEventContextClosedEvent等事件,它们都实现了ApplicationContextEvent

ApplicationEvent类层次结构

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

事件发布者角色由ApplicationEventPublisher充当,它也是一个接口,其中的publishEvent()方法在AbstractApplicationContext类中被实现,实际上事件是通过ApplicationEventMulticaster来广播出去的。

Spring的事件流程可以总结为以下三步:

  1. 定义一个事件:实现继承自ApplicationEvent的类,并编写相应的构造函数。
  2. 定义一个事件监听者:实现ApplicationListener接口,重写onApplicationEvent()方法。
  3. 使用事件发布者发布消息:通过ApplicationEventPublisherpublishEvent()方法发布消息。

示例代码如下:

// 定义事件
public class DemoEvent extends ApplicationEvent{
    private static final long serialVersionUID = 1L;
    private String message;
    public DemoEvent(Object source,String message){
        super(source);
        this.message = message;
    }
    public String getMessage() {
         return message;
    }
}

// 定义事件监听者
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
    @Override
    public void onApplicationEvent(DemoEvent event) {
        String msg = event.getMessage();
        System.out.println("接收到的信息是:"+msg);
    }
}

// 发布事件
@Component
public class DemoPublisher {
    @Autowired
    ApplicationContext applicationContext;
    public void publish(String message){
        applicationContext.publishEvent(new DemoEvent(this, message));
    }
}

当调用DemoPublisherpublish()方法,如demoPublisher.publish("你好")时,控制台会打印出:接收到的信息是:你好

适配器模式:接口适配的桥梁

适配器模式(Adapter Pattern)的作用是将一个接口转换成客户希望的另一个接口,使得接口不兼容的那些类可以一起工作。

在Spring AOP中,增强或通知(Advice)使用到了适配器模式,与之相关的接口是AdvisorAdapter。Advice常用的类型有BeforeAdviceAfterAdviceAfterReturningAdvice等,每种类型的Advice都有对应的拦截器。Spring预定义的通知需要通过对应的适配器适配成MethodInterceptor接口(方法拦截器)类型的对象。

在Spring MVC中,DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler(即通常所说的Controller控制器)。解析到对应的Handler后,由HandlerAdapter适配器进行处理。HandlerAdapter作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller则作为需要适配的类。

在Spring MVC中使用适配器模式可以避免大量的条件判断语句。如果不使用适配器模式,DispatcherServlet直接获取对应类型的Controller时,需要判断Controller的类型并调用相应的方法,这会导致代码难以维护,也违反了开闭原则。

装饰者模式:功能增强的灵活方式

装饰者模式能够动态地给对象添加一些额外的属性或行为。与继承相比,装饰者模式更加灵活。简单来说,当需要修改原有功能,但又不愿直接修改原有代码时,可以设计一个Decorator来包裹原有代码。

在JDK中,InputStream家族就是装饰者模式的典型应用,FileInputStream(读取文件)、BufferedInputStream(增加缓存,提高读取文件速度)等子类都在不修改InputStream代码的情况下扩展了其功能。

装饰者模式示意图

在Spring中,配置DataSource时,可能需要根据客户需求动态切换不同的数据源。此时可以利用装饰者模式,在不修改原有类代码的情况下动态切换数据源。Spring中使用的包装器模式在类名上通常含有Wrapper或者Decorator,这些类基本上都是动态地给一个对象添加一些额外的职责。

Spring中设计模式应用总结

Spring框架中广泛应用了多种设计模式,以下是一些主要设计模式的应用场景:

  • 工厂设计模式:通过BeanFactoryApplicationContext创建bean对象。
  • 代理设计模式:Spring AOP功能的实现基础。
  • 单例设计模式:Spring中的Bean默认都是单例的。
  • 模板方法模式jdbcTemplatehibernateTemplate等以Template结尾的对数据库操作的类。
  • 包装器设计模式:可用于动态切换不同的数据源。
  • 观察者模式:Spring事件驱动模型的实现。
  • 适配器模式:Spring AOP的增强或通知(Advice)以及Spring MVC中对Controller的适配。

更多设计模式相关内容可参考设计模式常见面试题总结

通过深入理解Spring框架中设计模式的应用,我们不仅能够更好地掌握Spring的内部实现机制,还能将这些设计思想灵活运用到自己的项目开发中,提高代码的质量和可维护性。

如果你觉得本文对你有帮助,欢迎点赞、收藏、关注三连,后续还会有更多关于Spring框架和设计模式的深入解读!

【免费下载链接】JavaGuide JavaGuide:这是一份Java学习与面试指南,它涵盖了Java程序员所需要掌握的大部分核心知识。这份指南是一份通俗易懂、风趣幽默的学习资料,内容全面,深受Java学习者的欢迎。 【免费下载链接】JavaGuide 项目地址: https://gitcode.com/gh_mirrors/ja/JavaGuide

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值