JAVA设计模式第七讲:设计模式在 Spring 源码中的应用

设计模式(design pattern)是对软件设计中普遍存在的各种问题,所提出的解决方案。本文以面试题作为切入点,介绍了设计模式的常见问题。我们需要掌握各种设计模式的原理、实现、设计意图和应用场景,搞清楚能解决什么问题本文是第七篇:设计模式在 Spring 源码中的应用

文章目录

    • 1、Spring 框架中蕴含的经典设计思想或原则
    • 2、剖析Spring框架中用来支持扩展的两种设计模式
      • 2.1、观察者模式在 Spring 中的应用
    • Action1:Google Guava 的 EventBus 实现中,被观察者发送消息到事件总线,事件总线根据消息的类型,将消息发送给可匹配的观察者。那在 Spring 提供的观察者模式的实现中,是否也支持按照消息类型匹配观察者呢?
      • 2.2、模板模式在 Spring 中的应用
    • Action1:请你说下 Spring Bean 的创建过程包含哪些主要的步骤?
    • 3、总结Spring框架用到的11种设计模式
      • 3.1、适配器模式在 Spring 中的应用
      • 3.2、策略模式在Spring 中的应用
      • 3.3、组合模式在 Spring 中的应用
      • 3.4、装饰器模式在 Spring 中的应用
      • 3.5、工厂模式在 Spring 中的应用
      • 3.6、代理模式在 Spring 中的应用
      • 3.7、单例模式在 Spring 中的应用
      • 3.8、Spring MVC中的适配器模式
      • 3.9、职责链模式在 Spring 中的应用
    • 4、总结

1、Spring 框架中蕴含的经典设计思想或原则

  • 1、约定大于配置
    • 基于注解的配置方式,我们在指定类上使用指定的注解,来替代集中的 XML 配置
      • 使用@RequestMapping注解,在controller类或接口上,标注对应的URL,使用 @Transaction注解表明支持事务等
    • 基于约定的配置方式
      • 就是提供配置的默认值,优先使用默认值
      • 比如在 Spring JPA中,我们约定类名默认跟表名相同,属性名默认跟表字段名相同,String 类型对应数据库中的 varchar 类型,long 类型对应数据库中的 bigint 类型
      • 该方式很好地体现了“二八法则”
      • Gson也符合约定优于配置
  • 2、低侵入、松耦合
    • 框架代码很少耦合在业务代码中;
    • Spring 提供的 IOC 容器,在不需要 Bean 继承任何父类或者实现任何接口的情况下,仅仅通过配置,就能将它们纳入进 Spring 的管理中。如果我们换一个 IOC 容器,也只是重新配置一下就可以了,原有的 Bean 都不需要任何修改;
    • 基于 AOP 这种开发模式,将非业务代码集中放到切面中,删除、修改的成本就变得很低
  • 3、模块化、轻量级
    • 从下图我们可以看出,Spring 在分层、模块化方面做得非常好。每个模块都只负责一个相对独立的功能。模块之间关系,仅有上层对下层的依赖关系,而同层之间以及下层对上层,几乎没有依赖和耦合;
    • 在依赖 Spring 的项目中,开发者可以有选择地引入某几个模块,而不会因为需要一个小的功能,就被强迫引入整个 Spring 框架;
  • 4、再封装、再抽象
    • Spring 对市面上主流的中间件、系统的访问类库,做了进一步的封装和抽象,提供了更高层次、更统一的访问接口;
    • 比如,Spring 提供了 spring-data-redis 模块,对 Redis Java 开发类库做了进一步的封装,Spring Cache,它定义了统一、抽象的 Cache 访问接口,这些接口不依赖具体的 Cache 实现(Redis、Guava Cache、Caffeine 等)
      • 可以参考这两篇文章:SpringBoot第41讲:SpringBoot集成Redis - 基于RedisTemplate+Jedis的数据操作
      • SpringBoot第40讲:SpringBoot整合Caffeine cache(最优秀的本地缓存)
    • 在这里插入图片描述

