老一辈的软件开发人员一般经历了从Model1到Model2,然后到后来的三层模型,最后到现在的Spring Boot。如果从Model1到Model2说起到我们现在使用的Spring Boot为整个时间轴的话,大致可以分为4个阶段:
(1)初级阶段:使用Model1/Model2/三层模模型进行开发;
(2)中级阶段:使用EJB进行分布式应用开发,忍受重量级框架带来的种种麻烦;
(3)高级阶段:使用Spring春天带给我们的美好,但是还要忍受很多繁琐的配置;
(4)骨灰级阶段:使用Spring Boot,畅享“预定大于配置”带给我们的种种乐趣!
Web发展初级阶段
1、Model1开发模式:
Model1的开发模式是:JSP+JavaBean的模式,它的核心是Jsp页面,在这个页面中,Jsp页面负责整合页面和JavaBean(业务逻辑),而且渲染页面,它的基本流程如下:
相信很多小伙伴在刚学习Web的时候,肯定使用到了Model1开发模式,也就是我们的业务代码、持久化代码直接写在Jsp页面里边,使用Jsp直接处理Web浏览器的请求,并使用JavaBean处理业务逻辑。
利用我们现在熟悉的MVC模型的思想去看,虽然编写代码十分容易,但Jsp混淆了MVC模型中的视图层和控制层,高度耦合的结果是Jsp代码十分复杂,后期维护困难!
2、Model2开发模式:
Model1虽然在一定程度上解耦了,但JSP依旧即要负责页面控制,又要负责逻辑处理,职责不单一!此时Model2应运而生,使得各个部分各司其职,Model2是基于MVC模式的。
Model2的开发模式是:Jsp+Servlet+JavaBean的模式,它和Model1不同的是,增加了Servlet,将调用页面数据,调用业务逻辑等工作放到了Servlet中处理,从而减轻了Jsp的工作负担!它的基本流程如下:
Model2开发模式将Servlet的概念引入架构体系中,使用它来分配视图层Jsp的显示页面,同时调用模型层的JavaBean来控制业务逻辑。
3、Model1和Model2的区别:
Model1:简单,适合小型项目的开发,但是Jsp的职责过于繁重,职责分工不明确。在后期的维护工作中,必将为此付出代价!
Model2:相对于Model1来说,职责分工更为明确,在Model1的基础上,抽取了Servlet层,体现了一个分层的思想,适合大型的项目开发!(当时的评判标准是适合大型项目开发的,现在看起来已经过时了!)
Model2看起来已经尽善尽美了,尽管如此,他还不能称之为一个比较完善的MVC设计模式!
4、Model1和Model2与三层的对比:
在Model2中,我们将Servlet抽取出单独的一层,和Jsp协作完成用户数据交互的工作,也就是表示层。那么作为三层结构来说,又做了什么样的改进呢?三层则是在此基础上,将JavaBean再一次进行分割:业务逻辑、数据持久化,三层如下:
(1)表示层,JSP/Servlet;
(2)业务逻辑层:业务规则;
(3)持久化层:主要包装持久化的逻辑 ;
各个的耦合性如下图:
Model1、Model2、三层是在解耦的基础上一步步进化而来,通过解耦我们可以进行进一步的抽象,以应对现实需求的变动。
四、Web发展中级阶段、高级阶段和骨灰级阶段
这一小节似乎有点应付,对于中级阶段,因为我没有用过EJB,在这里不敢妄加评论,以免误导大家。但是相信每一位接触过Spring的小伙伴,都应该知道Rod Johnson在2002年编写的《Expert One-to-One J2EE Design and Development》一书,Rod 在本书中对J2EE正统框架臃肿、低效、脱离现实的种种学院派做法提出了质疑,并以此书为指导思想,编写了interface21框架,也就是后来的Spring。
对于高级阶段和骨灰级阶段是我们后期一系列文章的重点,本篇只作为一个阶段划分,不做过多的解释,因此让我们重新回到Web发展的初级阶段。
对EJB有兴趣的可以参考文章:http://www.uml.org.cn/j2ee/2009112011.asp
五、Web发展初级阶段存在的问题
经历过初级阶段的小伙伴肯定看得懂下边的一个项目结构,一个简单的MVC三层结构,使用JSP+Servlet+MySQL+JDBC技术,面向接口编程:
1、面向接口编程的实例化对象
以用户管理模块为例,有一个UserDao接口,有一个接口的实现类UserDaoImpl,如下:
由于是面向接口编程,因此我们在每次使用UserDao的时候,都要进行实例化一次,实例化代码如下:
UserDao userDao = new UserDaoImpl();
我们在每次使用UserDao的时候都需要进行实例化,当然不仅仅有UserDao需要进行实例化,还有很多需要进行实例化的,举例如下:
可以看出,每一个方法中都需要进行实例化我们需要用到的接口的实现类,这就会存在大量的实例化对象,并且他们的生命周期可能就是从方法的调用开始到方法的调用结束为止,加大了GC回收的压力!
2、使用单利模式的一次改进
了解设计模式的可能会想到使用单利模式的方式来解决这个问题,以此来避免大量重复的创建对象,但是我们还要考虑到众多的这种对象的创建都需要改成单利模式的话,是一个耗时耗力的操作。
对于这个系统来说,如果都把这种面向接口的对象实现类转换为单利模式的方式的话,大概也要写十几个或者上百个这种单例模式代码,而对于一个单利模式的写法来说,往往是模板式的代码,以静态内部类的方式实现代理模式如下:
可以看出,这种方式有两个问题:
(1)业务代码与单利模式的模板代码放在一个类里,耦合性较高;
(2)大量重复的单利模式的模板代码;
从上述可以看出,使用的单利模式虽然从性能上有所提高,但是却加重了我们的开发成本。因此只会小规模的使用,例如我们操作JDBC的Utils对象等。
3、我们开发中遇到的痛点
从上述代码的演进过程我们可以看得出来,我们即需要一个单利的对象来避免系统中大量重复对象的创建和销毁,又不想因为使用单利模式造成大量重复无用的模板代码和代码的耦合!
(突然想到一个段子,想和大家分享一下:产品经理在给甲方汇报方案的时候说了两种方案:一种是实用的,一种是美观的,问甲方希望选择哪一种?甲方说:有没有即实用又美观的!)
4、我们还能怎么做
作为学院派的书生来说,我们可能会联想到“数据库连接池”,我们在获取数据库连接的时候会从这个池子中拿到一个连接的,假设这个数据库连接池很特殊,有且只能有N个数据库连接,并且每一个连接对象都不同(假设),那么这个不就相当于每一个连接都是单利的了吗?既可以避免大量对象的创建,也可以实现不会出现大量重复性的模板代码。
因此,这里应该有一个大胆的想法,我们是否可以建立一个池子,将我们的接口实现类对象放入到这个池子中,我们在使用的时候直接从这个池子里边取就行了!
5、这个池子
如果我们要创建这个池子,首先要确定需要把哪些对象放进这个池子,通过怎样的方式放进去,放进去之后如何进行管理,如何进行获取,池子中的每一个对象的生命周期是怎么样的等等这些东西都是我们需要考虑到的!
6、恭喜你
如果你已经了解了上述Web演进的过程,以及我们想要创建的这个池子,那么恭喜你!你已经打开了Spring核心原理的大门了!
上述我们想要创建的池子其实就是Spring容器的雏形,将接口实现类的对象放进池子进行管理的过程其实也是Spring IOC依赖注入、控制反转的雏形!
Spring的依赖注入/控制反转就是从我们的配置文件或注解中的得到我们需要进行注入到Spring容器的实现类的信息,Spring IOC通过这些配置信息创建一个个单利的对象并放入Spring容器中,Spring容器可以看做是一个集合保存着我们的这些对象。
一. Spring框架的作用
轻量:Spring是轻量级的,基本的版本大小为2MB
控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
面向切面的编程AOP:Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
容器:Spring包含并管理应用中对象的生命周期和配置
MVC框架: Spring-MVC
事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务JTA
异常处理:Spring提供方便的API把具体技术相关的异常
二. Spring的组成
Spring由7个模块组成:
Spring Core: 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
三. Spring容器
Sping的容器可以分为两种类型
1. BeanFactory:(org.springframework.beans.factory.BeanFactory接口定义)是最简答的容器,提供了基本的DI支持。最常用的BeanFactory实现就是XmlBeanFactory类,它根据XML文件中的定义加载beans,该容器从XML文件读取配置元数据并用它去创建一个完全配置的系统或应用。
2. ApplicationContext应用上下文:(org.springframework.context.ApplicationContext)基于BeanFactory之上构建,并提供面向应用的服务。
四. ApplicationContext通常的实现
ClassPathXmlApplicationContext:从类路径下的XML配置文件中加载上下文定义,把应用上下文定义文件当做类资源。
FileSystemXmlApplicationContext:读取文件系统下的XML配置文件并加载上下文定义。
XmlWebApplicationContext:读取Web应用下的XML配置文件并装载上下文定义。
1 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
五. IOC & DI
Inversion of Control, 一般分为两种类型:依赖注入DI(Dependency Injection)和依赖查找(Dependency Lookup).依赖注入应用比较广泛。
Spring IOC扶着创建对象,管理对象(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
优点:把应用的代码量降到最低。容器测试,最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。
DI依赖注入是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用床架对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述组件需要哪些服务,之后一个IOC容器辅助把他们组装起来。
IOC的注入方式:1. 构造器依赖注入;2. Setter方法注入。
六. 如何给spring容器提供配置元数据
XML配置文件
基于注解的配置
基于Java的配置@Configuration, @Bean
七. bean标签中的属性:
id
name
class
init-method:Bean实例化后会立刻调用的方法
destory-method:Bean从容器移除和销毁前,会调用的方法
factory-method:运行我们调用一个指定的静态方法,从而代替构造方法来创建一个类的实例。
scope:Bean的作用域,包括singleton(默认),prototype(每次调用都创建一个实例), request,session, global-session(注意spring中的单例bean不是线程安全的)
autowired:自动装配 byName, byType, constructor, autodetect(首先阐释使用constructor自动装配,如果没有发现与构造器相匹配的Bean时,Spring将尝试使用byType自动装配)
八. beans标签中相关属性
default-init-method
default-destory-method
default-autowire:默认为none,应用于Spring配置文件中的所有Bean,注意这里不是指Spring应用上下文,因为你可以定义多个配置文件。