本文共 8567 字,大约阅读时间需要 28 分钟。
因为笔者本学期自学了 SSM 三大框架以及 SpringBoot 框架,并且也做了两个基于 SSM 的小项目。于是对其中的最核心的 Spring 框架也有了一定的了解,于是想借此机会来剖析剖析其涉及到的设计思想,希望在这个过程中能进一步理解 Spring 的底层思想以及相关的设计思想的原理和应用。
Spring 是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
要谈到 Spring,那必须谈及其最核心的两个部分,就是控制反转 IoC 和 面向切面编程 AOP。
IoC 的中文意思就是“控制反转”。那什么叫“控制反转”呢?笔者是这么理解的:比如我们要想要吃一个面包,第一种途径就是自己动手,丰衣足食。我们可以自己去买面粉以及其他各种材料,自己在家里制作一个面包,这样我们可以任意地 DIY 我们的面包,包括口味,包括外形。当然,一般人都会选择第二种途径,那就是去面包店购买面包,我们甚至可以提供我们对口味、对外形的要求,定制我们想要的面包。这样生产面包的职责,就从我们手里反转到面包店手里了,即把制作面包的主动权交给了店家。
用专业的话来讲呢,在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用 Spring 的时候,每个对象在需要使用他的合作对象时,自己均要使用像 new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的。
这样会有什么缺点呢?—— 耦合度太高。
A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起。
使用 Spring 框架后就不一样了,创建合作对象B的工作是由 Spring 来做的,Spring 创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring 就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于 Spring 是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题,A得到 Spring 给我们的对象之后,两个人一起协作完成要完成的工作即可。
Spring 框架中的 AOP 是一种面向切面编程的思想。用通俗易懂的话来讲就是抽取出分散在各个方法中的重复代码,然后在程序编译或运行阶段将这些抽取处理的代码插入到需要执行的地方。如下图所示,这样我们就只需要专注于实现软件的业务逻辑模块即可。
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
针对抽象层编程,将具体类的对象通过依赖注入(Dependency Injection, DI)的方式注入到其他对象。
在 Spring 框架中实现 IoC 容器的方法就是依赖注入,其作用是在使用 Spring 框架创建对象时动态地将其所依赖的底下注入 Bean 组件中,其目标是让调用者不要主动去使用被调用者,而是让被调用者向调用者提供服务。IoC 和 bean对象 的配合完美实现了这个过程,我们可以使用 @Component 、@Service、@Controller、@Repository 等注解添加一个 bean 到 IoC 容器,然后通过 @autowired 注解就可以让 IoC 容器自动找到对应的类注入进来。
客户端不应该依赖那些它不需要的接口。
Spring 的 IoC 容器不仅仅可以通过一个类的类型来实现依赖注入,也可以通过接口类型来实现依赖注入,我们只需要让接口更加专门化,然后将其放入 IoC 容器中,需要的时候使用 Spring 进行注入即可。
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
迪米特法则要求一个软件实体应当尽可能少地与其他实体发生相互作用。
从Spring IoC 的机制上来说,天然就降低了不同实体之间的依赖关系,因为我们不是通过 new 的方式来创建对象,而且通过依赖注入的方式,所以很充分地体现了迪米特法则。
软件实体应当对扩展开放,对修改关闭。
至于开闭原则那就更加明显了,我们基于 Spring 框架做开发,在构建类时大多数都是按照该原则 —— 新功能构建新的类来实现,而并非在原有的类的基础上添加新的方法。
Spring 中 Bean 对象的创建就是典型的工厂模式。我们可以看一下下面这个图:
这一系列的 Bean 工厂, 为开发者管理对象间的依赖关系提供了很多便利和基础服务, 在 Spring 中有许多的 IOC 容器的实现供用户选择和使用。
举一个最简单的例子,我们来看看 Spring 是如何利用工厂模式来帮助我们创建 Bean 对象的。先看下面代码:
public static void main(String[] args) { //1. 获取核心容器对象 ApplicationContext appCon = new ClassPathXmlApplicationContext("bean.xml"); //2. 根据 id 获取 bean 对象 IAccountService as = (IAccountService)appCon.getBean("accountService"); //3. 使用创建出来的 bean 对象 as.accountSave();}
这个 ApplicationContext 是什么呢?从名字上看它跟“工厂”好像没什么关系。其实不然,我们可以来看看与它相关联的一些类:
从上图我们可以看到 ApplicationContext 这个接口其实继承了很多个工厂接口。实际上,ApplicationContext 作为 Spring 对外的门面,在Spring 提供了不同的实现,用来通过不同的途径,加载配置,实例化 Spring 容器。通过这种方式,实现接口编程,屏蔽实现细节,增强Spring 的可扩展性。
一个典型例子:Spring 与 Mybatis 的结合
这里我们往 Spring 容器里面配置一个 id 为 sqlSessionFactory 的 bean 对象,我们指定类类型是 SqlSessionFactoryBean,但实际上在创建这个 bean 对象的时候,我们生成的对象类型并不是 SqlSessionFactoryBean,而是 SqlSessionFactory。为什么呢?
查看 SqlSessionFactoryBean.java 源码,我们可以在里面找到以下方法:
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; } public Class getObjectType() { return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass(); }
实际上,SqlSessionFactoryBean 实现了 FactoryBean接口,所以返回的不是 SqlSessionFactoryBean 的实例,而是它的 SqlSessionFactoryBean.getObject() 的返回值。
Spring依赖注入Bean实例默认是单例的(当然,我们可以手动更改其为非单例的)。
Spring的依赖注入都是发生在 AbstractBeanFactory 的 getBean 里。getBean 的 doGetBean 方法调用 getSingleton 进行 bean 的创建。而 singleton 就是 Bean 作用域中的一个。使用 singleton 定义的 Bean 在 Spring 容器中只有一个 Bean 示例。
getBean() 调用 doGetBean()
@Override publicT getBean(String name, @Nullable Class requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); }
doGetBean() 调用 getSingleton 进行 bean 的创建。
protectedT doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { …… …… // Create bean instance. if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { // Explicitly remove instance from singleton cache: It might have been put there // eagerly by the creation process, to allow for circular reference resolution. // Also remove any beans that received a temporary reference to the bean. destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } …… …… return (T) bean;}
Spring 框架中实现 AOP 的底层就是动态代理模式的实现。Spring AOP 中常用 JDK 动态代理和 CGLIB 动态代理这两种技术。Spring 中默认使用 JDK 动态代理实现 AOP 编程。其中用到一个非常关键的类叫做 ProxyFactoryBean。其作用是什么呢?从上文我们知道切面是在应用运行的时刻被织入(所谓织入,就是把切面应用到目标对象并创建新的代理对象的过程)。一般情况下,在织入切面时,AOP 容器会为目标对象的创建动态地创建一个代理对象,然后我们可以从 IoC 容器中获取增强后的目标对象进行使用,这样就实现了 AOP 编程。
Spring 的事件驱动模型使用的就是观察者模式。Spring中观察者模式使用的一个实例就是 Listener 的实现。
我们知道事件机制的实现需要三个部分:事件,事件监听器,事件源。我们就从这三个部分入手分析。
ApplicationEvent 抽象类**[事件]**
public abstract class ApplicationEvent extends EventObject { private static final long serialVersionUID = 7099057708183571937L; private final long timestamp = System.currentTimeMillis(); public ApplicationEvent(Object source) { super(source); } public final long getTimestamp() { return this.timestamp; }}
这个类继承自 JDK 的 EventObject 类,并且通过构造器参数source得到事件源。该类的实现类 ApplicationContextEvent 表示ApplicaitonContext 的容器事件。
ApplicationListener 接口**[事件监听器]**
public interface ApplicationListenerextends EventListener { void onApplicationEvent(E var1);}
继承自jdk的EventListener,所有的监听器都要实现这个接口。这个接口只有一个onApplicationEvent()方法,该方法接受一个ApplicationEvent或其子类对象作为参数,在方法体中,可以通过不同对Event类的判断来进行相应的处理。当事件触发时所有的监听器都会收到消息。
ApplicationContext 接口**[事件源]**
这个接口即是我们所说的 IoC 容器,它不仅继承了许多个工厂接口,它还实现了ApplicationEventPublisher 接口,可以充当事件源。
public interface ApplicationEventPublisher { default void publishEvent(ApplicationEvent event) { this.publishEvent((Object)event); } void publishEvent(Object var1);}
Spring框架的资源访问是通过 Resource 接口来实现的 。Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。这就充分体现了策略模式。
Spring 框架采用模板方法模式来实现以一种统一而集中的方式来处理资源的获取和释放。目前笔者学习到的体现模板方法模式的最典型的例子就是 Jdbc Template。从名字上看我们就可以看得出来这个一个 jdbc 的模板类。
public class JdbcTemplate { public final Object execute(StatementCallback callback){ Connection con=null; Statement stmt=null; try{ con=getConnection(); stmt=con.createStatement(); Object retValue=callback.doWithStatement(stmt); return retValue; }catch(SQLException e){ ... }finally{ closeStatement(stmt); releaseConnection(con); } } ...//其它方法定义 }
SpringMVC 中有一个适配器类叫做 HandlerAdapter。
public interface HandlerAdapter { boolean supports(Object var1); @Nullable ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception; long getLastModified(HttpServletRequest var1, Object var2);}
前端发送请求给后端的时候,进入 SpringMVC 的前端控制器 DispatcherServlet。DispatcherServlet根据 HandlerMapping 返回的 handler,向 HandlerAdatper 发起请求,处理Handler。HandlerAdapter 根据规则找到对应的Handler并让其执行,执行完毕后 Handler 会向 HandlerAdapter 返回一个 ModelAndView,最后由 HandlerAdapter 向DispatchServelet 返回一个 ModelAndView。
HandlerAdatper使得Handler的扩展变得容易,只需要增加一个新的Handler和一个对应的 HandlerAdapter 即可。因此 Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类,让适配器代替 Controller 执行相应的方法。这样在扩展 Controller时,只需要增加一个适配器类就完成了 SpringMVC 的扩展了。
Spring中用到的装饰者模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。其作用就是动态地给一个对象添加一些额外的职责。
以上就是笔者对于 Spring 框架中涉及到的面向对象设计原则和设计模型的分析与讨论。当然,Spring 框架设计到的设计思想远不止这些,目前就笔者所学到的知识也只能是泛泛而谈,但相信随着对 Spring 框架学习的深入以及随着项目经验的积累,在未来会对其有更深入的理解和体会。
参考资料:
武汉大学王翀老师《软件需求与建模相关课件》
《J2EE框架整合开发入门到实战》(清华大学出版社)
https://blog.csdn.net/caoxiaohong1005/article/details/80039656
https://blog.csdn.net/bigtree_3721/article/details/51037547