2、剖析Spring框架中用来支持扩展的两种设计模式

常用来实现扩展特性的设计模式有:观察者模式、模板模式、职责链模式、策略模式等。我们剖析 Spring 框架为了支持可扩展特性用的 两种设计模式:观察者模式和模板模式

2.1、观察者模式在 Spring 中的应用

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

观察者模式可以参考这篇文章:JAVA设计模式第四讲:行为型设计模式

  • 第10.1节

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

事件角色

  • ApplicationEvent (org.springframework.context包下)充当事件的角色,这是一个抽象类,它继承了java.util.EventObject并实现了 java.io.Serializable接口。

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

  • ContextStartedEvent:ApplicationContext 启动后触发的事件;
  • ContextStoppedEvent:ApplicationContext 停止后触发的事件;
  • ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
  • ContextClosedEvent:ApplicationContext 关闭后触发的事件。

继承类图如下:

  • 在这里插入图片描述

事件监听者角色
ApplicationListener 充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEven()方法来处理ApplicationEvent。ApplicationListener接口类源码如下,可以看出接口定义,接口中的事件只要实现了 ApplicationEvent就可以了。所以,在 Spring中我们只要实现 ApplicationListener 接口的 onApplicationEvent() 方法即可完成监听事件

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

ApplicationEvent 和 ApplicationListener 的代码实现都非常简单,内部并不包含太多属性和方法,它们最大的作用是做类型标识之用

public abstract class ApplicationEvent extends EventObject {/** use serialVersionUID from Spring 1.2 for interoperability */private static final long serialVersionUID = 7099057708183571937L;/** System time when the event happened */private final long timestamp;public ApplicationEvent(Object source) {super(source);this.timestamp = System.currentTimeMillis();}public final long getTimestamp() {return this.timestamp;}
}public class EventObject implements java.io.Serializable {protected transient Object source;public EventObject(Object source) {if (source == null)throw new IllegalArgumentException("null source");this.source = source;}public Object getSource() {return source;}public String toString() {return getClass().getName() + "[source=" + source + "]";}
}

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

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

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

Spring 的事件流程总结

  • 定义一个事件: 实现一个继承自 ApplicationEvent,并且写相应的构造函数;
  • 定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方法;
  • 使用事件发布者发布消息: 可以通过 ApplicationEventPublisher 的 publishEvent()方法发布消息。
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
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;}
}// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{//使用onApplicationEvent接收消息@Overridepublic void onApplicationEvent(DemoEvent event) {String msg = event.getMessage();System.out.println("接收到的信息是:"+msg);}
}// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {@AutowiredApplicationContext applicationContext;public void publish(String message){//发布事件applicationContext.publishEvent(new DemoEvent(this, message));}
}

当调用 DemoPublisher 的 publish() 方法的时候,比如 demoPublisher.publish(“你好”) ,控制台就会打印出:接收到的信息是:你好。

其中,ApplicationContext 可以类比 Google EventBus 框架中的“事件总线”

  • ApplicationContext 这个类并不只是为观察者模式服务的。它底层依赖 BeanFactory(IOC 的主要实现类),提供应用启动、运行时的上下文信息,是访问这些信息的最顶层接口

