Spring框架中蕴含的经典设计思想
从 Spring 看框架的作用
解耦业务和非业务开发、让程序员聚焦在业务开发上;隐藏复杂实现细节、降低开发难度、减少代码 bug;实现代码复用、节省开发时间;规范化标准化项目开发、降低学习和维护成本等等。如果要用一句话来总结一下的话,那就是简化开发!
相比单纯的 CRUD 业务代码开发,非业务代码开发要更难一些。所以,将一些非业务的通用代码开发为框架,在项目中复用,除了节省开发时间之外,也降低了项目开发的难度。除此之外,框架经过多个项目的多次验证,比起每个项目都重新开发,代码的 bug 会相对少一些。而且,不同的项目使用相同的框架,对于研发人员来说,从一个项目切换到另一个项目的学习成本,也会降低很多。
拿常见的 Web 项目开发举例来说,通过在项目中引入 Spring MVC 开发框架,开发一个 Web 应用,我们只需要创建 Controller、Service、Repository 三层类,在其中填写相应的业务代码,然后做些简单的配置,告知框架 Controller、Service、Repository 类之间的调用关系,剩下的非业务相关的工作,比如,对象的创建、组装、管理,请求的解析、封装,URL 与 Controller 之间的映射,都由框架来完成。
不仅如此,如果直接引入功能更强大的 Spring Boot,那将应用部署到 Web 容器的工作都省掉了。Spring Boot 内嵌了 Tomcat、Jetty 等 Web 容器。在编写完代码之后,我们用一条命令就能完成项目的部署、运行。
Spring 框架蕴含的设计思想
约定优于配置
在使用 Spring 开发的项目中,配置往往会比较复杂、繁琐。比如,我们利用 Spring MVC 来开发 Web 应用,需要配置每个 Controller 类以及 Controller 类中的接口对应的 URL。
简化配置有两种方法
-
基于注解
在指定类上使用指定的注解,来替代集中的 XML 配置。比如,使用 @RequestMapping 注解,在 Controller 类或者接口上,标注对应的 URL;使用 @Transaction 注解表明支持事务等。
-
基于约定
也常叫作“约定优于配置”或者“规约优于配置”(Convention over Configuration)。通过约定的代码结构或者命名来减少配置。就是提供配置的默认值,优先使用默认值。程序员只需要设置那些偏离约定的配置就可以了。比如,在 Spring JPA(基于 ORM 框架、JPA 规范的基础上,封装的一套 JPA 应用框架)中,我们约定类名默认跟表名相同,属性名默认跟表字段名相同,String 类型对应数据库中的 varchar 类型,long 类型对应数据库中的 bigint 类型等等。
基于刚刚的约定,代码中定义的 Order 类就对应数据库中的“order”表。只有在偏离这一约定的时候,例如数据库中表命名为“order_info”而非“order”,我们才需要显示地去配置类与表的映射关系(Order 类 ->order_info 表)。
两种配置方式在 Spring 中都有用到。Spring 在最小化配置方面做得淋漓尽致,有很多值得我们借鉴的地方。
低侵入、松耦合
Spring 提供的 IOC 容器,在不需要 Bean 继承任何父类或者实现任何接口的情况下,仅仅通过配置,就能将它们纳入进 Spring 的管理中。如果我们换一个 IOC 容器,也只是重新配置一下就可以了,原有的 Bean 都不需要任何修改。
除此之外,Spring 提供的 AOP 功能,也体现了低侵入的特性。在项目中,对于非业务功能,比如请求日志、数据采点、安全校验、事务等等,我们没必要将它们侵入进业务代码中。因为一旦侵入,这些代码将分散在各个业务代码中,删除、修改的成本就变得很高。而基于 AOP 这种开发模式,将非业务代码集中放到切面中,删除、修改的成本就变得很低了。
模块化、轻量级
从图中我们可以看出,Spring 在分层、模块化方面做得非常好。每个模块都只负责一个相对独立的功能。模块之间关系,仅有上层对下层的依赖关系,而同层之间以及下层对上层,几乎没有依赖和耦合。除此之外,在依赖 Spring 的项目中,开发者可以有选择地引入某几个模块,而不会因为需要一个小的功能,就被强迫引入整个 Spring 框架。所以,尽管 Spring Framework 包含的模块很多,已经有二十几个,但每个模块都非常轻量级,都可以单独拿来使用。正因如此,到现在,Spring 框架仍然可以被称为是一个轻量级的开发框架。
再封装、再抽象
Spring 不仅仅提供了各种 Java 项目开发的常用功能模块,而且还对市面上主流的中间件、系统的访问类库,做了进一步的封装和抽象,提供了更高层次、更统一的访问接口。
比如,Spring 提供了 spring-data-redis 模块,对 Redis Java 开发类库(比如 Jedis、Lettuce)做了进一步的封装,适配 Spring 的访问方式,让编程访问 Redis 更加简单。
还有Spring Cache,实际上也是一种再封装、再抽象。它定义了统一、抽象的 Cache 访问接口,这些接口不依赖具体的 Cache 实现(Redis、Guava Cache、Caffeine 等)。在项目中,我们基于 Spring 提供的抽象统一的接口来访问 Cache。这样,我们就能在不修改代码的情况下,实现不同 Cache 之间的切换。除此之外, JdbcTemplate是对 JDBC 的进一步封装和抽象,为的是进一步简化数据库编程。不仅如此,Spring 对 JDBC 异常也做了进一步的封装。封装的数据库异常继承自 DataAccessException 运行时异常。这类异常在开发中无需强制捕获,从而减少了不必要的异常捕获和处理。除此之外,Spring 封装的数据库异常,还屏蔽了不同数据库异常的细节(比如,不同的数据库对同一报错定义了不同的错误码),让异常的处理更加简单。