“Spring 中用到了哪些设计模式?”,这个问题,在面试中常见问到,是常见的八股,今天我们就来简单的分析一下其中的设计模式。
设计模式,即Design Patterns,是指在软件设计中,被反复使用的一种代码设计经验。使用设计模式的目的是为了可重用代码,提高代码的可扩展性和可维护性。
简单工厂(Simple Factory)模式和工厂方法(Factory Method)模式是实现工厂设计模式的两种不同方式。
简单工厂模式中,一个工厂类根据传入的参数,决定创建哪一种产品类的实例。这种方式实现简单,易于理解,但当产品种类多、结构复杂时,工厂类职责过重,增加新的产品需要修改工厂逻辑,违背开闭原则。
工厂方法模式定义了一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。这样,当增加新产品时,只需增加相应的具体工厂类即可,无需修改已有代码,符合开闭原则。
在Spring框架中,BeanFactory 和 ApplicationContext 都提供了工厂模式的功能,用于创建和管理Java对象(bean)。BeanFactory 提供了基本的DI(依赖注入)功能,而 ApplicationContext 是 BeanFactory 的子接口,提供了更多的高级特性,如事件发布、国际化支持、Web应用上下文等。
BeanFactory 是Spring框架的基础设施,提供了配置框架和基本功能。它采用懒加载方式,即在需要某个bean时才进行创建和注入,这样做可以节省资源和时间。而 ApplicationContext 则在启动时一次性创建所有单例bean,它采用的是即时加载策略。这样做的好处是可以立即发现配置错误,但是会占用更多内存和延长启动时间。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。这种模式通常用于管理共享资源,如数据库连接、文件系统或硬件设备,这些资源在整个应用程序中应该只有一个实例,以避免数据不一致或资源冲突。
实现单例模式的关键是确保构造函数私有化,防止外部通过 new 关键字创建类的实例。同时,提供一个静态方法来获取该类的唯一实例。如果实例尚未创建,则在该方法中创建并存储一个实例,否则返回已存在的实例。
单例模式的几种常见实现方式包括:
在 Spring 框架中,默认的作用域是单例(singleton),这意味着 Spring 容器中每个 Bean 的定义只对应一个实例。这种方式的好处是减少了资源消耗,提高了性能,特别是在创建重量级对象时。
除了单例作用域,Spring 还提供了其他作用域,以满足不同场景的需求:
Spring 通过内部的 BeanFactory 和 ApplicationContext 容器来管理这些 Bean 实例,确保它们的生命周期和作用域符合开发者的配置。Spring 的单例实现是通过在内部维护一个注册表(ConcurrentHashMap),用于存储每个 Bean 的单例实例,从而保证在全局范围内只有一个实例被创建和共享。
适配器模式(Adapter)是一种结构型设计模式,它允许不兼容的接口协同工作,无需修改它们的代码。适配器通过在两个接口之间创建一个中间层,将一个接口转换成另一个接口,使得原本因为接口不兼容而不能一起工作的类可以一起工作。
在软件设计中,适配器模式通常用于以下场景:
1. 当你想要使用一个已经存在的类,但是它的接口不符合你的需求时。
2. 当你需要创建一个可重用的类,该类可以与不相关的或不可预见的类协同工作时。
3. 当你需要使用多个不兼容的类库时。
在 Spring AOP 中,适配器模式用于将不同类型的通知(Advice)适配成方法拦截器(MethodInterceptor)。这样,不论是什么类型的通知,都可以通过适配器转换成方法拦截器,从而能够在 AOP 框架中被统一处理。例如,MethodBeforeAdviceAdapter 类会将 MethodBeforeAdvice 适配成 MethodBeforeAdviceInterceptor。
在 Spring MVC 中,适配器模式用于将不同的控制器(Controller)适配成统一的处理器(Handler)。DispatcherServlet 通过 HandlerMapping 解析请求对应的 Handler,然后由 HandlerAdapter 适配器处理。HandlerAdapter 是一个期望接口,具体的适配器实现类用于对目标控制器类进行适配,使得不同的控制器能够以统一的方式被 DispatcherServlet 调用。
通过使用适配器模式,Spring 框架提供了一种灵活的方式来扩展和集成各种不同的组件,而不需要修改这些组件的源代码。这种设计使得 Spring 框架具有很高的扩展性和适应性,能够满足不同场景下的开发需求。
代理模式(Proxy)是一种结构型设计模式,它允许你为另一个对象提供一个代理或占位符,以便控制对这个对象的访问。代理模式的关键点是,代理对象和真实对象实现相同的接口,客户端与代理对象交互,而代理对象则将交互转发给真实对象,或者在转发之前进行一些额外的处理。
代理模式的主要目的是提供一种间接访问的方式,以便在不修改原始对象代码的情况下,增加额外的功能。这种模式在许多场景中非常有用,比如:
在 Spring AOP 中,代理模式被用于实现面向切面编程。AOP 允许开发者定义跨多个对象的横切关注点(cross-cutting concerns),如日志、安全和事务管理。通过使用代理,Spring AOP 可以在不修改目标对象代码的情况下,将这些横切关注点织入到应用程序中。
Spring AOP 使用两种代理方式:
1. JDK 动态代理:如果目标对象实现了至少一个接口,Spring AOP 将使用 JDK 动态代理。JDK 动态代理利用反射机制动态创建一个实现了目标对象接口的代理类。
2. CGLIB 代理:如果目标对象没有实现任何接口,Spring AOP 将使用 CGLIB 代理。CGLIB(Code Generation Library)是一个代码生成库,它可以在运行时动态生成目标类的子类,并在子类中覆盖非final方法以实现代理。
通过使用代理模式,Spring AOP 提供了一种强大的方式来增加横切关注点,从而提高了代码的重用性和模块间的解耦。这种设计使得开发者可以更加专注于业务逻辑,而不是重复的交叉问题代码。
观察者模式(Observer)是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并被自动更新。这种模式允许对象之间的松耦合,因为它们不需要显式地相互引用,而是通过抽象的观察者接口进行交互。
在 Spring 的事件驱动模型中,观察者模式得到了经典的应用。Spring 的事件机制允许我们在应用程序中发布和监听事件,这样可以解耦不同的组件,提高代码的可维护性和扩展性。
Spring 事件驱动模型中的主要角色包括:
1. 事件(ApplicationEvent):这是所有事件对象的基类,它继承了 java.util.EventObject,通常用于携带事件发生时的数据。Spring 提供了一些内置的事件,如 ContextStartedEvent、ContextStoppedEvent、ContextRefreshedEvent 和 ContextClosedEvent,这些事件在 ApplicationContext 的生命周期中触发。
2. 事件发布者(ApplicationEventPublisher):这是事件的发布者,任何类都可以成为事件发布者,只需注入 ApplicationEventPublisher 并调用 publishEvent 方法即可。在 Spring 中,ApplicationContext 实现了 ApplicationEventPublisher 接口,因此可以直接发布事件。
3. 事件监听者(ApplicationListener):这是事件的监听者,它是一个接口,包含一个 onApplicationEvent 方法,用于处理接收到的 ApplicationEvent。开发者可以通过实现 ApplicationListener 接口来创建自定义的事件监听器,并在事件发生时执行相应的逻辑。
使用 Spring 的事件驱动模型,开发者可以轻松地实现分布式事件通知,从而实现系统组件之间的解耦。例如,当一个商品被添加到系统中时,可以发布一个商品添加事件,其他感兴趣的组件(如搜索服务)可以通过监听这个事件来更新索引,而无需与商品管理组件直接交互。
通过这种方式,Spring 的观察者模式应用提供了一个灵活的事件处理机制,使得开发者可以更好地组织和维护复杂的业务逻辑。
策略模式(Strategy)是一种行为设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。策略模式通常用于当一个算法在多个地方使用,并且这些算法的实现可能经常变化或者需要根据不同的情况使用不同的算法时。
在 Spring 框架中,策略模式的应用可以通过 Resource 接口来看出。Resource 接口是 Spring 提供的一个用于抽象和访问资源的接口,它定义了一系列的方法来访问不同类型的资源,如文件系统、类路径、URL 等。Resource 接口本身不提供具体的资源访问逻辑,而是通过其实现类来提供。
Spring 为 Resource 接口提供了多种实现类,每种实现类都对应一种特定的资源访问策略:
1. UrlResource:用于访问 URL 指定的资源,比如文件系统中的资源、HTTP 链接等。
2. ClassPathResource:用于访问类路径下的资源,这些资源通常位于应用程序的 CLASSPATH 中。
3. FileSystemResource:用于访问文件系统中的资源。
4. ServletContextResource:用于访问 ServletContext 路径下的资源,适用于 Web 应用程序。
5. InputStreamResource:用于直接访问输入流,通常用于临时资源。
6. ByteArrayResource:用于访问字节数组作为资源的场合。
通过使用策略模式,Spring 框架能够提供一种统一的方式来访问不同的资源,同时保持高度的灵活性和可扩展性。开发者可以根据需要选择合适的 Resource 实现类,而无需关心资源访问的具体细节。这种设计使得资源访问逻辑与客户端代码解耦,客户端只需与 Resource 接口交互,而具体的资源访问策略可以在运行时动态确定。
适配器模式是一种结构型设计模式,它允许不兼容的接口协同工作,通过创建一个中间层(适配器)来进行接口转换。在软件设计中,适配器模式通常用于以下场景:
1. 当你想要使用一个已经存在的类,但是它的接口不符合你的需求时。
2. 当你需要创建一个可重用的类,该类可以与不相关的或不可预见的类协同工作时。
3. 当你需要使用多个不兼容的类库时。
在 Spring AOP 中,适配器模式用于将不同类型的通知(Advice)适配成方法拦截器(MethodInterceptor)。这样,不论是什么类型的通知,都可以通过适配器转换成方法拦截器,从而能够在 AOP 框架中被统一处理。例如,MethodBeforeAdviceAdapter 类会将 MethodBeforeAdvice 适配成 MethodBeforeAdviceInterceptor。
在 Spring MVC 中,适配器模式用于将不同的控制器(Controller)适配成统一的处理器(Handler)。DispatcherServlet 通过 HandlerMapping 解析请求对应的 Handler,然后由 HandlerAdapter 适配器处理。HandlerAdapter 是一个期望接口,具体的适配器实现类用于对目标控制器类进行适配,使得不同的控制器能够以统一的方式被 DispatcherServlet 调用。
为什么要在 Spring MVC 中使用适配器模式?
这是因为 Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式,DispatcherServlet 就需要直接获取对应类型的 Controller 并自行判断其类型,这将导致代码难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。通过使用适配器模式,Spring MVC 可以在不修改现有代码的情况下,轻松地添加新的 Controller 类型,从而提高系统的可扩展性和可维护性。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。