ApplicationContext 只是一个接口,具体的代码实现包含在它的实现类AbstractApplicationContext 中。跟观察者模式相关的代码,如下所示。只需要关注它是如何发送事件和注册监听者就好

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {/** Statically specified listeners */private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();public Collection<ApplicationListener<?>> getApplicationListeners() {return this.applicationListeners;}@Overridepublic void publishEvent(ApplicationEvent event) {publishEvent(event, null);}		@Overridepublic void publishEvent(Object event) {publishEvent(event, null);}protected void publishEvent(Object event, @Nullable ResolvableType eventType) {	// Decorate event as an ApplicationEvent if necessaryApplicationEvent applicationEvent;if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;}	else {applicationEvent = new PayloadApplicationEvent<>(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();}}// Multicast right now if possible - or lazily once the multicaster is initializedif (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);} else {getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);} else {this.parent.publishEvent(event);}}}@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.addApplicationListener(listener);}else {this.applicationListeners.add(listener);}}protected void registerListeners() {// Register statically specified listeners first.for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// Do not initialize FactoryBeans here: We need to leave all regular beans// uninitialized to let post-processors apply to them!String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}// Publish early application events now that we finally have a multicaster...Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;this.earlyApplicationEvents = null;if (earlyEventsToProcess != null) {for (ApplicationEvent earlyEvent : earlyEventsToProcess) {getApplicationEventMulticaster().multicastEvent(earlyEvent);}}}
}

从代码中可以得出,真正的消息发送,实际上是通过 ApplicationEventMulticaster 这个类来完成的,它通过线程池,支持异步非阻塞、同步阻塞这两种类型的观察者模式

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}
}

结论:这也体现了 Spring 框架的扩展性,也就是在不需要修改任何代码的情况下,扩展新的事件和监听。

Action1:Google Guava 的 EventBus 实现中,被观察者发送消息到事件总线,事件总线根据消息的类型,将消息发送给可匹配的观察者。那在 Spring 提供的观察者模式的实现中,是否也支持按照消息类型匹配观察者呢?

支持按照消息类型匹配观察者,最终调用 SimpleApplicationEventMulticaster 类的multicastEvent方法通过反射匹配类型,根据配置采用异步还是同步的监听方式。

2.2、模板模式在 Spring 中的应用

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

模版模式可以参考这篇文章:JAVA设计模式第四讲:行为型设计模式

  • 第10.1节

与之相关的面试题:请你说下 Spring Bean 的创建过程包含哪些主要的步骤?

Spring Bean 的创建过程,可以大致分为两大步:对象的创建和对象的初始化

  • 其中就涉及模板模式。它也体现了 Spring 的扩展性。利用模板模式,Spring 能让用户定制 Bean 的创建过程

对象的初始化有两种实现方式。一种是在类中自定义一个初始化函数,并且通过配置文件,显式地告知 Spring,哪个函数是初始化函数。如下所示,在配置文件中,我们通过 init-method 属性来指定初始化函数。

public class DemoClass {//...public void initDemo() {//...初始化..}
}
// 配置:需要通过init-method显式地指定初始化方法
<bean id="demoBean" class="com.xzg.cd.DemoClass" init-method="initDemo"></bean>

缺点:这种初始化函数并不固定,由用户随意定义

解决方案:Spring 提供了另外一个定义初始化函数的方法,那就是让类实现Initializingbean 接口。这个接口包含一个固定的初始化函数定义(afterPropertiesSet() 函数)。Spring 在初始化 Bean 的时候,可以直接通过 bean.afterPropertiesSet() 的方式,调用 Bean 对象上的这个函数,而不需要使用反射来调用了。

代码示例如下:

public class DemoClass implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {//...初始化...}
}
// 配置:不需要显式地指定初始化方法
<bean id="demoBean" class="com.xzg.cd.DemoClass"></bean>

这种方法的缺点:业务代码(DemoClass)跟框架代码(InitializingBean)耦合在了一起,替换框架的成本就变高了

Bean 的销毁过程,在 Java 中,对象的回收是通过 JVM 来自动完成的。但是,我们可以在将 Bean 正式交给 JVM 垃圾回收前,执行一些销毁操作。

两种实现方式

  • 1、通过配置 destroy-method 指定类中的销毁函数
  • 2、实现 DisposableBean 接口

Spring Bean 的整个生命周期如下图所示,它将要执行的函数封装成对象,传递给模板(BeanFactory)来执行

  • 对象的初始化又可以分解为 3 个小的步骤:初始化前置操作、初始化、初始化后置操作
    • 定义在接口 BeanPostProcessor 中
      在这里插入图片描述

