“Spring”——每个Javaer开发者都绕不开的字眼,从21世纪第一个十年国内异常活跃的SSH框架,到如今以Spring Boot做为入口粘合了各类应用。Spring如今已经完成了从web入口到微服务架构再到数据处理整个生态,看着如今https://spring.io/projects上长长的项目清单,一脸懵逼的自问到这些究竟是啥?能够干吗?html
早期的Spring并无这么多亮瞎眼的项目,仅仅是围绕着core、context、beans以及MVC提供了一个简单好用搭建网站级应用的工具。那个时候彻底是一个与J2EE的繁杂多样对抗简单便捷的小清新。Srping之父Rod的一本《J2EE Development without EJB》宣告J2EE那么名堂彻底没多大用处。通过这么多年的发展,事实也证实除了Servlet、JDBC以及JSP彷佛其余东西无关紧要。后来Vertx、WebFlux等Reactive机制框架的出现,以及先后端分离开发的盛行,彷佛Servlet也无关紧要了、jsp也快消失了。因此如今Oracle干脆把J2EE这个烫手山芋直接丢给开源社区了。java
Rod的轮子理论造就了Spring的2大核心概念——IoC(Inversion of Control)和beans。Spring IoC和Beans的概念度娘、谷哥一搜一大把,在此就不重复介绍了。我的认为IoC和Beans最基本的实现思想来自于设计模式的几大原则,它之因此这么好用而且深刻人心就是体现了设计模式的精髓。mysql
依赖倒转原则:Spring的介绍Framework文档的开篇就提到反向依赖注入(DI——dependency injection ),其目标是让调用者不要主动去使用被调用者,而是让被调用者向调用者提供服务。IoC和beans的配合完美实现了这个过程,一个@component注解添加一个bean到Ioc容器,一个@autowired注解Ioc容器会找到对应的类注入进来。web
接口隔离原则:Ioc不只仅根据class类型注入bean,他还会根据接口类型自动装配注入一个bean。spring
里氏代换原则:在接口隔离的原则的基础上咱们能够利用XML配置文件来制定装配的服务。例如javax.sql.DataSource是Java里提供数据库连接服务的接口,世面上有各类各样开源或闭源的工具实现了DataSource接口,例如c3p0和druid。咱们想要切换他们仅仅须要像下面这样添加或删除一个bean(固然先要引入Jar包):sql
<!-- 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框架自己那么多构建类都是按照这个原则开发的——新功能用新的类实现,而非增长原有方法。后端
如今咱们知道Spring的2大核心是IoC和Beans。IoC字面翻译叫“控制反转”,这个“反转”过程实现的思想其实蛮简单的:就是先有一个容器(container),咱们把实现各类功能的bean(一个类的实例)一股脑向容器里面扔,至于最后这些bean被谁用了经过配置和注解来肯定。设计模式
上面提到了配置,在2.5版本以前配置只能经过XML文件实现,以后引入了annotation配置的方式,而后3.x版本以后能够彻底使用Java代码来实现配置而无需XML文件。配置文件的格式和做用其实也不复杂,就是告诉容器我要扔进去什么bean。扔进去的bean固然须要初始化一些数据了,丢一个光秃秃没有任何数据的实例到容器中貌似也没多大用处,因此XML文件中就提供了一些标签来标记如何初始化数据:websocket
<?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” />
其实注意一下四点便可:
我的感受使用spring到如今name出现场景并很少,也不多看到哪一个开源项目经过name的方式向外暴露服务。
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直译过来叫范围、界限、广度。不过按照字面意思理解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>