Spring核心——设计模式与IoC
“Spring”——每一个Javaer开发者都绕不开的字眼,从21世纪第一个十年国内异常活跃的SSH框架,到现在以Spring Boot作为入口粘合了各种应用。Spring现在已经完成了从web入口到微服务架构再到数据处理整个生态,看着现在https://spring.io/projects上长长的项目清单,一脸懵逼的自问到这些到底是啥?可以干嘛?
一切都从IoC开始
早期的Spring并没有这么多亮瞎眼的项目,仅仅是围绕着core、context、beans以及MVC提供了一个简单好用搭建网站级应用的工具。那个时候完全是一个与J2EE的繁杂多样对抗简单便捷的小清新。Srping之父Rod的一本《J2EE Development without EJB》宣告J2EE那么名堂完全没多大用处。经过这么多年的发展,事实也证明除了Servlet、JDBC以及JSP似乎其他东西可有可无。后来Vertx、WebFlux等Reactive机制框架的出现,以及前后端分离开发的盛行,似乎Servlet也可有可无了、jsp也快消失了。所以现在Oracle干脆把J2EE这个烫手山芋直接丢给开源社区了。
Rod的轮子理论造就了Spring的2大核心概念——IoC(Inversion of Control)和beans。Spring IoC和Beans的概念度娘、谷哥一搜一大把,在此就不重复介绍了。个人认为IoC和Beans最基本的实现思想来自于设计模式的几大原则,它之所以这么好用并且深入人心就是体现了设计模式的精髓。
依赖倒转原则:Spring的介绍Framework文档的开篇就提到反向依赖注入(DI——dependency injection ),其目标是让调用者不要主动去使用被调用者,而是让被调用者向调用者提供服务。IoC和beans的配合完美实现了这个过程,一个@component注解添加一个bean到Ioc容器,一个@autowired注解Ioc容器会找到对应的类注入进来。
接口隔离原则:Ioc不仅仅根据class类型注入bean,他还会根据接口类型自动装配注入一个bean。
里氏代换原则:在接口隔离的原则的基础上我们可以利用XML配置文件来制定装配的服务。例如javax.sql.DataSource是Java里提供数据库链接服务的接口,世面上有各种各样开源或闭源的工具实现了DataSource接口,例如c3p0和druid。我们想要切换他们仅仅需要像下面这样添加或删除一个bean(当然先要引入Jar包):
<!-- c3p0 -->
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate"/>
<property name="user" value="admin"/>
<property name="password" value="123456"/>
</bean>
<!-- druid -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/c3p0jdbctemplate" />
<property name="username" value="admin"/>
<property name="password" value="123456"/>
</bean>
聚合复用原则:SpringFramework号称非侵入式框架,我们在使用的过程中也很少有继承的情况,基本上所有的特性都是通过注解(Annotation)来实现,需要某一项服务也是将其注入后使用。虽然我们在开发的过程中为了实现一些高级功能会继承重写某些方法后,然后再将我们的新类添加到Ioc中,但是Spring本身并不太鼓励这样去实现。
除了前面4项原则,迪米特法则和开闭原则并没有太直观的体现。对于迪米特法则来说Ioc机制本身就实现了调用者与被调用者之间不会直接发生依赖关系(new创建)。而开闭原则,Spring框架本身那么多构建类都是按照这个原则开发的——新功能用新的类实现,而非增加原有方法。
Beans
配置
现在我们知道Spring的2大核心是IoC和Beans。IoC字面翻译叫“控制反转”,这个“反转”过程实现的思想其实蛮简单的:就是先有一个容器(container),我们把实现各种功能的bean(一个类的实例)一股脑向容器里面扔,至于最后这些bean被谁用了通过配置和注解来确定。
上面提到了配置,在2.5版本之前配置只能通过XML文件实现,之后引入了annotation配置的方式,然后3.x版本之后可以完全使用Java代码来实现配置而无需XML文件。配置文件的格式和作用其实也不复杂,就是告诉容器我要扔进去什么bean。扔进去的bean当然需要初始化一些数据了,丢一个光秃秃没有任何数据的实例到容器中貌似也没多大用处,所以XML文件中就提供了一些标签来标记如何初始化数据:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 省略xmlns -->
<beans>
<bean id="otherBean" class="myProject.OtherBean" />
<bean id="myBean" class="myProject.MyClass">
<!-- 通过setOtherBean方法设置OtherBean的实例 -->
<property name="otherBean" ref="otherBean"/>
<!-- 通过setValue方法设置数值 -->
<property name="value" value="myValue"/>
</bean>
</beans>
参数
下面是Bean相关的参数,它们可以用XML<bean>标签来配置,也可以用@bean传递一个参数来设定:
class | 标记当前Bean加载的类 |
name | Bean的别名和名称。 |
scope | Bean的范围,默认是单例。 |
constructor | 构造函数注入<constructor-arg /> |
properties | 属性注入<property> |
autowiring | auto注入模式 |
lazy | 懒加载模式 |
initialization | 制定初始化类时执行的方法 |
destruction | 制定类销毁时要执行的方法 |
Spring Framework的官网用了一个小节专门介绍bean的命名方式,既可以用id来标识,又可以用name来标识,第一次看还挺晕乎的。
<bean id="myBeanId" name=“myAlias1,myAlias2” />
其实注意一下四点即可:
- id和name均可以标识一个bean,但是id必须是全局一对一的,而一个bean可以用多个name,用,号分割。
- 如果不给bean制定id,那么容器会为他自动生成一个唯一的序列号。
- name可以配合<alias>标签使用来转换别名。
个人感觉使用spring到现在name出现场景并不多,也很少看到哪个开源项目通过name的方式向外暴露服务。
创建模式与Scope
Bean只是一个和IoC容器相对应的概念:IoC容器存放并管理bean,bean是IoC机制的最小工作单元。往后的AOP等功能都是建立在Bean的基础上拓展开来的——要使用Spring这些功能首先得是一个Ioc容器中的Bean。Bean实际上就是一个Java类的实例,只不过实例化工作交给了Ioc容器而已。
Bean的实例化有3种方式——构造方法创建、静态工厂、动态工厂。每一个Bean对应的Scope实际上就2个参数——singleton与prototype(实际上还有其他参数可以使用,这里说只有2个具体原因见后面Scope的说明)。
单例构造创建
90%的Bean都是直接通过这种方法方法来创建的。这也是我们最常见的配置方式:
<bean id="myBean" class="myProject.MyClass" />
当以上面这样的方式配置一个bean时,Ioc容器会直接调用构造方法来创建一个类实例(当然在定义类时必须提供一个公开的构造方法)。由于默认情况下bean的scope参数是singleton,所以创建出来bean在不指定scope的状态下都是一个单例。
某些时候我们会在类当中再用static 来设定一个嵌入类:
package myProject;
class MyClass {
static class MyNestClass{
public MyNestClass(){}
}
}
可以通过“$”符号关联的方式创建这个Bean:
<bean id="myBean" class="myProject.MyClass$MyNestClass" />
静态工厂创建
静态工厂创建bean和静态工厂模式的概念一样,就是指定一个工厂类,然后通过一个静态方法返回一个新的bean。
XML配置:
<bean id="myFactory"
class="myProject.MyFactory"
factory-method="createInstance"/>
工厂类:
class MyFactory {
static class MyClass{};
private static MyClass myClass = new MyClass();
private MyFactory() {}
public static MyClass createInstance() {
return myClass;
}
}
动态工厂创建
动态工厂在设计模式上叫“抽象工厂”,spring官网将其自称为实例工厂(instance factory)。这里叫“动态工厂”是想对他们加以区分。虽然“实例工厂”并不是教科书似的抽象工厂,但是目的就是实现工厂动态创建。动态工厂与静态工厂最大的区别就是会先将工厂本身设置成一个bean(实例化),然后再通过这个工厂bean来创建“产品bean”。看下面的例子:
<bean id="myLocator" class="myProject.MyLocator">
<!-- 自身就是一个实例化的bean,可以设定任何bean的配置 -->
</bean>
<!-- 绑定bean与一个动态工厂 -->
<bean id="instanceFactory"
factory-bean="myLocator"
factory-method="createInstance"/>
class MyFactory {
static class MyClass{};
public MyClass createInstance() {
return new MyClass();
}
}
一个工厂可以同时用于创建多个bean方法:
<bean id="myLocator" class="myProject.MyFactory" />
<bean id="serverOne"
factory-bean="myLocator"
factory-method="createClassOne"/>
<bean id="serverTwo"
factory-bean="myLocator"
factory-method="createClassTwo"/>
class MyFactory {
static class MyServerOne{};
static class MyServerTwo{};
public MyServerOne createClassOne() {
return new MyServerOne();
}
public MyServerTwo createClassTwo() {
return new MyServerTwo();
}
}
为什么需要实例化方法
可能你会想,Spring实例化提供一个简单的bean创建实例就好了,干嘛还要整静态工厂、抽象工厂之类的东西?
实际上我个人认为Spring的架构大神们是想通过一套简单的机制帮你实现设计模式中的所有创建模式——静态工厂、抽象工厂、单例模式、建造者模式和原型模式。因为IoC的最大任务之一就是代替我们创建各种Bean(类实例),而类实例的创建无非就是这几种创建模式。
这里仅仅介绍了2种工厂模式,下面将结合Bean的Scope属性介绍其他模式的思路。
Scope
scope直译过来叫范围、界限、广度。不过按照字面意思理解Bean的Scopd属性肯定要跑偏的。Scope数据涉及2个层面的含义。
首先在实现层面,对于设计模式来说,Scope就只有2种模式——singleton模式和prototype模式。
其次在应用层面,除了上面2个,Scope还提供了request、session、application、websocket。从字面上看就知道实际上这些Scope参数仅仅是指定了一个bean的适用范围。
以request为例,要启用他需要保证应用的“上下文”是web模式,例如XmlWebApplicationContext,其他情况下会抛出异常。然后"scope=request"的工作方式就是外部发起一个请求后,web层(servlet)启用一个线程来响应这个请求。到了业务层面我们需要指定一些bean来处理这个请求,当这些bean设定为request时,那么它仅仅用于这一次请求就抛弃。下一次请求出现时会创建一个新的实例。
所以不管是request、session、application还是websocket,实际上都是通过prototype模式创建的实例,也就是设计模式中的原型模式,虽然并不一定是教科书般的标准,但是在整个容器中他实现了原型的特性。
此外singleton模式和 Gang of Four (GoF)中定义的通过ClassLoad实现的单例模式也有很大的区别,但是对于Ioc容器而言,任何bean在一个容器中绝对是一个单例,现在所有的资源都通过容器来管理依赖关系,那么最终的效果也是一个单例。
建造者模式
到目前为止,还有一个创建模式未出场——建造者模式。建造者模式实际上就是通过一个标准的方法组装一个复杂的对象。
标准的建造者模式先得有一个Director提供外部访问接口,外部调用者要创建一个复杂对象时向接口传递指定参数,然后Director根据参数调用Builder提供的各种方法,这些方法再用concrete去构建最终的Product。
实际上把复杂对象创建的过程看成各个bean依赖构造的过程即可实现模式,例如:
<!-- cpu部件 -->
<bean id="amdCpu" class="myProject.cpu.Amd"/>
<bean id="intelCpu" class="myProject.cpu.Intel"/>
<!-- 显卡部件 -->
<bean id="amdGraphics" class="myProject.graphics.Amd"/>
<bean id="nvdiaGraphics" class="myProject.graphics.Nvdia"/>
<!-- 组装电脑1 -->
<bean id="myComputer" class="myProject.computer.MyComputer">
<property name="cpu" ref="amdCpu"/>
<property name="graphics" ref="nvdiaGraphics"/>
</bean>
<!-- 组装电脑2 -->
<bean id="yourComputer" class="myProject.computer.YourComputer">
<property name="cpu" ref="intelCpu"/>
<property name="graphics" ref="amdGraphics"/>
</bean>
原文连接:https://my.oschina.net/chkui/blog/1835837
Spring核心——设计模式与IoC的更多相关文章
- 30个类手写Spring核心原理之Ioc顶层架构设计(2)
本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...
- Spring核心原理之IoC容器初体验(2)
本文节选自<Spring 5核心原理> 1 IoC与DI基本概念 IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建.依赖,反转给容器来帮忙实现 ...
- spring核心思想:IOC(控制反转)和DI(依赖注入)
Spring有三大核心思想,分别是控制反转(IOC,Inversion Of Controller),依赖注入(DI,Dependency Injection)和面向切面编程(AOP,Aspect O ...
- Spring核心原理之 IoC容器中那些鲜为人知的细节(3)
本文节选自<Spring 5核心原理> Spring IoC容器还有一些高级特性,如使用lazy-init属性对Bean预初始化.使用FactoryBean产生或者修饰Bean对象的生成. ...
- Spring核心思想:IOC(控制反转)、DI(依赖注入)和AOP(面向切面编程)
Spring有三大核心思想,分别是控制反转(IOC,Inversion Of Controller),依赖注入(DI,Dependency Injection)和面向切面编程(AOP,Aspect O ...
- Spring核心模块:IoC容器介绍
1.IoC容器运用的是控制反转模式. 2.IoC容器负责管理对象之间的依赖关系,并完成对象的注入. 3.在IoC设计中,会将依赖关系注入到特定组件中,其中setter注入和构造器注入是主要的注入方式. ...
- Spring的三大核心思想:IOC(控制反转),DI(依赖注入),AOP(面向切面编程)
Spring核心思想,IoC与DI详解(如果还不明白,放弃java吧) 1.IoC是什么? IoC(Inversion of Control)控制反转,IoC是一种新的Java编程模式,目前很多 ...
- 2019年Spring核心知识点整理,看看你掌握了多少?
前言 如今做Java尤其是web几乎是避免不了和Spring打交道了,但是Spring是这样的大而全,新鲜名词不断产生,学起来给人一种凌乱的感觉,在这里总结一下,理顺头绪. Spring 概述 Spr ...
- Spring核心之IOC
IOC是Spring的两大核心之一:IOC的核心就是解耦. 举个例子:有2个班级可以上课,校长指定老师去上课,代码如下 package com.hongcong.test; public class ...
随机推荐
- 使用Spring+MySql实现读写分离(一)关于windows下安装mysql5.6
前面讲过关于mysql的优化,主要是建表时对于大量数据的表添加索引机制,提高查询效率,以及一些sql语句的简单优化,毕竟我也不是专业的数据库管理员,大牛勿喷. 今天写两章关于javaweb项目中,对于 ...
- [转] Introduction to AppArmor
Introduction to AppArmor http://ubuntuforums.org/showthread.php?t=1008906 Contents Post 1 Introducti ...
- Jenkins 定时构建语法规则
1.Jenkins自由风格任务定时构建 2.语法规则 定时构建语法 * * * * * 第一个*表示分钟,取值0~59 第二个*表示小时,取值0~23 第三个*表示一个月的第几天,取值1~31 第四个 ...
- 两张图彻底搞懂MyBatis的Mapper原理!
作者:肥朝 简单使用 这是一个简单的Mybatis保存对象的例子 1@Test 2public void testSave() throws Exception { 3 //创建sessionFact ...
- Kubernetes---DaemonSet
DaemonSet用于管理在集群中每个Node上仅运行一份Pod的副本实例. kind: DaemonSet
- [Objective-C语言教程]数组(14)
Objective-C编程语言提供了一种叫作数组的数据结构,它可以存储相同类型的固定大小顺序元素的集合.数组用于存储数据集合,但将数组视为相同类型的变量集合通常更有用. 可以声明一个数组变量(例如nu ...
- python高级-面向对象特性(12)
一.继承的概念 在现实生活中,继承一般指的是子女继承父辈的财产,在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物:同理,波斯猫和巴厘猫都继承自猫,而沙 ...
- 【MML】华为MML AAA接口联调,Java版本
1.我们先设置一些常量数据 package cn.cutter.ztesoft.HuWeiMML.constrant; /** * @description: AAA接口常量设置 * @author: ...
- Servlet & JSP系列文章总结
前言 谢谢大家的捧场,真心感谢我的阅读者. @all 下一期,重点在 数据结构和算法 ,希望给大家带来开心.已经出了几篇,大家爱读就是我的开心. Servlet & JSP系列总结 博客, ...
- iOS逆向开发(3):锁定APP的目标类与函数 | reveal | lldb | debugserver | 远程调试
之前介绍了怎么获取APP的所有类的结构信息,这个有什么用呢?用处大了,比如以这一步为基础,下一步通过注入来做更多研究工作. 注入的最小单位是函数,实际上,编译执行的程序在编译后,类就不复存在了,留下来 ...