Spring 中的 ApplicationContext 会自动检测在配置文件中实现了 BeanPostProcessor 接口的所有 Bean,并把它们注册到 BeanPostProcessor 处理器列表中。在 Spring 容器创建 Bean 的过程中,Spring 会逐一去调用这些处理器。

public interface BeanPostProcessor {Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;
}

Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。

Action:模板方法的样例代码如下所示

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 {@Overridepublic void PrimitiveOperation2() {//当前类实现}@Overridepublic void PrimitiveOperation3() {//当前类实现}
}

Action1:请你说下 Spring Bean 的创建过程包含哪些主要的步骤?

3、总结Spring框架用到的11种设计模式

3.1、适配器模式在 Spring 中的应用

在 Spring MVC 中,定义一个 Controller 最常用的方式是,通过 @Controller 注解来标记某个类是 Controller 类,通过@RequesMapping 注解来标记函数对应的 URL。

定义一个 Controller的三种方法

  • 方法一:通过 @Controller、@RequestMapping 来定义
@Controller
public class DemoController {@RequestMapping("/employname")public ModelAndView getEmployeeName() {ModelAndView model = new ModelAndView("Greeting");model.addObject("message", "Dinesh");return model;}
}
  • 方法二:实现Controller接口 + xml配置文件:配置 DemoController 与URL的对应关系
public class DemoController implements Controller {@Overridepublic ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) {ModelAndView model = new ModelAndView("Greeting");model.addObject("message", "Dinesh Madhwal");return model;}
}
  • 方法三:实现Servlet接口 + xml配置文件:配置 DemoController 类与URL的对应关系
public class DemoServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws  Exception {this.doPost(req, resp);} @Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Exception {resp.getWriter().write("Hello World.");}
}

在应用启动的时候,Spring 容器会加载这些 Controller 类,并且解析出 URL 对应的处理函数,封装成 Handler 对象,存储到 HandlerMapping 对象中。当有请求到来的候,DispatcherServlet 从 HanderMapping 中,查找请求 URL 对应的 Handler,然后调用执行 Handler 对应的函数代码,最后将执行结果返回给客户端。

3.2、策略模式在Spring 中的应用

Todo

3.3、组合模式在 Spring 中的应用

Todo

3.4、装饰器模式在 Spring 中的应用

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。

在这里插入图片描述

装饰者模式示意图

Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式。Spring 中用到的包装器模式在类名上含有 Wrapper或者 Decorator。这些类基本上都是动态地给一个对象添加一些额外的职责。

3.5、工厂模式在 Spring 中的应用

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

两者对比:

  • BeanFactory:延迟注入(使用到某个 bean 的时候才会注入),相比于ApplicationContext 来说会占用更少的内存,程序启动速度更快。
  • ApplicationContext:容器启动的时候,不管你用没用到,一次性创建所有 bean。BeanFactory 仅提供了最基本的依赖注入支持, ApplicationContext 扩展了 BeanFactory,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext 会更多。

ApplicationContext 的三个实现类:

  • ClassPathXmlApplication:把上下文文件当成类路径资源。
  • FileSystemXmlApplication:从文件系统中的 XML 文件载入上下文定义信息。
  • XmlWebApplicationContext:从Web系统中的XML文件载入上下文定义信息。
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();}
}

3.6、代理模式在 Spring 中的应用

  • Spring中两种代理方式,
    1、若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.proxy类代理,
    2、若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。

  • 代理模式经典应用是 AOP

3.7、单例模式在 Spring 中的应用

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

使用单例模式的好处:

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。

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

  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
  • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

Spring 实现单例的方式:

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

Spring 通过 ConcurrentHashMap 实现单例注册表的特殊方式实现单例模式。Spring 实现单例的核心代码如下

// 通过 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));}}
}

3.8、Spring MVC中的适配器模式

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

为什么要在 Spring MVC 中使用适配器模式?

  • Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){  ((MultiActionController)mappedHandler.getHandler()).xxx  
}else if(mappedHandler.getHandler() instanceof XXX){  ...  
}else if(...){  ...  
}  

