JDK中使用了哪些设计模式?Spring中使用了哪些设计模式?这两个问题在面试中比较常见。在网上搜了一下,关于Spring中设计模式的解释都差不多,而且大部分都是老的。所以,我自己花了几天时间总结了一下。由于个人能力有限,文章中的错误之处还请大家指出。另外,文章篇幅有限。我只是简单地解释了设计模式和一些源代码。本文的主要目的是回顾Spring中常见的设计模式。设计模式代表了面向对象软件开发中最好的计算机编程实践。Spring框架中广泛使用了不同类型的设计模式。我们来看看都有哪些设计模式?控制反转(IoC)与依赖注入(DI)IoC(InversionofControl,控制反转)是一个非常非常重要的概念,它不是一种技术,而是一种解耦的设计思想。它的主要目的是通过一个“第三方”(Spring中的IOC容器)实现具有依赖关系的对象之间的解耦(IOC易于管理对象,你随便用),从而降低代码之间的耦合。IoC是一种原则,而不是一种模式,以下模式(但不限于)实现了IoC原则。ioc-patternsSpringIOC容器就像一个工厂。当我们需要创建对象时,只需要配置配置文件/注解即可,无需考虑对象是如何创建的。IOC容器负责创建对象、将它们连接在一起、配置这些对象,并处理这些对象从创建到完全销毁的整个生命周期。在实际项目中,如果一个Service类有成百上千个类作为底层,我们就需要实例化Service。你可能每次都要弄清楚Service所有底层类的构造函数,这可能会让人抓狂。如果使用IOC,只需要配置好,然后在需要的地方引用即可,大大增加了项目的可维护性,降低了开发难度。关于SpringIOC的理解,推荐看一下知乎的这个回答:https://www.zhihu.com/question/23277575/answer/169698662,很好。如何理解控制反转?例如:“对象a依赖于对象b,当对象a需要使用对象b时,必须自己创建。但是当系统引入IOC容器时,对象a和对象b失去了之前的直接联系。此时这时,当对象a需要使用对象b时,我们可以指定IOC容器创建一个对象b,并将其注入到对象a中”。对象a获取依赖对象b的过程由主动行为变为被动行为,控制权被逆转,这就是控制反转名称的由来。DI(DependencyInject,依赖注入)是一种实现控制反转的设计模式。依赖注入是将实例变量传递给一个对象。工厂设计模式Spring使用工厂模式通过BeanFactory或ApplicationContext创建bean对象。两者比较:BeanFactory:延迟注入(只有在使用bean时才会注入),比BeanFactory占用内存少,启动程序更快。ApplicationContext:当容器启动时,不管你用不用,一次性创建所有的bean。BeanFactory只提供了最基本的依赖注入支持,ApplicationContext扩展了BeanFactory。除了BeanFactory的功能之外,还有额外的功能,所以开发者一般使用ApplicationContext较多。ApplicationContext的三个实现类:ClassPathXmlApplication:将上下文文件视为类路径资源。FileSystemXmlApplication:从文件系统中的XML文件加载上下文定义信息。XmlWebApplicationContext:从Web系统中的XML文件加载上下文定义信息。示例:importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.FileSystemXmlApplicationContext;publicclassApp{publicstaticvoidmain(String[]args){ApplicationContextcontext=newFileSystemXmlApplicationContext("C:/work/IOCContainers/springframework.applicationcontext/src/mainresources/bean-factory-config.xml");HelloApplicationContextobj=(HelloApplicationContext)context.getBean("helloApplicationContext");obj.getMsg();}}在我们的系统中,有些对象我们只需要一个,例如:线程池、缓存、对话框、注册表、日志记录对象、充当打印机、显卡等设备驱动程序的对象。实际上,这一类对象只能有一个实例。如果创建多个实例,可能会导致一些问题,例如:程序行为异常、资源使用过多或结果不一致。使用单例模式的好处:对于经常使用的对象,可以省去创建对象所花费的时间,对于那些重量级的对象来说,这是非常可观的系统开销;由于新操作的减少,系统Memory的使用频率也降低了,从而减轻了GC压力,缩短了GC停顿时间。Spring中bean的默认作用域是单例(singlecase)。Spring中的bean除了单例作用域外,还有以下作用域:prototype:为每个请求创建一个新的bean实例。request:每个HTTP请求都会生成一个新的bean,只在当前HTTP请求内有效。session:每个HTTP请求都会生成一个新的bean,只在当前HTTPsession内有效。global-session:globalsession作用域只在基于portlet的web应用中有意义,在Spring5中没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型JavaWeb插件程序。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与servlet不同的是,每个portlet都有不同的sessionSpring实现单例的方式:xml:注解:@Scope(value="singleton")Spring以一种特殊的方式实现单例模式,通过ConcurrentHashMap实现单例注册。Spring实现单例的核心代码如下://通过ConcurrentHashMap实现单例注册(线程安全)privatefinalMapsingletonObjects=newConcurrentHashMap(64);publicObjectgetSingleton(StringbeanName,ObjectFactory>singletonFactory){Assert.notNull(beanName,"'beanName'mustnotbenull");synchronized(this.singletonObjects){//检查缓存中是否有实例ObjectsingletonObject=this.singletonObjects.get(beanName);if(singletonObject==null){//...省略很多代码try{singletonObject=singletonFactory.getObject();}//...省略很多代码//如果实例对象不存在,我们将其注册到单例注册表中.addSingleton(beanName,singletonObject);}return(singletonObject!=NULL_OBJECT?singletonObject:null);}}//添加对象到单例注册表protectedvoidaddSingleton(StringbeanName,ObjectsingletonObject){synchronized(this.singletonObjects){this.singletonObjects.put(beanName,(singletonObject!=null?singletonObject:NULL_OBJECT));}}}代理设计模式在AOP中的应用模块通常调用的逻辑或职责(如事务处理、日志管理、权限控制等)是封装,便于减少系统中代码的重复,降低模块之间的耦合度,方便以后的扩展性和可维护性。SpringAOP基于动态代理。如果被代理的对象实现了接口,SpringAOP会使用JDKProxy创建代理对象。对于没有实现接口的对象,不能使用JDKProxy进行代理。这时候SpringAOP就会使用Cglib。这时SpringAOP会使用Cglib生成代理对象的子类作为代理,如下图所示:SpringAOPProcess当然也可以使用AspectJ。SpringAOP集成了AspectJ。AspectJ应该被认为是Java生态系统中最完整的AOP框架。使用AOP后,我们可以将一些常用的功能抽象出来,直接在需要的地方使用,大大简化了代码量。在我们需要增加新功能的时候也很方便,也提高了系统的扩展性。AOP用于日志功能、事务管理等场景。SpringAOP和AspectJAOP有什么区别?SpringAOP是运行时增强,而AspectJ是编译时增强。SpringAOP基于代理(Proxying),而AspectJ基于字节码操作(BytecodeManipulation)。SpringAOP集成了AspectJ,AspectJ应该算是Java生态中最完善的AOP框架了。AspectJ比SpringAOP更强大,但SpringAOP相对简单。如果我们的方面较少,则两者之间的性能差异不大。但是当切面太多的时候,最好选择AspectJ,比SpringAOP快很多。模板方法模板方法模式是一种行为设计模式,它定义了操作中算法的骨架,将一些步骤推迟到子类。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的一些具体步骤的实现。模板方法UML图publicabstractclassTemplate{//这是我们的模板方法publicfinalvoidTemplateMethod(){PrimitiveOperation1();PrimitiveOperation2();PrimitiveOperation3();}protectedvoidPrimitiveOperation1(){//当前类实现}//子类实现的方法protectedabstractvoidPrimitiveOperation2();protectedabstractvoidPrimitiveOperation3();}publicclassTemplateImplextendsTemplate{@OverridepublicvoidPrimitiveOperation2(){//当前类实现}@OverridepublicvoidPrimitiveOperation3(){//当前类实现}}在Spring中,jdbcTemplate、hibernateTemplate等以Template结尾的类用于数据库操作,它们使用模板模式。一般情况下,我们使用继承来实现模板方式,但是Spring并没有使用这种方式,而是使用Callback方式配合模板方法方式,既达到了代码复用的效果,又增加了灵活性。观察者模式观察者模式是一种对象行为模式。表示对象之间存在依赖关系。当一个对象发生变化时,该对象所依赖的对象也会做出响应。Spring事件驱动模型是观察者模式的经典应用。Spring的事件驱动模型非常好用,可以在很多场景下解耦我们的代码。例如,我们每添加一个产品,就需要重新更新产品索引。这时候我们就可以使用观察者模式来解决这个问题。Spring事件驱动模型中的三个角色事件角色ApplicationEvent(在org.springframework.context包下)充当事件的角色。这是一个抽象类,继承了java.util.EventObject,实现了java.io.Serializable接口。Spring默认存在以下事件,都是ApplicationContextEvent的实现(继承自ApplicationContextEvent):ContextStartedEvent:ApplicationContext启动后触发的事件;ContextStoppedEvent:ApplicationContext停止后触发的事件;ContextRefreshedEvent:ApplicationContext初始化或刷新完成后触发的事件;ContextClosedEvent:ApplicationContext关闭后触发的事件。ApplicationEvent-Subclass事件监听器角色ApplicationListener充当事件监听器角色,它是一个接口,它只定义了一个onApplicationEvent()方法来处理ApplicationEvent。ApplicationListener接口类的源码如下。从接口定义可以看出,接口中的事件只需要实现ApplicationEvent即可。所以在Spring中我们只需要实现ApplicationListener接口实现onApplicationEvent()方法即可完成事件监听包org.springframework.context;importjava.util.EventListener;@FunctionalInterfacepublicinterfaceApplicationListenerextendsEventListener{voidonApplicationEvent(Evar1);}事件发布者角色ApplicationEvent作为事件的发布者,也是一个接口。@FunctionalInterfacepublicinterfaceApplicationEventPublisher{defaultvoidpublishEvent(ApplicationEventevent){this.publishEvent((Object)event);}voidpublishEvent(Objectvar1);}ApplicationEventPublisherApplicationEventPublisher接口的方法publishEvent()在AbstractApplicationContext类中实现。阅读这个方法的实现,你会发现上面的事件实际上是通过ApplicationEventMulticaster广播的。具体内容太多,这里就不分析了,以后可能会单独写一篇文章来提。Spring的事件流程总结定义一个事件:实现对ApplicationEvent的继承,并编写相应的构造函数;定义一个事件监听器:实现ApplicationListener接口,重写onApplicationEvent()方法;使用事件发布者发布消息:通过ApplicationEventPublisher的publishEvent()方法发布消息。Example://定义一个事件,继承自ApplicationEvent并编写对应的构造函数}//定义一个事件监听器,实现ApplicationListener接口,重写onApplicationEvent()方法;@ComponentpublicclassDemoListenerimplementsApplicationListener{//使用onApplicationEvent接收消息@OverridepublicvoidonApplicationEvent(DemoEventevent){Stringmsg=event.getMessage();System.out.println("接收到的信息是:"+msg);}}//发布事件,可以通过ApplicationEventPublisher的publishEvent()方法发布消息。@ComponentpublicclassDemoPublisher{@AutowiredApplicationContextapplicationContext;publicvoidpublish(Stringmessage){//publisheventapplicationContext.publishEvent(newDemoEvent(this,message));}}当调用DemoPublisher的publish()方法时,比如demoPublisher.publish("Hello"),控制台会打印出来:收到的信息是:你好。适配器模式(AdapterPattern)将一个接口转换成客户想要的另一个接口。适配器模式使那些具有不兼容接口的类能够协同工作。它的别名是Wrapper。SpringAOP中的适配器模式我们知道SpringAOP的实现是基于代理模式的,但是SpringAOP的增强或者说建议(Advice)使用的是适配器模式,相关的接口就是AdvisorAdapter。常用的Advice类型有:BeforeAdvice(目标方法调用前,pre-advice),AfterAdvice(目标方法调用后,post-notification),AfterReturningAdvice(目标方法执行后,return前)等在。每一种Advice(通知)都有对应的拦截器:MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor。Spring预定义的通知必须通过相应的适配器适配MethodInterceptor接口(方法拦截器)类型的对象(例如:MethodBeforeAdviceInterceptor负责适配MethodBeforeAdvice)。springMVC中的Adapter模式在SpringMVC中,DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler。解析到对应的Handler(也就是我们平时所说的Controller控制器)后,会交由HandlerAdapter适配器进行处理。HandlerAdapter是预期的接口,具体的适配器实现类用于适配目标类,Controller是需要适配的类。为什么在SpringMVC中使用适配器模式?SpringMVC中有多种类型的Controller,不同类型的Controller处理请求的方式不同。如果不使用adapter方式,DispatcherServlet直接获取对应类型的Controller,需要自己判断,像下面的代码:).xxx}elseif(mappedHandler.getHandler()instanceofXXX){...}elseif(...){...}如果再添加一个Controller类型,需要在上面的代码中再添加一行判断语句。这种形式使得程序难以维护,违背了设计模式的开闭原则——对扩展开放,对修改关闭。装饰者模式装饰者模式可以动态地为对象添加一些额外的属性或行为。装饰器模式比使用继承更灵活。简单的说,当我们需要修改原来的功能,但是又不想直接修改原来的代码时,我们设计一个Decorator来覆盖原来的代码。其实在JDK中有很多地方使用了装饰器模式,比如InputStream家族。InputStream类下还有FileInputStream(读取文件)、BufferedInputStream(增加缓存,大大提高读取文件速度)等子类,无需修改InputStream。代码案例扩展了它的功能。Decorator模式示意图在Spring中配置DataSource时,DataSource可能是不同的数据库和数据源。是否可以在不修改原有类代码的情况下,根据客户的需求动态切换不同的数据源?这时候就要用到装饰器模式了(具体原理我自己也没搞懂)。Spring中使用的包装器模式在类名上包含Wrapper或Decorator。这些类基本上动态地向对象添加一些额外的职责。总结一下Spring框架中使用了哪些设计模式:工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。代理设计模式:SpringAOP功能的实现。单例设计模式:Spring中的Bean默认都是单例的。模板方法模式:在Spring中,jdbcTemplate、hibernateTemplate等以Template结尾的类进行数据库操作都是使用模板模式。Wrapper设计模式:我们的项目需要连接多个数据库,不同的客户每次访问都会根据自己的需要访问不同的数据库。这种模式可以让我们根据客户的需求动态切换不同的数据源。观察者模式:Spring事件驱动模型是观察者模式的经典应用。适配器模式:SpringAOP的增强或通知(Advice)使用适配器模式,springMVC也使用适配器模式适配Controller。...