吴雨桐
(四川大学锦城学院 计算机与软件学院,四川 成都 611731)
设计模式是一套可以被反复使用、程序员熟知的、经过分类编目的代码设计经验的一种总结。使用设计模式主要是为了可重用代码、让代码更容易被他人理解、保证代码的可靠性。毫无疑问,设计模式无论是于人于己还是对于程序来讲都是多赢的,设计模式使代码编制真正工程化,就像软件工程的基石,和大厦的一块块砖石一样。设计模式分为创建模式、结构模式和行为模式三大类。其中创建模式一般对于对象的实例创建比较有用。结构模式通过处理对象和类之间的组合来作用于企业级开发应用的设计结构,以达到降低项目开发的复杂度,提高其系统的可复用性和性能。行为模式是作用域一组对象之间的交互作用。
Spring 是编程领域的一个轻量级开源框架,被广大java 工程师所熟知。该框架在 2002 年最早提出并随后创建,是为了解决企业级编程开发的复杂性,实现快捷开发的应用型框架 。Spring 是一个开源容器框架,集成各类型的工具,通过核心的Bean Factory 实现了底层的类的实例化和生命周期的管理。在整个框架中,各类型的功能被抽象成一个个的 Bean,这样就可以实现各种功能的管理,包括动态加载和切面编程。当然在这种开源框架中,也会用到万能的设计模式。在Spring 中大概使用到了九种设计模式。这九种分别是简单工厂(非二十三种设计模式中的一种)、工厂方法模式、单例模式、适配器模式、装饰器模式、代理模式、观察者模式、策略模式以及模块方法模式。
简单工厂模式又名静态工厂方法,简单工厂模式简单来说就是由一个工厂类来接收参数并动态的决定创建哪一个产品类。
在Spring 中,Bean Factory 就是简单工厂模式的应用。它作为IOC 最基本的容器,负责生产和管理Bean,并且它还为其他具体的IOC 容器提供了基本规范。如果说它是Spring 里面最低层的接口,提供了最简单的容器功能,那么Application Context 就是更高级的容器,它继承Bean Factory 接口,还增加了更多新功能,如国际化,可访问资源以及消息发送、响应机制等。
第一步是创建一个Student 实体类。第二步是在配置文件中传入参数,通过id 属性指定参数,通过class 指定具体的类,通过scope 属性来定义是否为单例。第三步是测试,创建一个main 函数,通过new Class Path Xml Application Context 来指定xml 文件,用get Bean 方法来传入参数,从而获取到xml 中配置的类。
在使用简单工厂的时候通常不用创建简单工厂类的实例,因此可以把简单工厂类实现成一个工具类,直接使用静态方法结果。也就是说简单工厂的方法通常都是静态的,所以也被称为静态工厂。如果放置客户端随意创造简单工厂实例,也可以把简单工厂的构造方法私有化。想要理解Spring 里面的简单工厂模式其实很简单,想想我们在学习java 设计模式的时候,做简单工厂实例的流程,其必不可缺的流程就是工厂类,没有工厂你就什么也拿不到。在Spring 中,Bean Factory 就是这么一个工厂,如果没有这个工厂,就好比我们需要工厂里生产的商品,但是拿到手还得我们自己动手,不仅麻烦还不利于日后的维护。
这种模式涉及到的是一个单一的类。该类只负责创建自己的对象,同时确保只创建单个对象。在上述简单工厂模式实例中其实已经设计到了单例模式的运用。
在Spring 中,依赖注入默认就是为单例,依赖注入是发生在Abstract Bean Factory 的get Bean 方法中,其中的do Get Bean方法调用get Singleton 进行Bean 的创建。单例模式的定义为:保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。Spring 对单例的实现就是完成了后面半句话,即提供了一个全局的访问点Bean Factory。而且在Spring 中实现单例模式只需要设置一个属性或者一个注解。
Xml 配置:在上面简单工厂模式中有提到xml 文件配置,在配置中有这么一个属性scope,将代码中的Prototype 改成Singleton 即可。
注解:@Scope
在我们的项目中,有一些对象确实只需要创建一个就够了,比如线程池、缓存、日志。创建的太多反而会成为负担,所以在Spring 中默认的就是单例。一般我们都说,最成功的单例并不是双重检验锁,而是枚举,枚举本身就是一种单例,并且无法做到反射攻击,再一个最优雅的无非就是Spring 本身实现的单例:在Spring 中,在一些注解作用下的类默认都是单例的,目前通过我对Spring 的了解,我认为最优注解为@Component,主要使用场景有:数据库的配置、Redis 的配置、权限的配置、Filter 的过滤、拦截器、swagger 及自定义的时间转换器、类型转换器、对接第三方硬件时,调用硬件的dll、so文件等。我个人比较常遇到的就是数据库配置以及拦截器的配置。当然在Spring 中也不是所有注解在默认情况下都是单例的,因为@Component+@Bean 并不是单例,在调用过程中可能会创建多个实例,出现错误,所以要避免这种组合使用,另外,在控制器层中的常用注解@Restcontroller 也是多例的。
代理模式是给某一个对象提供一个代理对象,并由代理对象来控制对原对象的引用.代理模式还分静态代理与动态代理。
AOP(面向切面编程)五大通知类型:
图1 五大通知类型
代码为切面类,注解使用@Aspect,将该类定义标识为一个切面供容器来读取,用Logger Factory 来记录日志,@Pointcut注解是植入Advice 的触发条件,它定义的是表达式和方法签名。方法签名一定要是公开以及无返回值类型的,然后根据实际需求选择图3 中的五大通知类型。
结果:以登录操作为例
图2 日志
代理模式可以将那些与业务无关,但又可以将一些业务模块共同调用的责任或逻辑封装起来。为了增加其代码的复用性,降低各个模块间的耦合度,并且有利于项目未来的可拓展性和可维护性。最常见的一些实用场景如下面这些:日志的记录、跟踪、优化与监控和事务的处理、持久化、性能优化、资源池、连接池的管理、系统的权限管理以及缓存、错误处理、调试、加载等。上文便是通过AOP 实现了日志记录。而且在Spring AOP 中实现的是一种动态代理,如果目标方法有接口的时候会自动选用JDK 动态代理目标,相反如果方法没有接口时候则会选择选择 CGLib 动态代理。
适配器模式的目的是将一个类接口转变为客户端目标接口,从而使因不匹配而无法工作的两个类能一起工作,因此又被称为转换器模式等。
前文提到,Spring AOP 是基于代理模式来实现的,在Spring AOP 中通过使用的 Advice(通知)来增强被代理类的功能。与之相关的接口为Advisor Adapter,通知的类型与图1 中相似,分为方法前、方法后等等。每个类型的通知都有对应的拦截器,Spring 需要将每个通知都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换。
Spring 框架的子模块Spring MVC,使用处理器分离模型、控制和视图的开发模式,提高系统服用性、可维护性和灵活性的开发模式,达到不同技术层级间松耦合的效果。其中,Dispatcher Servlet 根据请求信息调用Handler Mapping,解析请求对应的Handler。解析到后,开始由Handler Adapter 适配器处理。它作为期望接口,具体的适配器实现类用于对目标类进行适配,控制器作为需要适配的类。
Spring AOP 在前文代理模式中已经提到,这里就说说Spring MVC。Spring MVC 之所以要采用适配器模式,是因为当在一个项目中,控制器种类繁多,每一个控制器都会有不同的请求来进行处理,如果不使用适配器模式,就需要自行if else语句去判断。如果这样的话,每加一个控制器都需要加一个判断语句,不仅使得系统难以维护,还违反了设计模式中的开闭原则。
设计模式本身是作为一套众人知晓,经过分类编目,且被人们反复使用的代码编写经验的总结。但是,这套总结仅仅存在于代码之上。我们在项目开发,软件开发中遇到的一些问题,仅凭着使用设计模式还是远远不够的,于是Spring 应运而生。它是由于开发的复杂性而创建的。Spring 与现有的技术不同,它更多强调的是面向对象。它的初衷便是为了让软件开发变得更加简单,开发人员从以前的使用类转向更好的使用接口,并且,它将使用接口的复杂度降到了最低,几乎接近零。其实我们作为一个程序员,无论是前端还是后端,设计模式的重要性是其他所有框架所不能比拟的。现在大多数框架中都用到了很多的设计模式。正是因为运用到的这些设计模式,才使得我们在使用框架进行项目开发时能够做到游刃有余。