假如我们再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭

3.9、职责链模式在 Spring 中的应用

  • 拦截器

  • 职责链模式在 Spring 中的应用是拦截器(Interceptor)

4、总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
  • 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
  • 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
  • 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/74459.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

渗透测试漏洞原理之---【业务安全】

文章目录 1、业务安全概述1.1业务安全现状1.1.1、业务逻辑漏洞1.1.2、黑客攻击目标 2、业务安全测试2.1、业务安全测试流程2.1.1、测试准备2.1.2、业务调研2.1.3、业务建模2.1.4、业务流程梳理2.1.5、业务风险点识别2.1.6 开展测试2.1.7 撰写报告 3、业务安全经典场景3.1、业务…

Excel VSTO开发7 -可视化界面开发

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 7 可视化界面开发 前面的代码都是基于插件启动或者退出时&#xff0c;以及Excel Application的相关事件&#xff0c;在用户实际操作…

亚马逊云科技人工智能内容审核服务:大大降低生成不安全内容的风险

生成式人工智能技术发展日新月异&#xff0c;现在已经能够根据文本输入生成文本和图像。Stable Diffusion是一种文本转图像模型&#xff0c;可以创建栩栩如生的图像应用。通过Amazon SageMaker JumpStart&#xff0c;使用Stable Diffusion模型轻松地从文本生成图像。 尽管生成式…

并发-Executor框架笔记

Executor框架 jdk5开始&#xff0c;把工作单元与执行机制分离开来&#xff0c;工作单元包括Runable和Callable&#xff0c;执行机制由Executor框架来提供。 Executor框架简介 Executor框架的两级调度模型 Java线程被一对一映射为本地操作系统线程 java线程启动会创建一个本…

Unity3D URP 仿蜘蛛侠风格化BloomAO

Unity3D URP 仿蜘蛛侠风格化Bloom&AO BloomBloom效果流程&#xff1a;制作控制面板VolumeComponent.CSCustom Renderer FeatherCustom Renderer PassBloom ShaderComposite Shader 完善Custom Feather风格化AO 总结 本篇文章介绍在URP中如何进行风格化后处理&#xff0c;使…

高阶数据结构-----三种平衡树的实现以及原理(未完成)

TreeMap和TreeSet的底层实现原理就是红黑树 一)AVL树: 1)必须是一棵搜索树:前提是二叉树&#xff0c;任取一个节点&#xff0c;它的左孩子的Key小于父亲节点的Key小于右孩子节点的Key&#xff0c;中序遍历是有序的&#xff0c;按照Key的大小进行排列&#xff0c;高度平衡的二叉…

2024字节跳动校招面试真题汇总及其解答(一)

1. 【算法题】重排链表 给定一个单链表 L 的头节点 head ,单链表 L 表示为: L0 → L1 → … → Ln - 1 → Ln请将其重新排列后变为: L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → … 不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 示例 1: 输入:hea…

SSH是如何配置的

目录 什么是SSH SSH可以做什么其他用途&#xff1f; ssh有几种连接方法吗 我应该用哪种方法连接SSH1或SSH2&#xff1f; 每天都在用SSH你知道SSH的原理吗 开启ssh后telnet会关闭吗 SSH的优缺点 SSH和Telnet之间优缺点的对比 SSH的配置实验 ensp Cisco H3C 1、什么是…

QT—基于http协议的网络文件下载

1.常用到的类 QNetworkAccessManager类用于协调网络操作&#xff0c;负责发送网络请求&#xff0c;创建网络响应 QNetworkReply类表示网络请求的响应。在QNetworkAccessManager发送一个网络请求后创建一个网络响应。它提供了以下信号&#xff1a; finished()&#xff1a;完成…

【八大经典排序算法】:直接插入排序、希尔排序实现 ---> 性能大比拼!!!

