13-spring框架
unit13-spring框架
spring简介
什么是Spring?
spring是分层的JavaSE及JavaEE应用于全栈的轻量级开源框架,以IoC(Inverse Of Control:控制反转/反转控制)和AOP(Aspact Oriented Programming:面向切面编程)为核心,提供了表现层SpringMVC和持久层Spring
JDBC以及业务层事务管理等众多模块的企业级应用技术,还能整合开源世界中众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。
SSH(struts2 spring hibernate)
SSM(springmvc spring mybatis)
Spring的本质是管理软件中的对象,即创建对象和维护对象之间的关系
Spring的发展历程
1997 年 IBM提出了EJB 的思想
1998 年,SUN制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson (罗德·约翰逊,spring 之父)
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)
2017年9月份发布了spring的最新版本spring 5.0通用版
Spring的优势
1).方便解耦,简化开发
通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为较为底层的需求编写代码,可以更专注于上层的应用。
2).AOP 编程的支持
通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP(Object Oriented Programming:面向对象编程) 实现的功能可以通过 AOP 轻松应付。
3).声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4).方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5).方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。
6).降低 JavaEE API 的使用难度。
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API 的使用难度大为降低。
7).Spring框架源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。
spring的架构
Spring 最初的目标就是要整合一切优秀资源,然后对外提供一个统一的服务。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:
bf0676465991176183ba7d0765728e57
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
模块 | 说明 | |
---|---|---|
核心容器Spring Core | 核心容器,提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 | |
Spring Context | Spring上下文,是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。 | |
Spring AOP | 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能集成到了 Spring 框架中。可以很容易地使 Spring框架管理的任何对象支持AOP。Spring AOP模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,就可以将声明性事务管理集成到应用程序中。 | |
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。 |
程序中的耦合和解耦
什么是程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
总结:在软件工程中,耦合指的就是指对象之间的依赖关系。对象之间的依赖程度越高,耦合度就越高。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。
降低程序之间的依赖程度,即降低程序之间的耦合度的过程就叫做解耦。
例如:早期的Jdbc操作中,在注册数据库驱动时,为什么采用的是Class.forName的方式,而不是采用DriverManager.registerDriver的方式?
1 | public class TestJdbc { |
除了DriverManager.registerDriver
会导致驱动注册两次外,更重要的是,如果使用这种方式,JDBC程序就会依赖于数据库的驱动类(MySQL的Driver类),如果后期程序因数据量和性能原因升级到Oracle数据库,就需要修改程序源代码——重新导入新的驱动类,这会增加很多不必要的麻烦!
而是用Class.forName
方式注册驱动,这样的好处是Jdbc程序不再依赖具体的驱动类,即使删除(或不导入)mysql驱动包,程序依然可以编译(当然不可能运行,因为运行时肯定需要依赖驱动)。
此时类中仅仅是将mysql驱动类的全限定类名写死在程序中(只是一个字符串),可以将这个字符串提取到配置文件中,后期可以通过修改配置文件(而不用修改程序代码)轻松的替换数据库产品。
工厂模式解耦介绍
在实际开发中可以将三层(表现层、业务层、持久层)的对象都使用配置文件配置起来,当启动服务器加载应用时,可以通过工厂读取配置文件,根据配置文件中的配置将这些对象创建出来,在接下来使用的时候,直接拿过来使用即可。
那么,这个负责读取配置文件,根据配置文件创建并返回这些对象的类就是工厂。
可以通过【工厂+接口+配置文件】的方式解除程序中的耦合。
工厂模式解耦示例
解耦程序编写步骤:
1、创建一个Maven的Java工程(day16-spring)
2、创建持久层接口和接口实现类
com.tedu.dao.EmpDao (接口)
com.tedu.dao.EmpDaoImpl (实现类)
3、创建业务层接口和接口实现类
com.tedu.service.EmpService (接口)
com.tedu.service.EmpServiceImpl (实现类)
4、创建表现层测试程序(com.tedu.controller.EmpController)并运行测试程序
5、通过工程+配置文件+接口(已有)方式解耦
(1)创建工厂类(com.tedu.factory.BeanFactory)并实现
(2)提供配置文件,将service接口和dao接口的实现类的全限定类名编写到配置文件中。
6、使用工厂获取service接口和dao接口的实例,替换使用new的方式获取接口的实例。
详细代码如下:
1、创建持久层接口(com.tedu.dao.EmpDao)
1 | package com.tedu.dao; |
2、创建持久层接口实现类(com.tedu.dao.EmpDaoImpl)
1 | package com.tedu.dao; |
3、创建业务层接口(com.tedu.service.EmpService)
1 | package com.tedu.service; |
4、创建业务层接口实现类(com.tedu.service.EmpServiceImpl)
1 | package com.tedu.service; |
5、创建表现层测试类(com.tedu.controller.EmpController)
1 | package com.tedu.controller; |
在上面的程序中,EmpController中要调用Service层的方法,所以通过new对象的形式获取了EmpService接口子类的实例,代码如下:
1 | private EmpService service = new EmpServiceImpl(); |
在EmpService的实现类中要调用Dao层的方法,所以通过new对象的形式获取了EmpDao接口子类的实例,代码如下:
1 | private EmpDao dao = new EmpDaoImpl(); |
如果在上面的程序中将EmpDaoImpl或者EmpServiceImpl移除,会导致其他类中的代码编译错误。此时表现层和业务层,及业务层和持久层之间的依赖程度过高,如果将来替换某一层,很可能会造成其他层无法运行,只能通过修改程序代码保证程序运行,这样依赖就会提高维护成本以及造成不必要的麻烦。
而在程序中new对象的方式造成了这种程序之间的依赖程度提升,即提升了程序之间的耦合性。
使用工厂+配置文件+接口解耦代码如下:
6、创建com.tedu.factory.BeanFactory类,用于创建各个层所需要的对象。
1 | package com.tedu.factory; |
7、在源码目录下创建一个config.properties文件,文件内容配置如下:
1 | EmpService=com.tedu.service.EmpServiceImpl |
8、将EmpController类中通过 “new对象的形式获取了EmpService接口子类的实例” 以及在EmpServiceImpl类中通过 “new对象的形式获取了EmpDao接口子类的实例” 改为使用BeanFactory工厂获取Service和Dao层的实例。如下:
1 | /* 获取Service接口的子类实例 |
1 | /* 获取Dao接口的子类实例 |
Spring IOC控制反转
什么是控制反转
IOC(Inverse Of Control)控制反转,即,把创建对象的权利交给框架。
也就是指将对象的创建、对象的存储、对象的管理交给了spring容器。
(spring容器是spring中的一个核心模块,用于管理对象,底层可以理解为是一个map集合)
0a11c44a30f5a7328589e4031d624ffd
在此之前,当需要对象时,通常是利用new关键字创建一个对象:
1 | /* 获取Service接口的子类实例 |
但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。
而现在我们可以将对象的创建交给框架来做:
1 | /* 获取Service接口的子类实例 |
只需要将类提前配置在配置文件中,就可以将对象的创建交给框架来做。当需要对象时,不需要自己创建,而是通过框架直接获取即可,省去了new对象的过程,自然就降低类和类之间的依赖关系,也就是耦合性。
IOC入门案例
下面将使用spring的IOC解决程序间的耦合
创建工程,引入jar包
创建Maven工程,引入spring相关依赖包
1、创建Maven—Java工程
e38fbdb8819178517de105a52e112a84
2、引入junit、spring的jar包:在maven工程的pom.xml文件的根标签(project)内添加如下配置:
1 | <dependencies> |
导入后保存pom文件,项目如图所示:
ffe9287f31af4c4bf68f5d098b1d2039
创建spring配置文件
创建spring核心配置文件—applicationContext.xml
1、在工程的src/main/resources源码目录下,创建applicationContext.xml文件:
3b521e8ba5d563ec21a2e142fa6cf623
2、在applicationContext.xml中添加文件头信息:
1 |
|
3、将EmpService接口的实现类的实例
以及EmpDao接口的实现类的实例交给Spring容器创建,在核心配置文件中添加如下配置:
1 |
|
创建测试类进行解耦
创建测试类—TestSpring,通过spring的IOC解决程序中的耦合问题
1、创建测试类
30070bf2108c5dcd69ad97c00021d08b
2、测试步骤及代码如下:
1 | public class TestSpring { |
3、运行结果:
96cee091183ae525af0bf5314bf057c1
4、入门案例总结:
这就是spring框架的IOC——控制反转。之前我们自己new对象,例如:
User u = new User();
而现在,变成由一个初始化的xml配置文件来创建,也就是由spring容器来创建。
User user = (User) ac.getBean(“user”);
当程序运行,spring开始工作后,会加载整个xml核心配置文件,读取到,获取到class属性中类的全路径,利用反射创建该类的对象。
Bean对象的单例和多例
Bean对象的单例和多例概述
在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用域。
最常用是singleton(单例)或prototype(多例)。其含义如下:
1) singleton:单实例,是默认值。这个作用域标识的对象具备全局唯一性。
1 | 当把一个 bean 定义设置scope为singleton作用域时,那么Spring IOC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IOC容器中只会创建当前类的唯一一个对象。 |
2) prototype:多实例。这个作用域标识的对象每次获取都会创建新的对象。
1 | 当把一个 bean 定义设置scope为singleton作用域时,Spring IOC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作) |
为什么用单实例或多实例?
之所以用单实例,在没有线程安全问题的前提下,没必要每个请求都创建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态(例如,可改变的成员变量),此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一个:当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),使用多实例,否则单实例;
在Spring中配置Bean实例是单例还是多例方法是:
单例:
1 | <bean id="user" scope="singleton" class="com.tedu.spring.User"> |
多例:
1 | <bean id="user" scope="prototype" class="com.tedu.spring.User"> |
测试spring的单实例和多实例
1、创建TestIOC2类,测试代码如下:
1 | package com.tedu.spring; |
3、将applicationContext.xml中,User类bean标签的scope值设置为singleton:
1 | <bean id="user" scope="singleton" class="com.tedu.spring.User"> |
运行TestIOC2,运行结果为:
4f79acd66228d70451ef21a27911d21b
4、将applicationContext.xml中,User类bean标签的scope值修改为prototype
1 | <bean id="user" scope="prototype" class="com.tedu.spring.User"> |
再次运行TestIOC2,运行结果为:
805f9db5f3eed078dc77876db32298ac
Spring DI依赖注入
两种注入方式介绍
DI(Dependency Injection)依赖注入 。
依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。
如果对象由我们自己创建,这一切都变得很简单,例如:
1 | User user = new User(); |
或者:
1 | User user = new User("韩少云", 18); |
如果对象由spring创建,那么spring是怎么给属性赋值的?spring提供两种方式为属性赋值:
(1).Set方式注入
(2).构造方法注入
set方式注入
普通属性注入
需求:通过Spring创建User实例,并为User实例的name和age属性(普通属性)赋值
1、创建User类,声明name和age属性,并添加对应的setter和getter方法,以及toString方法
1 | package com.tedu.spring; |
2、在applicationContext.xml中声明User类的bean实例
1 | <!-- 声明User类的bean实例 --> |
3、创建测试类—TestDI
1 | package com.tedu.spring; |
由于这里没有为User对象的属性赋值,所以此时运行测试,结果为:
1175a00c572e203e5413862754583a75
4、修改applicationContext.xml中User实例的声明,为User实例注入属性
1 | <!-- 声明User类的bean实例 --> |
其中name属性的值,必须要和User类中所注入属性对应的get方法的名字去掉get后首字母变为小写的名字相同。
例如:为 User类中的age属性赋值,由于name属性对应的get方法名字为getAge,当去调用get和首字母变为小写后的名称为age,因此为age属性注入的配置内容为:
1 | <property name="age" value="20"> |
普通属性直接通过value注入即可。
5、运行测试类TestDI,结果为:
上面通过spring提供的set方式对User对象的属性进行了赋值赋值,所以此时运行测试,结果为:
abda0664c744fdef3e8ece4ea2db2a45
对象属性注入
需求:通过Spring创建User实例,并为User对象的userInfo属性(对象属性)赋值
1、创建UserInfo类
1 | package com.tedu.spring; |
2、在applicationContext.xml中,声明UserInfo类的bean实例
1 | <!-- 声明UserInfo类的bean实例 --> |
3、修改User类,声明userInfo属性,添加对应的setter和getter方法,并重新生成toString方法
1 | public class User { |
4、在applicationContext.xml中,将UserInfo对象作为值,赋值给User对象的userInfo属性
1 | <!-- 声明User类的bean实例 --> |
由于此处是将UserInfo对象作为值赋值给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。
对象属性通过ref属性注入。
5、运行测试类TestDI,结果为:
62c1243d1a13010db1a3c3cd8b116759
构造方法注入
需求:通过Spring创建User对象,并为User对象的属性(name、age、UserInfo属性)赋值
1、为User类声明构造函数
1 | //声明无参构造函数 |
2、修改applicationContext.xml文件,将set方式修改为构造方法注入。
1 | <bean id="user" class="com.tedu.spring.User"> |
其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!
同样的,普通属性直接通过value注入即可;
对象属性通过ref属性注入。
3、运行测试类TestDI,结果为:
4b1ba956af8e1244bd1a30553f4fe6bf
扩展内容
applicationContext.xml没有提示的解决办法
1、配置spring-beans-4.1.xsd文件
(1)找到spring-beans-4.1.xsd的文件的位置,例如:
3526b8f7cb18db7def8c59c6b8f5a8c6
(2)复制下面的url地址:
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
(3)在eclipse菜单栏中: window –> Preferences –> 在搜索框中搜索 [ xml ]
XML –> XML Catalog –> User Specified Entries –> Add…
eef68594c0d7b072e09702b3d6289831
(4)在弹出的窗口中:
10a6f5e3901af4d2fd9cba0d644e00bd
2、配置spring-context-4.1.xsd文件
(1)找到spring-context-4.1.xsd的文件的位置,例如:
f88878c81816bfccdaf534b48ecbf37d
(2)复制下面的url地址:
1 | http://www.springframework.org/schema/context/spring-context-4.0.xsd |
(3)在eclipse菜单栏中: window –> Preferences –> 在搜索框中搜索 [ xml ] XML –> XML Catalog –> User Specified Entries –> Add…
eef68594c0d7b072e09702b3d6289831
(4)在弹出的窗口中:
bab1eb1bf264e64de4a3672c96f23e8b