第一章:Spring核心IOC
1、企业级系统特点
企业级系统
大规模:用户数量多、数据规模大、功能众多(天猫,淘宝,京东,苏宁等)
性能和安全要求高 (用户信息的高安全性,代码的安全性等)
业务复杂(业务复杂度提高,简单的下单可能用到各种业务判断和逻辑,调用各种接口等)
灵活应变(业务不是固定的,需要经常改变业务代码或者实现各种功能,要求快速高效)
EJB框架:重量级框架,服务器端框架,学习EJB的高昂代价,和极低的开发效率,极高的资源消耗,都造成了EJB的使用困难,且不利于项目迁移,不能再主流web容器中运行等
Spring容器取代了原有的EJB容器,因此以Spring框架为核心的应用无须EJB容器支持,可以在Web容器中运行,而可以理解为EJB需要EJB提供的容器才能进行部署。
Spring容器管理的不再是复杂的EJB组件,而是POJO(Plain Old Java Object) Bean。
2、Spring简介
2.1、什么是Spring
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。
框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。
特点:
-
Spring是一个开源框架
-
Spring为简化企业级开发而生,使用Spring开发可以将Bean对象,Dao组件对象,Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发却变得非常的优雅和简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。
-
Spring是一个IOC(DI)和AOP容器框架。
2.2、Spring体系结构
Spring一共有越20个组件,但是真正的核心组件只有几个,我们一起来看下Spring框架的总体架构图,了解一下他的体系结构。
1层次 核心容器
Beans–管理javabean
Core–核心
Context–上下文配置
expression language – 表达式语言
2层次 面向切面
AOP–面向切面编程
Aspects–切面,AOP的框架
3层次 数据访问
jdbc–数据库开发
orm–整合hibernate,包括对mybatis持久化框架的支持
Transactions–事务管理,很重要
Struts–整合Struts框架
4层次
整合Junit单元测试
web–支持web开发,就是后面的MVC框架
Spring框架的核心组件只有三个:Core、Context和Beans。他们构建起了整个Spring的骨骼架构,没有他们就不可能有AOP、Web等上层的特性功能。上面这些是Spring特性功能。
比较重要的几个包:
AOP包(主要提供面向切面编程的实现),
Web(主要提供了Web应用开发的支持及针对Web应用的MVC思想实现) 、
ORM(为我们之前学的Hibernate,以及Mybatis这类持久化框架提供支持)、
还有Spring MVC(这个是它自带的一个web视图层,可以替代到Sturts2,后面还会详细的学习这个SpringMVC框架)。。等等,其中最最核心的就是AOP和下面Spring核心包,也是我们学习的重点。
3、Spring设计理念
Spring是面向Bean的编程,就是面向java实体,没有实体,Spring框架的意义也就没了
Context:Spring的运行环境,代表上下文,负责运行和管理实体对象
Core:Spring的核心,可以看做是提供的各种工具类
比作演出:Beans是演员,Context是舞台环境,Core是服装和道具
Spring 两大核心技术
控制反转(IoC:Inversion of Control )/依赖注入(DI:Dependency Injection )
面向切面编程(AOP:Aspect Oriented Programming)
4、Spring的优良特性
[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API(看是不是必须要实现其接口或者继承某些类)
[2]依赖注入特性将组件关系透明化(对象之间,由IoC容器管理调用),降低了耦合度
控制反转:IOC——Inversion of Control,指的是将对象的创建权交给Spring去创建。使用Spring之前,对象的创建都是由我们自己在代码中new创建。而使用Spring之后。对象的创建都是由给了Spring框架。
依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用setXX方法去设置,而是通过配置赋值。
[3]面向切面编程:Aspect Oriented Programming——AOP,允许将通用任务进行集中式处理,可以将日志处理,事务处理等抽离出来,单独处理
[4]与第三方框架的良好整合,使现有技术更加易用,推进编码最佳实践–写更少的代码实现更多的功能
5、实现spring的helloworld
5.1、 新建java项目
通过官网下载spring的包,地址:https://repo.spring.io/release/org/springframework/spring/
下载对应版本,是dist.zip包,解压后,使用的主要jar包为:
4个核心jar包(beans、core、context、expression对应的jar包,其它的都不需要(doc和source的jar不能放,会报错)) + 1个依赖(commons-loggins…jar,这个是因为core包中做了很多日志相关的东西,日志就用到了这个包里面的内容)
5.2、新建一个lib目录,然后将下载的jar放到lib下面,选中所有,右击添加build path即可
5.3、创建xml配置文件applicationContext.xml,习惯上是这个名字
5.4、新建包,com.kgc.spring.beans,新建Person类,属性name,工具获取getset方法,toString方法,然后编写普通方法sayHi
public class Person {
private String who;
public String getWho() {
return who;
}
public void setWho(String who) {
this.who = who;
}
/**
* 自定义一个普通打招呼的方法
*/
public void sayHi(){
System.out.println("My Name Is :" + who);
}
}
5.5、Spring配置文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
bean标签:声明一个需要spring的IOC容器帮我们创建的实例对象
id属性:实例对象的别名
class属性:指定创建哪个类(JavaBean)的实例对象-类的全类名
-->
<bean id="person" class="com.kgc.spring.beans.Person">
<!-- property子标签:给创建的实例对象中的属性赋值,实际使用反射调用setter方法 -->
<property name="who">
<!-- 可以处理特殊字符(<![CDATA[包含特殊符号内容]]) -->
<value>HelloSpring</value>
</property>
<!-- value属性中如果包含特殊字符,必须转义 -->
<!-- <property name="who" value="HelloWorld"/> -->
</bean>
</beans>
5.6、编写代码获取HelloSpring实例,依赖junit单元测试
public class HelloSpringTest {
@Test
public void helloSpring() {
// 通过ClassPathXmlApplicationContext实例化Spring的上下文IOC容器对象
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过ApplicationContext的getBean()方法,根据id(配置的id对应)来获取bean的实例
Person person = (Person) context.getBean("person");
// 执行print()方法
person.sayHi();
}
}
执行main方法就可以输出内容了
6、什么是IOC
为什么要用IOC:
现有开发创建类的实例是通过new创建的,高耦合,new一个类要了解类所有,且不利于代码扩展和修改
IoC 全称为 Inversion of Control,翻译为 “控制反转”(可以理解为整体思想),它还有一个别名为 DI(Dependency Injection),即依赖注入(定义接收资源的方式,也可以理解为IOC的具体实现)。
整个 IoC 理论的核心:
所谓 IOC ,就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系
如何理解“控制反转”好呢?理解好它的关键在于我们需要回答如下四个问题:
谁控制谁
控制什么
为何是反转
哪些方面反转了
在回答这四个问题之前,我们先看 IOC 的定义:
上面这句话是。如何来理解这句话?我们引用一个例子来走阐述
已找女朋友为例(对于程序猿来说这个值得探究的问题)。一般情况下我们是如何来找女朋友的呢?首先我们需要根据自己的需求(漂亮、身材好、性格好)找一个妹子,然后到处打听她的兴趣爱好、微信、电话号码,然后各种投其所好送其所要,最后追到手
这就是我们通常做事的方式,如果我们需要某个对象,一般都是采用这种直接创建的方式(new BeautifulGirl()),这个过程复杂而又繁琐,而且我们必须要面对每个环节,同时使用完成之后我们还要负责销毁它,在这种情况下我们的对象与它所依赖的对象耦合在一起。
其实我们需要思考一个问题?我们每次用到自己依赖的对象真的需要自己去创建吗?我们知道,我们依赖对象其实并不是依赖该对象本身,而是依赖它所提供的服务,只要在我们需要它的时候,它能够及时提供服务即可,至于它是我们主动去创建的还是别人送给我们的,其实并不是那么重要。再说了,相比于自己千辛万苦去创建它还要管理、善后而言,直接有人送过来是不是显得更加好呢?
这个给我们送东西的“人” 就是 IoC,在上面的例子中,它就相当于一个婚介公司,作为一个婚介公司它管理着很多男男女女的资料,当我们需要一个女朋友的时候,直接跟婚介公司提出我们的需求,婚介公司则会根据我们的需求提供一个妹子给我们,我们只需要负责谈恋爱,生猴子就行了。你看,这样是不是很简单明了。
诚然,作为婚介公司的 IoC 帮我们省略了找女朋友的繁杂过程,将原来的主动寻找变成了现在的被动接受(符合我们的要求),更加简洁轻便。你想啊,原来你还得鞍马前后,各种巴结,什么东西都需要自己去亲力亲为,现在好了,直接有人把现成的送过来,多么美妙的事情啊。所以,简单点说,IoC 的理念就是让别人为你服务
在没有引入 IoC 的时候,被注入的对象直接依赖于被依赖的对象,有了 IoC 后,两者及其他们的关系都是通过 Ioc Service Provider 来统一管理维护的。被注入的对象需要什么,直接跟 IoC Service Provider 打声招呼,后者就会把相应的被依赖对象注入到被注入的对象中,从而达到 IOC Service Provider 为被注入对象服务的目的。所以 IoC 就是这么简单!原来是需要什么东西自己去拿,现在是需要什么东西让别人(IOC Service Provider)送过来
现在在看上面那四个问题,答案就显得非常明显了:
谁控制谁:在传统的开发模式下,我们都是采用直接 new 一个对象的方式来创建对象,也就是说你依赖的对象直接由你自己控制,但是有了 IOC 容器后,则直接由 IoC 容器来控制。所以“谁控制谁”,当然是 IoC 容器控制对象。
控制什么:控制对象。
为何是反转:没有 IoC 的时候我们都是在自己对象中主动去创建被依赖的对象,这是正转。但是有了 IoC 后,所依赖的对象直接由 IoC 容器创建后注入到被注入的对象中,依赖的对象由原来的主动获取变成被动接受,所以是反转。
哪些方面反转了:所依赖对象的获取被反转了。
所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。
工厂模式,类似领养宠物,根据不同的宠物类名或者类型,获取不同的对象,即可以根据不同的对象名称,获取不同的对象实例,被动接受,而非主动创建。
7、WEB实现的示例:
新建项目web项目,选择jdk,tomcat版本后,创建完成,然后项目右击,属性Properties,打开窗口的,Project Facets,然后勾选spring的依赖,如果不是MyEclipse,那就需要自己添加了,使用都一样
1)新建包com.kgc.spring和类Person,跟上面一样
2)编写配置文件
新建source folder — resources(如果直接建普通目录,不能直接读取,必须手动添加)
将applicationContext.xml,移动到resources目录下
文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 配置bean
配置方式: 基于xml的方式 ,使用的全类名的方式.
<bean>: 受Spring管理的一个javaBean对象.
id: <bean>的唯一标识. 在整个IOC容器中唯一不重复.
class: 指定javaBean的全类名. 目的是通过反射创建对象。
Class cls = Class.forName("com.kgc.spring.helloWorld.Person");
Object obj = cls.newInstance(); 必须提供无参数构造器.
<property>: 给对象的属性赋值
name: 指定属性名 , 指定set风格的属性名. 如果将实体中的set方法名改为setWho2,此配置就会直接报错了。
value:指定属性值
-->
<bean id="person" class="com.kgc.spring.Person">
<!-- property元素用来为实例的属性赋值,此处实际是调用setWho()方法实现赋值操作 -->
<property name="name">
<!-- 此处将字符串"Spring"赋值给who属性 -->
<value>HelloSpring</value>
</property>
</bean>
</beans>
3)编写代码获取HelloSpring实例
public class HelloSpringTest {
@Test
public void helloSpring() {
// 通过ClassPathXmlApplicationContext实例化Spring的上下文
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
// 通过ApplicationContext的getBean()方法,根据id(配置的id对应)来获取bean的实例
Person person = (Person) context.getBean("person");
// 执行sayHello方法
person.sayHello();
}
}
4)Spring中IOC容器的实现
1)在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
2)Spring提供了IOC容器的两种实现方式① BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。 ② ApplicationContext:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。
ApplicationContext的主要子接口
ConfigurableApplicationContext
- 是ApplicationContext的子接口,包含一些扩展方法
- refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
WebApplicationContext
- 专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
ApplicationContext的主要实现类
- ClassPathXmlApplicationContext:对应类路径(src)下的XML格式的配置文件(类路径就是编译后的bin路径,可以看编译目录,看文件位置)
- FileSystemXmlApplicationContext:对应文件系统(磁盘中)中的XML格式的配置文件
- 在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
5)bean对象获取方式
方式一:通过ApplicationContext的getBean()方法,根据id(配置的id对应)来获取bean的实例
Person person = (Person) context.getBean("person");
方式二:通过ApplicationContext的getBean()方法,根据具体类型来获取bean的实例
Person person = context.getBean(Person.class);
这个是通过配置文件的配置全类名进行获取实体对象,如果多个同类型的bean,会报错
方式三:通过ApplicationContext的getBean()方法,根据id值+具体类型来获取bean的实例
Person person = context.getBean("person", Person.class);
6)依赖注入的两种方式
1) 通过bean的setter方法赋值
2) 通过bean的构造器赋值
小结:
设值注入和构造注入的区别:
设值注入-习惯使用
通过setter访问器实现
时效性差
灵活性好,但setter方法数量较多
通过无参构造实例化
构造注入
通过构造方法实现
时效性好
灵活性差,仅靠重载限制太多
通过匹配的构造方法实例化,但建议保留无参构造
7)p命名空间注入属性值
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5版本开始引入了一个新的p命名空间,可以通过元素属性的方式配置Bean 的属性。
使用p命名空间后,基于XML的配置方式将进一步简化。
p 命名空间的特点:使用属性而不是子元素的形式配置Bean的属性,从而简化了配置代码,注意是setter注入,不是构造注入。
对于直接量(基本数据类型、字符串)属性:p:属性名="属性值"
对于引用Bean的属性:p:属性名-ref="Bean的id"
8)可以使用的值,即不同数据类型注入,了解
字面量(可以使用字符串标识的值,就叫字面量)
1) 可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
2) 基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
3) 若字面值中包含特殊字符,可以使用<![CDATA[]]>把字面值包裹起来
9)引用其他的bean
配置文件中,引入已经存在的bean,比如Person中,存在car的实体类型属性
<!-- 引用其他的bean,这个只能是IOC容器内的,不能找外面的 -->
<bean id="person" class="com.kgc.spring.Person">
<!-- car不能使用value了,使用ref关联最初的那个配置car -->
<property name="car" ref="car"></property>
</bean>
1、内部bean的使用
修改spring配置文件
<!-- 内部bean -->
<bean id="person1" class="com.kgc.spring.di.Person">
<property name="car">
<!-- 内部bean 只能 在内部使用,没有必要配置id了. -->
<bean class="com.kgc.spring.di.Car">
<property name="brand" value="Mini"></property>
<property name="crop" value="宝马"></property>
<property name="price" value="300000"></property>
</bean>
</property>
</bean>
2、给级联属性赋值
还是在Person对象中引用其他bean汽车Car的第一个示例,<bean>标签内的
<property name="car" ref="car"></property>
后增加如下配置
<!-- 给级联属性赋值-->
<property name="car.speed" value="280"></property>
再运行单元测试testRef,看结果,新的值就有了
10)集合属性注入
在Spring中可以通过一组内置的XML标签来配置集合属性,例如:,或
1)数组和List
配置java.util.List类型的属性,需要指定<list>标签,在标签里包含一些元素。
这些标签
可以通过<value>指定简单的常量值,通过<ref>指定对其他Bean的引用。
通过<bean>指定内置bean定义。通过<null/>指定空元素。甚至可以内嵌其他集合。
数组的定义和List一样,都使用<list>元素。
配置java.util.Set需要使用<set>标签,定义的方法与List一样。
<property name="list">
<!-- 构造集合 -->
<list>
<!--<bean>
如果没有,可以使用内部bean类写多个 <ref/>如果是事先有了注入的bean了,就可以直接指向
-->
<ref bean="bean1"/>
<ref bean="bean2"/>
<ref bean="bean3"/>
</list>
<!-- <array></array> 这个不建议使用,直接用list即可 ,既可以给list,也可以给数组赋值-->
<!-- <set></set> -->
</property>
2)Map集合
Java.util.Map通过<map>标签定义,<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。
必须在<key>标签里定义键。
因为键和值的类型没有限制,所以可以自由地为它们指定<value>、<ref>、<bean>或<null/>元素。
当然也可以将Map的键和值作为<entry>的属性定义:简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义。
<!-- Map集合 -->
<bean id="map" class="类全类名">
<property name="map" >
<!-- 构造map -->
<map>
<entry key="AA" value-ref="bean1"></entry>
<entry key="BB" value-ref="bean2"></entry>
<entry key="CC" value-ref="bean"></entry>
</map>
</property>
</bean>
3)集合类型bean
按上面类内引用的集合对象(如personList的配置),如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要 将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间
修改spring配置文件spring-di.xml,增加头信息引入util命名空间,加载在beans标签内,跟beans类似
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd
配置内容
<!-- 集合Bean -->
<util:list id="listBean">
<ref bean="bean1"/>
<ref bean="bean2"/>
<ref bean="bean3"/>
<!-- <bean>
</bean> -->
</util:list>
使用:
<property name="list" ref="listBean"></property>
11)工厂bean的使用
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
BeanFactory 与 FactoryBean的区别?,这二者没有任何关系,相当于两个概念
类似于:
Collection 与 Collections 的区别
Final finally 的区别
12)bean的高级配置:bean与bean的关系,了解
13)bean的作用域 *
在Spring中,可以在元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
<!--
bean的作用域:
singleton: 单例的(默认值), 在整个IOC容器中只能存在一个bean的对象. 而且在IOC
容器对象被创建时,就创建单例的bean的对象. 后续每次通过getBean()方法
获取bean对象时,返回的都是同一个对象.
prototype: 原型的/多例的 在整个IOC容器中可有多个bean的对象。 在IOC容器对象被
创建时, 不会创建原型的bean的对象。 而是等到每次通过getBean()方法获取
bean对象时,才会创建一个新的bean对象返回.
request: 一次请求对应一个bean对象,只在web环境中有效
session: 一次会话对应一个bean对象,只在web环境中有效
-->
13)bean的生命周期
1) Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
2) Spring IOC容器对bean的生命周期进行管理的过程:
① 通过构造器或工厂方法创建bean实例
② 为bean的属性设置值和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,调用bean的销毁方法
3) 在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
14)bean的后置处理器
1) bean的后置处理器
① bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
② bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型
应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
③ bean后置处理器时需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
postProcessBeforeInitialization(Object, String)
postProcessAfterInitialization(Object, String)
2) 添加bean后置处理器后bean的生命周期变为7个了
①通过构造器或工厂方法创建bean实例
②为bean的属性设置值和对其他bean的引用
③将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
④调用bean的初始化方法
⑤将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
⑥bean可以使用了
⑦当容器关闭时调用bean的销毁方法
15)使用spring配置数据源连接池
c3po(连接池管理包c3p0-0.9.1.jar)和mysql的连接数据库驱动包加入到项目中
增加配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 直接配置c3p0连接池 ComboPooledDataSource 连接池对象 -->
<bean id ="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
<property name="initialPoolSize" value="5"></property>
<property name="maxPoolSize" value="10"></property>
</bean>
</beans>
注意:
使用外部properties文件
key要加上一个前缀,防止出错,因为spring加载配置的时候,会将配置的解析信息放到跟电脑的系统变量一起,可能导致获取的值是系统的,而不是配置的,要注意。
修改spring的配置文件,注释掉直接配置方式,增加如下内容
<!-- 通过引入外部的属性文件配置c3p0连接池 -->
<!-- 引入外部的属性文件 -->
<!-- 推荐写法:增加context命名空间-->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
8、Spring的自动装配
1)xml装配bean
自动装配的概念
手动装配:以value或ref的方式明确指定属性值都是手动装配。
自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。
装配模式
1)根据类型自动装配:将类型匹配的bean作为属性注入到另一个bean中。若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定哪个bean最合适该属性,不能执行自动装配。
byType: 使用bean的属性的类型与IOC容器中<bean>的class进行匹配。 如果唯一匹配则装配成功,如果匹配到多个兼容类型的bean,则抛出异常。
2)根据名称自动装配:必须将配置的目标bean的id名称和类的属性名设置的完全相同
byName: 使用bean的属性名与IOC容器中<bean>的id值进行匹配. 匹配成功则装配成功. 不成功则不装配。
建议:
在项目中更多的使用注解的方式实现,xml配置的方式基本不用,了解原理即可。
2)注解装配bean:
相对于XML方式,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式。
常用注解:
1) 普通组件:@Component
标识一个受Spring IOC容器管理的控制层组件
2) 持久化层组件:@Repository
标识一个受Spring IOC容器管理的持久化层组件
3) 业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
4) 控制层/表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
5) 组件命名规则
①默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
②使用组件注解的value属性指定bean的id
注意:
事实上Spring并没有能力识别一个组件到底是不是它所标记的类型
即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误
所以@Respository、@Service、@Controller这几个注解
仅仅是为了让开发人员自己明确当前的组件扮演的角色,在使用上基本是没有区别的。
--------------------------配置组件扫描-------------------------
<!--
组件扫描: 扫描加了注解的类,并管理到IOC容器中
base-package: 基包. Spring会扫描指定包以及子包下所有的类,将带有注解的类管理到IOC容器中
-->
<context:component-scan base-package="com.kgc.spring.annotation"></context:component-scan>
--------------------------指定扫描和排除扫描-------------------------
如果想要某个组件不让IOC管理,spring也提供了对应的处理
------指定扫描------
<!-- 指定扫描
设置父属性:use-default-filters="false" -->
<context:component-scan base-package="com.kgc.spring.annotation" use-default-filters="false">
增加context:component-scan的子节点:
<!-- 按照注解方式进行指定,注意type和expression(注解的全类名) -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
或者
<!-- 按照指定类进行指定,注意type和expression(具体类的全类名) -->
<context:include-filter type="assignable" expression="com.kgc.spring.annotation.controller.xxxController"/>
------排除扫描------
<!-- 排除扫描
设置父属性:use-default-filters="true" 这个是默认值 -->
<context:component-scan base-package="com.kgc.spring.annotation" use-default-filters="true">
<!-- 按照注解方式进行指定,注意type和expression(注解的全类名) -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
或者
<!-- 按照指定类进行指定,注意type和expression(具体类的全类名) -->
<context:exclude-filter type="assignable" expression="com.kgc.spring.annotation.controller.xxxController"/>
-----------使用注解自动装配:@Autowired注解-----------
IOC的特点就是,同一个容器中的对象,我只要告诉当前bean对象,需要谁,然后就自动给我了,所以这个时候要使用一个新的主键@Autowired
-----------@Autowired注解的工作机制-----------
/**
* @Autowired 完成bean属性的自动装配
*
* 工作机制:
1)首先会使用byType的方式进行自动装配,如果能唯一匹配,则装配成功, 如果匹配到多个兼容类型的bean,还会尝试使用byName的方式进行唯一确定,如果能唯一确定,则装配成功,如果不能唯一确定,则装配失败,抛出异常.
*
* 2)默认情况下, 使用@Autowired标注的属性必须被装配,如果装配不了,会抛出异常.
*
3)可以使用required=false来设定不是必须要被装配.
*
* 4)如果匹配到多个兼容类型的bean,可以使用@Qualifier来进一步指定要装配的bean的id值。
*
* 5)@Autowired @Qualifier 注解即可在成员变量上,也可以加在对应的set方法上。
*
*/
9、详细案例见课堂项目代码
第二章:Spring核心AOP
1、AOP的定义和原理
AOP的目标:让我们可以“专心做事”
AOP原理
将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
采用代理机制组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能
前奏,问题引入
情景:数学计算器
1) 要求
①执行加减乘除运算
②日志:在程序执行期间追踪正在发生的活动(举例,线上出问题不能停业务的)
2) 代码示例
新建包:com.kgc.spring.aop.before
新建计算器接口
/**
* 算数计算器
*
*/
public interface ArithmeticCalculator {
public int add(int a ,int b );
public int sub(int a, int b );
public int mul(int a ,int b );
public int div(int a, int b );
}
创建实现类
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override
public int add(int a, int b) {
System.out.println("日志处理---->这个是加法运算开始,参数[" + a + "," + b + "]");
int result = a + b;
System.out.println("日志处理---->这个是加法运算结束,结果[" + result + "]");
return result;
}
@Override
public int sub(int a, int b) {
System.out.println("日志处理---->这个是减法运算开始,参数[" + a + "," + b + "]");
int result = a - b;
System.out.println("日志处理---->这个是减法运算结束,结果[" + result + "]");
return result;
}
@Override
public int mul(int a, int b) {
System.out.println("日志处理---->这个是乘法运算开始,参数[" + a + "," + b + "]");
int result = a * b;
System.out.println("日志处理---->这个是乘法运算结束,结果[" + result + "]");
return result;
}
@Override
public int div(int a, int b) {
System.out.println("日志处理---->这个是除法运算开始,参数[" + a + "," + b + "]");
int result = a / b;
System.out.println("日志处理---->这个是除法运算结束,结果[" + result + "]");
return result;
}
}
分析:
1)代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
2)代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码
3)代码维护难:如果需要修改日志处理。如果日志需求发生变化,必须修改所有模块。
4)硬编码:为了写代码而写代码,没有任何设计而言
5)内容耦合:功能和日志就应该是相互区分开的,将日志内容强制加入业务(但是要注意,如果是业务内部,需要增加具体的日志信息,还是需要特别处理的,跟aop没有关系)
动态代理的原理
代理设计模式的原理:使用一个代理对象将原始对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
比如:身边的微商,真正商家卖东西的就是原始对象,而代理商只是增加了宣传,也可以理解为增强处理
AOP的概述
1) AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传
统 OOP(Object-Oriented Programming,面向对象编程)的补充。
2) AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点(可以理解为功能)。
在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
3) AOP的好处:
① 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
② 业务模块更简洁,只包含核心业务代码
2、AOP的相关术语
增强(Adivce):又翻译成通知,定义了切面是什么以及何时使用,描述了切面要完成的工作和何时需要执行这个工作。是织入到目标类连接点上的一段程序代码。增强包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息。(所以spring提供的增强接口都是带方位名:BeforeAdvice、(表示方法调用前的位置)AfterReturninAdvice、(表示访问返回后的位置)ThrowAdvice等等,所以只有结合切点和增强两者一起才能确定特定的连接点并实施增强逻辑)
切入点(Pointcut):Advice定义了切面要发生“故事”和时间,那么切入点就定义了“故事”发生的地点。例如某个类或者方法名,Spring中允许我们使用正则来指定。
连接点(Joinpoint):切入点匹配的执行点称作连接点。如果说切入点是查询条件,那连接点就是被选中的具体的查询结果(具体的数据记录)。简单记忆,就是类中的各个方法
切面(Aspect):切点和增强组成切面,是封装了各个抽离的功能点的类。Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中
代理(Proxy):AOP框架创建的对象。一个类被AOP织入增强之后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。
目标对象(Target):增强逻辑的织入的目标类
织入(Weaving):将增强添加到目标类具体连接点上的过程。AOP有三种织入的方式:编译期织入、类装载期织入、动态代理织入(spring采用动态代理织入)
更好理解:在一个多或多个连接点上,可以将切面的功能(advice)织入到程序的执行过程中
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。Joint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。Advice
(增强):Advice 定义了在Pointcut
里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Target
(目标对象):织入Advice
的目标对象.。Weaving
(织入):将Aspect
和其他对象连接起来, 并创建Advice
d object 的过程
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
来让我们看一下上面的一个小故事和 AOP 到底有什么对应关系.
首先我们知道, 在 Spring AOP 中 Joint point
指代的是所有方法的执行点, 而 point cut 是一个描述信息, 它修饰的是 Joint point
, 通过 point cut, 我们就可以确定哪些 Joint point
可以被织入 Advice
. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point
就相当于 爪哇的小县城里的百姓,pointcut
就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice
则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问.
为什么可以这样类比呢?
Joint point
: 爪哇的小县城里的百姓: 因为根据定义,Joint point
是所有可能被织入Advice
的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是Joint point
. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人.Pointcut
:男性, 身高约七尺五寸: 我们知道, 所有的方法(joint point) 都可以织入Advice
, 但是我们并不希望在所有方法上都织入Advice
, 而Pointcut
的作用就是提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加Advice
. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问.Advice
:抓过来审问,Advice
是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于 point cut 所限定的那些Joint point
上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓.Aspect
::Aspect
是point cut
与Advice
的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个Aspect
.
Advice 的类型
before advice
, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)after return advice
, 在一个 join point 正常返回后执行的 adviceafter throwing advice
, 当一个 join point 抛出异常后执行的 adviceafter(final) advice
, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.around advice
, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.introduction
,introduction可以为原有的对象增加新的属性和方法。
说明:
切面可以理解为由增强处理和切入点组成,既包含了横切逻辑的定义,也包含了连接点的定义。面向切面编程主要关心两个问题,即:在什么位置,执行什么功能。Spring AOP是负责实施切面的框架,即由Spring AOP完成织入工作。
用这个能做哪些事情:
1.Spring声明式事务管理配置。
2.Controller层的参数校验。
3.在执行方法前,判断是否具有权限。
4.对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。
5.信息过滤,页面转发等等功能
2.1、简介
AspectJ:Java社区里最完整最流行的AOP框架。
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
2.2、基本用法
2.2.1、核心配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 组件扫描 -->
<context:component-scan base-package="com.kgc.spring.annotation"></context:component-scan>
<!-- 基于注解使用AspectJ: 主要的作用是当Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为 与AspectJ切面匹配的bean创建代理. 默认使用jdk的动态代理,如果要使用cglib的,必须proxy-target-class="true" -->
<aop:aspectj-autoproxy/>
</beans>
2.2.2、用AspectJ注解@Aspect声明切面
要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。
当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创建代理。
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。
通知是标注有某种注解的简单的Java方法。
AspectJ支持5种类型的通知注解:
① @Before:前置通知,在方法执行之前执行 ② @After:后置通知,在方法执行之后执行 ③ @AfterRunning:返回通知,在方法返回结果之后执行 ④ @AfterThrowing:异常通知,在方法抛出异常之后执行 ⑥ @Around:环绕通知,围绕着方法执行,可以简单连理为前置,后置,返回和异常的结合体
2.2.3、切入点表达式
* * com.kgc.spring.annotation.*.*(..)
* 第一个 * : 任意修饰符 任意返回值
* 第二个 * : 任意类
* 第三个 * : 任意方法
* .. : 任意参数列表
这里面如果要获取方法名和参数,那就用到一个连接点对象: JoinPoint(org.aspectj.lang包下的)
2.2.4、切面执行优先级
通过注解@Order(值)设置切面的优先级
默认int类型最大值 2147483647 值越小优先级越高
2.2.5、重用切入点表达式
在切面类中,随便定义一个方法,如下
/**
* 声明切入点表达式
*/
@Pointcut("execution(* com.kgc.spring.aspectJ.annotation.*.*(..))")
public void declarePointCut() {}
使用如下
@After("declarePointCut()")
或
@AfterReturning(value="declarePointCut()",returning="result")
如果想要在其它切面中也使用,不能直接用,必须如下写
@Before("声明了切入点表达式的切面.declarePointCut()")
这样就可以了
3、用xml的方式配置切面-了解,不常用
3.1、概述:
除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的。
正常情况下,基于注解的声明要优先于基于XML的声明。通过AspectJ注解,切面可以与AspectJ兼容,而基于XML的配置则是Spring专有的。由于AspectJ得到越来越多的 AOP框架支持,所以以注解风格编写的切面将会有更多重用的机会。
3.2、配置细节
在bean配置文件中,所有的Spring AOP配置都必须定义在aop:config元素内部。对于每个切面而言,都要创建一个aop:aspect元素来为具体的切面实现引用后端bean实例。
切面bean必须有一个标识符,供aop:aspect元素引用。
3.3、简单示例
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 目标对象 -->
<bean id="arithmeticCalculatorImpl" class="com.kgc.spring.aspectJ.xml.ArithmeticCalculatorImpl"></bean>
<!-- 切面 -->
<bean id="loggingAspect" class="com.kgc.spring.aspectJ.xml.LoggingAspect"></bean>
<bean id="validationAspect" class="com.kgc.spring.aspectJ.xml.ValidationAspect"></bean>
<!-- AOP: 切面 通知 切入点表达式 -->
<aop:config>
<!-- 切面 -->
<aop:aspect ref="loggingAspect" order="2">
<!-- 切入点表达式 -->
<aop:pointcut expression="execution(* com.kgc.spring.aspectJ.xml.*.*(..))" id="myPointCut"/>
<!-- 通知 -->
<aop:before method="beforeMethod" pointcut-ref="myPointCut"/>
<aop:after method="afterMethod" pointcut-ref="myPointCut"/>
<aop:after-returning method="afterReturningMethod" pointcut-ref="myPointCut" returning="result"/>
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="myPointCut" throwing="ex"/>
<!-- 注释掉,它是上面的合体,所以不用并存 -->
<!-- <aop:around method="aroundMethod" pointcut-ref="myPointCut"/> -->
</aop:aspect>
<aop:aspect ref="validationAspect" order="1">
<aop:before method="beforeMethod" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
对于around和afterThrowing通知,要想二者兼容,在around的通知里将异常抛出而非打印:
throw new RuntimeException(throwable);
// throwable.printStackTrace();
4、Spring的事务管理
4.1、事务概述
- 在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
- 事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
- 事务的四个关键属性(ACID)
原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
4.2、事务管理的分类:
编程式事务管理
1) 使用原生的JDBC API进行事务管理
①获取数据库连接Connection对象
②取消事务的自动提交
③执行操作
④正常完成操作时手动提交事务
⑤执行失败时回滚事务
⑥关闭相关资源
2) 评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型 的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务 的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务 管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
声明式事务管理
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
Spring既支持编程式事务管理,也支持声明式的事务管理。
4.3、Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
事务管理器的主要实现
1) DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
2) JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理,基本不用
3) HibernateTransactionManager:用Hibernate框架存取数据库,不涉及
4.4、配置spring数据源
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.kgc.spring.tx.annotation"></context:component-scan>
<!-- 数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
4.5、配置spring的事务管理器
<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 管住事务就要先管住数据源连接 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解
transaction-manager 用来指定事务管理器, 如果事务管理器的id值 是 transactionManager,可以省略不进行指定。
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
4.6、Spring事务注解用法
@Transactional注解支持两种方式
一种是在方法上,只对当前方法生效,优先级高于类上面的
一种是在类上面,对当前类中所有的方法都起作用
4.7、事务属性:
/**
*
* 1. 事务的传播行为 propagation: 一个事务方法被另外一个事务方法调用时,当前的事务如何使用事务.
* Propagation.REQUIRED 默认值. 使用调用者的事务(全程一个事务).
* Propagation.REQUIRES_NEW 将调用者的事务挂起, 重新开启事务来使用.
如:@Transactional(propagation=Propagation.REQUIRES_NEW)
* 2. 事务的隔离级别 isolation
* 1 读未提交 脏读(读取别的事务没有提交的数据)
* 2 读已提交 不可重复读(两次读取的数据不一致)
* 4 可重复读(mysql默认) 幻读(两次读取一次没数据,后来又有数据)
* 8 串行化 效率低。
* 3. 事务的回滚与不回滚 默认情况下, Spring会对所有的运行时异常进行事务回滚.
* rollbackFor
* rollbackForClassName
* noRollbackFor
* noRollbackForClassName
* 4. 事务的只读设置:
* readOnly
* true: 只读 代表着只会对数据库进行读取操作, 不会有修改的操作.(只是告诉Spring而已,但是仍然可以做写操作,不建议有修改操作,如果加了这个,spring对修改是不会自动加锁了)
* 如果确保当前的事务只有读取操作,就有必要设置为只读,可以帮助数据库
* 引擎优化事务
* false: 非只读 不仅会读取数据还会有修改操作。
* 5. 事务的超时设置: 设置事务在强制回滚之前可以占用的时间.
* timeout: 秒为单位
*
*/