【八大经典排序算法】&#xff1a;直接插入排序、希尔排序实现 ---> 性能大比拼&#xff01;&#xff01;&#xff01; 一、 直接插入排序1.1 插入排序原理1.2 代码实现1.3 直接插入排序特点总结 二、希尔排序 ( 缩小增量排序 )2.1 希尔排序原理2.2 代码实现2.3 希尔排序特点…

数据可视化、BI和数字孪生软件:用途和特点对比

在现代企业和科技领域&#xff0c;数据起着至关重要的作用。为了更好地管理和理解数据&#xff0c;不同类型的软件工具应运而生&#xff0c;其中包括数据可视化软件、BI&#xff08;Business Intelligence&#xff09;软件和数字孪生软件。虽然它们都涉及数据&#xff0c;但在功…

文献阅读:Chain-of-Thought Prompting Elicits Reasoning in Large Language Models

文献阅读&#xff1a;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 1. 文章简介2. 具体方法3. 实验结果 1. 数学推理 1. 实验设计2. 实验结果3. 消解实验4. 鲁棒性考察 2. 常识推理 1. 实验设计2. 实验结果 3. 符号推理 1. 实验设计2. 实验结果 4.…

华为数通方向HCIP-DataCom H12-821题库(单选题:241-260)

第241题 ​​LS Request​​报文不包括以下哪一字段? A、通告路由器(Advertising Router) B、链路状态 ID (Link Srate ID) C、数据库描述序列号(Database Dascription Sequence lumber) D、链路状态类型 Link state type) 答案:C 解析: LS Request 报文中包括以下字段…

协议定制 + Json序列化反序列化

文章目录 协议定制 Json序列化反序列化1. 再谈 "协议"1.1 结构化数据1.2 序列化和反序列化 2. 网络版计算器2.1 服务端2.2 协议定制(1) 网络发送和读取的正确理解(2) 协议定制的问题 2.3 客户端2.4 代码 3. Json实现序列化反序列化3.1 简单介绍3.2 使用 协议定制 J…

【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

python调用GPT实现:智能用例生成工具

工具作用&#xff1a; 根据输入的功能点&#xff0c;生成通用测试点 实现步骤 工具实现主要分2个步骤&#xff1a; 1.https请求调用Gpt,将返回响应结果保存为.md文件 2.用python实现 将 .md文件转换成.xmind文件 3.写个简单的前端页面&#xff0c;调用上述步骤接口 详细代…

操作系统强化认识之Shell编程学习与总结

目录 1.Shell的概述 2.Shell脚本入门 3.变量 3.1.系统预定义变量 3.2.自定义变量 3.3.特殊变量 4.运算符 5.条件判断 6.流程控制 6.1.if判断 6.2.case语句 6.3.for循环 6.4.while循环 7.read读取控制台输入 8.函数 8.1.系统函数 8.2.自定义函数 9.正则表示式入…

【C++入门到精通】C++入门 ——搜索二叉树(二叉树进阶)

阅读导航 前言一、搜索二叉树简介1. 概念2. 基本操作⭕搜索操作&#x1f36a;搜索操作基本代码&#xff08;非递归&#xff09; ⭕插入操作&#x1f36a;插入操作基本代码&#xff08;非递归&#xff09; ⭕删除操作&#x1f36a;删除操作基本代码&#xff08;非递归&#xff0…

MySQL无法查看系统默认字符集以及校验规则

show variables like character_set_database; show variables like collation_database;这个错误信息表示MySQL在尝试访问performance_schema.session_variables表时&#xff0c;发现该表不存在。这个问题可能是由于MySQL的版本升级导致的。解决这个问题的一种方法是运行mysql…

论文浅尝 | 训练语言模型遵循人类反馈的指令

笔记整理&#xff1a;吴亦珂&#xff0c;东南大学硕士&#xff0c;研究方向为大语言模型、知识图谱 链接&#xff1a;https://arxiv.org/abs/2203.02155 1. 动机 大型语言模型&#xff08;large language model, LLM&#xff09;可以根据提示完成各种自然语言处理任务。然而&am…