文章目录
- 前言
- 一、
- Spring框架的优势
- 二、 Spring的使用流程
- Spring的依赖注入
- bean后处理器
- 容器后处理器
- FileSystemResource类
- Spring的AOP机制
- AOP基础知识
- AOP应用案例
- Spring的事务管理
- 事务控制参数
- 事务的隔离级别
- 总结
前言
学习完了Spring框架,总结回顾一下。
一、
Spring框架的组成结构:
Spring致力于Java EE开发整体的解决方案,贯穿表现层、业务层、持久层,为企业的应用开发提供了一个轻量级的解决方案。其中包括:基于依赖注入的核心机制,基于AOP的声明式事务管理,与多种持久层技术的整合,以及优秀的Web MVC框架等。Spring框架的优点如下:
- 低侵入式设计,代码的污染极低。
- 独立于各种应用服务器。
- Spring的DI(Dependcy Injection)容器降低了业务对象替换的复杂性,提高了组件之间的解耦。
- Spring的AOP(Aspect Oriented Programming)机制支持通用任务的集中式管理。
- Spring的ORM(Object Relational Mapping)和DAO(Data Access Object)提供了与第三方持久层框架的良好整合,并简化了底层数据库访问流程。
- Spring的开放性良好,开发者可自由选用Spring框架的部分或全部。
Spring框架相当于一个巨大的工厂,可以把项目中所用到的所有Java Bean管理起来。开发者只需要把所用到的Bean配置到Spring容器中,就可以采用统一的方式来访问Bean的相应功能。
另外,Spring采用反射的方式,在运行时动态地生成相应的Bean。这种方式极大地解耦了程序,使得程序的可维护性大大提升。Spring框架的结构图如下所示。
从上图中可以看出,Spring框架的组成主要包括以下部分:
核心容器
- Spring 上下文
- Spring AOP
- Spring DAO
- Spring ORM
- Spring Web模块
- Spring MVC框架
Spring框架的优势
统一的容器管理模式,提高了开发效率和代码可维护性。
统一的事务管理模式,提高了代码重用度
二、 Spring的使用流程
通过一个简单的例子,分步骤来说明Spring容器的使用流程。
1、编写一个Java类的Person。代码如下:
这个类定义了一个name属性,并且为这个属性设置了set方法、information方法,最后输出这个人的名字。由此可以看出,这是一个很普通的Java类。
- 给Spring容器导入Bean方法
在applicationContext.xml文件中添加如下代码:
在上面的代码中,把前面定义的Person类作为被Spring容器管理的Java Bean。在配置文件中, <bean>
指定了id、class两个属性。其中,id属性是用来唯一标识这个bean,class属性则指定了bean的路径。在<bean>
标签中,含有一个<property>
子标签,定义了要被管理的属性。上述代码把Person类中属性name的值是设置为“张三”。
3.加入spring context的maven依赖
测试
下面来编写一个主程序,对上面定义的Java类和配置文件通过Spring框架提供的接口进行测试,代码如下:
public static void main(String[]args){//读取Spring配置文件
ApplicationContextact=new ClassPathXmlApplicationContext("applicationContext.xml");
//从Spring容器中获取id为p1的beane
Person pl=act.getBean("p1",Person.class);↵
pl.information();
}↵
运行结果
17:46:17.092 [main]DEBUG
org.springframework.context.support.ClassPathXmlApplicationContext -Refreshing
org.springframework.context.support.ClassPathXmlApplicationContext@504bae78
17:46:17.211 [main]DEBUG
org.springframework.beans.factory.xml.XmlBeanDefinitionReader -Loaded 1 beandefinitions from class path resource [applicationContext.xml]
17:46:17.234 [main]DEBUG
org.springframework.beans.factory.support.DefaultListableBeanFactory -Creatingshared instance of singleton bean 'p1'
这个人的名字是:张三
由上述程序可以看出,Spring程序的使用流程和开发者以前的使用流程大不相同。按照以前的使用流程,当需要一个Bean的实例时,可以通过new关键字创建一个Bean实例。使用Spring框架后,需要Bean实例时,不是直接创建,而是从Spring容器中根据id创建Bean。对于Spring框架来说,会自动初始化一个Bean实例,然后根据Java的反射机制,调用相应属性的set方法给属性赋值。此种执行过程叫做控制反转(IoC),也叫依赖注入(DI)。
- Spring框架默认的配置文件为applicationContext.xml。该文件一般放在src的根目录下,系统可以自动加载该文件。
- 开发者可以在
applicationContext.xml
中定义各种Java Bean,这些Java Bean可以被Spring容器统一管理。
<?xml version="1.0"encoding="UTF-8"?>4
<beans xmlns="http://www.springframework.org/schema/beans"+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"↵
xsi:schemaLocation="http://www.springfiamework.org/schema/beansuhttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="paperCup"class="dps.bean.PaperCup">
<propertyname="color"value="白"/>
</bean>
<bean id="glassCup"class="dps.bean.GlassCup">
<propertyname="color"value="黑"/>
</bean>
<beanid="chinese class="dps.bean.Chinese">
<constructor-arg value="李四"index="0"/>
<constructor-arg ref="glassCup"index="1"/>
</bean>↵
<!--其他JavaBean的配置-->
</beans>↵
Spring的配置文件可以重命名,也可以放在其他位置。此时,如果是Web项目,就需要在web.xml中指明需要加载的配置文件的具体位置,如下面的代码所示:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>4/WEB-INF/bean.xml,WEB-INF/bean2.xml,</param-value>
</context-param>
如果是Web项目,还需要在web.xml文件中添加如下代码,表示在Web项目汇总要使用Spring框架。监听Spring依赖jar包的引入
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListenere</listener-class>
</listener>
这样,在Web项目启动的时候,就可以初始化Spring容器以及Spring容器中相应的Bean,以便项目的其他Web组件使用。
如果项目的规模比较大,可以给每个模块创建一个配置文件。通过这种方式,编码时逻辑比较清晰,也更利于提高项目的可维护性和可扩展性。
Spring的依赖注入
- Spring框架的核心功能之一就是通过依赖注入来管理Bean之间的依赖关系。
- 所谓的依赖注入(DI),是指程序运行过程中,如果需要另一个对象协作时,无须在代码中创建被调用对象,而是通过容器自动创建被调用者对象。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。
- 通过依赖注入,可以保留抽象接口,让组件依赖于抽象接口。当组件要与其它实际的对象发生依赖关系时,需要通过抽象接口来注入依赖的实际对象。以往设置对象属性都通过Java类中的set方法实现,而Spring不是这样,它是通过其配置文件设置的。
在依赖注入模式下,因为创建被调用者的工作不再由调用者来完成,所以也称为控制反转。创建被调用者的实例的工作通常由Spring容器来完成,然后注入调用者。依赖注入的属性类型可以是系统类型,也可以是用户自定义类型。依赖注入有两种方式:
设值注入
:IoC容器使用属性的setter方法来注入被依赖的实例。构造注入
:IoC容器使用构造器来注入被依赖的实例。
下面以一个较为复杂的例子,来介绍这两种注入方式的不同。注意,这里要注入的属性类型是用户自定义类型。
设值注入
假定,人有说话和喝水的功能,人又分为中国人和美国人。则可以定义IPerson接口和ICup接口,其代码分别如下:
public interface IPerson {
//说话↵
public voidsayHello();↵
//喝水↵
public void drink();
}↵
public interface ICup{↵//杯子可以装水↵
public void fillWater();↵
}↵
定义两种杯子,纸杯和玻璃杯,代码分别如下:
//纸杯↵
public class PaperCup implements ICup{/杯子的颜色↵
private Sting color↵
public void setColor(Sting color){this.color=color:
}
@Ovemide+
public void fillWaterO){+
System.out.println(使用"+this.color+"颜色的纸杯喝水。"):}
}
//玻璃杯↵
public class GlassCup implements ICup {
/杯子的颜色↵
private Sting color
}↵
public void setColor(String color){+
this.color=color₃↵
}↵
@Ovemide+
public void fillWaterO){+
System.out.println("使用"+this.color+"颜色的玻璃杯喝水。"):}↵
接着定义两种人,中国人和美国人,代码分别如下:
/中国人
public class Chinese implements IPerson{↵private Sting namez↵private ICup cup:↵
public String getName0{+retumname₂e
}↵
public void setName(String name){this name=namez↵
}↵
public ICup getCupO{+
retum cup:↵
}↵
public void setCup(ICup cup){↵
this.cup=cupk
}↵
@Ovemide+
public void sayHello0
{
System.out.println(name+"说,你好"):
}↵
@Ovemide+
public void drink0{
this.cup.fillWaterO:↵
}↵
州
//美国人↵
public class American implements IPerson {
private String nameze
privateICupcup:↵
public String getName0{+
retumname;
}↵
public void setName(String name){
this name=namez↵
}↵
public ICup getCupO{↵
retum cup:↵
}↵
public void setCup(ICup cup){↵
this.cup=cup:↵
}↵
public void sayHello(↵
{
System.out.println(name+"say.hello.")
}↵
@Ovemide+
public void drink0{↵
this.cup.fillWaterO:↵
}↵
}+
定义设值注入的配置文件,如下所示:
<bean id="paperCup"cass="dps bean PaperCup">property name="color"value="白">
</bean>4
<bean id="glassCup"class="dps.bean.GlassCup">propertyname="color"value="黑"
<bean>4
<beanid="chinese"class="dps.bean.Chinese"><property name="name"value=张三"sproperty name="cup"ref="glassCup">↵
</bean>
最后,编写一个测试程序进行验证,代码如下:
public static void main(String[]args){/读取Spring配置文件
ApplicationContextact act = new ClassPathXmlApplicationContext("applicationContext.xml");
//从Spring容器中获取id为p1的beane
IPersonchinese=act.getBean("chinese",IPerson.dass);
chinese.sayHello0:↵
chinese.drink():
}
运行结果如下:
设值注入
上述代码及运行结果可以看出,设值注入实际是调用相应属性的set方法,来达到赋值的目的。对于设值注入的全部流程,可以总结如下:
- 被依赖的属性需要定义
set
方法。 - 配置文件中使用
<property>
标签。如果是简单属性,就使用value赋值;如果是复杂属性,就使用ref赋值。
构造注入
对于上述代码,如果采用构造注入,就要修改Chinese类以及相应的配置文件。修改后的Chinese类代码如下:
/中国人
public class Chinese implements IPerson{↵private Sting namez↵private ICup cup:↵
public String getName0{+retumname₂e
}↵
public void setName(String name){this name=namez↵
}↵
public ICup getCupO{+
retum cup:↵
}↵
public void setCup(ICup cup){↵
this.cup=cupk
}↵
@Ovemide+
public void sayHello0
{
System.out.println(name+"说,你好"):
}↵
@Ovemide+
public void drink0{
this.cup.fillWaterO:↵
}↵
州
构造注入
修改后的配置文件如下:
<beanid="paperCup"class="dps.bean PaperCup"><property name="color"value="白"><bean>4
<bean id="glassCup"class="dps.bean.GlassCup">property name="color"value="黑">
<bean>
↵
测试代码不变,运行结果如下:
从修改后的代码及运行结果可以看出,构造注入实际通过配置文件调用相应类的构造方法,实现属性赋值的目的。
- 对于设值注入方式,具有以下使用特性:
- 对于习惯了传统Java Bean开发的程序员而言,通过setter方法设定依赖关系显得更加直观、自然。
- 如果依赖关系(或继承关系)较为复杂,那么构造子注入模式的构造函数也会相当庞大(需要在构造函数中设定所有依赖关系),此时设值注入模式往往更为简洁。
- 对于某些第三方类库而言,可能要求组件必须提供一个默认的构造函数(如Struts中的Action),此时构造注入类型的依赖注入机制就体现出其局限性,难以完成期望的功能。
对于构造注入方式,具有以下使用特性:
- “在构造期即创建一个完整、合法的对象”,对于这条Java设计原则,构造注入无疑是最好的响应者。
- 避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,更加易读。
- 由于没有setter方法,依赖关系在构造时由容器一次性设定,因此组件在被创建之后即处于相对“不变”的稳定状态,无需担心上层代码在调用过程中执行setter方法对组件依赖关系造成破坏,特别是对于Singleton模式的组件而言,这可能对整个系统产生巨大的影响。
- 同样,由于关联关系仅在构造函数中表达,因此只有组件创建者需要关心组件内部的依赖关系。对调用者而言,组件中的依赖关系处于黑盒之中。这样一来,不仅对上层屏蔽不必要的信息,而且为系统的层次清晰性提供了保证。
- 通过使用构造注入,意味着可以在构造函数中决定依赖关系的注入顺序。对于一个大量依赖外部服务的组件而言,依赖关系的获得顺序可能非常重要,比如某个依赖关系注入的先决条件是组件的DataSource及相关资源已经被设定。
一般来说,建议采用以设值注入为主,构造注入为辅的注入策略。
对于依赖关系无须变化的注入,尽量采用构造注入;而其它依赖关系的注入,则考虑采用设值注入。
Spring框架中提供了大量的注释配置功能,能完成和传统XML文件相同的功能。一般来说,注释配置相对于XML配置具有很多的优势。
- 更充分利用Java的发射机制
- 注释配置可以充分利用Java的反射机制获取类结构信息,这些信息可以有效减少配置的工作。 能增强程序的内聚性
- 注释和Java代码位于一个文件中,而XML配置采用独立的配置文件。
- 在很多情况下,注释配置比XML配置更受欢迎,目前在实际开发中也大量使用。Spring 2.5 的一大增强就是引入了很多注释类,现在开发者已经可以使用注释配置完成大部分XML配置的功能。
- Spring 2.5引入了@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
- 当 Spring容器启动时,AutowiredAnnotationBeanPostProcessor将扫描Spring容器中所有Bean。当发现Bean
- @Autowired 注释时,就找到和其匹配(默认按类型匹配)的Bean,并注入到对应的地方中去。
- 除了@Autowired注释外,Spring还提供了其他的注释,如@Component、@Resource、@Scope等其他注释,能显著提高开发效率。
- Spring框架的高级用法包括后处理器、资源访问、AOP机制及事务管理。
- Spring框架允许开发者使用两种后处理器扩展IoC容器,这两种后处理器可以后处理IoC容器本身或对容器中所有的Bean进行后处理。IoC容器还提供了AOP功能,极大丰富了Spring容器的功能。
Spring框架提供了良好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展。这种扩展并不是通过实现BeanFactory或ApplicationContext的子类,而是通过两个后处理器对IoC容器进行扩展。所谓后处理器,其实就是通过统一的方式,对功能模块的功能进行增强。下面将分别介绍Spring的两种常用后处理器。
Bean后处理器
容器后处理器
bean后处理器
这种后处理器会对容器中特定的Bean进行定制,例如功能的加强。Bean后处理器必须实现BeanPostProcessor接口,这个接口中包含如下两个方法:
Object postProcessBeforeInitialization(Object bean ,String name) throws BeansException
。该方法的第一个参数是系统将要后处理的Bean实例,第二个参数是该Bean实例的名字。
Object postProcessAfterInitialization(Object bean ,String name) throws BeansException
。同样的,该方法的第一个参数是系统将要后处理的Bean实例,第二个参数是该Bean实例的名字。
实现Bean后处理器必须实现这两个方法,它们用于对Bean实例实行增强处理,并会在目标Bean初始化之前和之后分别被回调。
public class MyBeanPostProcessorimplements BeanPostProcessor{@Ovemide
public ObjectpostProcessAfterInitialization(Object bean,Sting beanName) throws BeansException{
System.out.println("Bean后处理器在初始化之前对"+beanName+"进行增强处理")
retum bean;
}
@Ovemide
public Object postProcessBeforeInitialization(Object bean,Sting beanName) throws BeansException{↵
System.out.println("Bean后处理器在初始化之后对"+beanName+”进行增强处理"):
//如果该bean是Person类的实例,则改变其属性值↵
if(bean instanceof Person){+
Person p=(Person)bean;
p.setName("海宁不掉头发"):↵
}↵
retum bean;
}↵
}↵
public class Personimplements InitializingBeam{
private Sting name;
public void setName(String name){
System.out.println("Spring执行依赖关系注入-----etName方法"):↵
this name=name;
}
public Person0{
System.out.println("Spring实例化bean Person bean实例——-Person构造函数);
}
↵
public voidinformation({
System.out.print("这个人的名字是:"+name):
}↵
public voidinit0{
System.out.println(“正在执行初始化--------init方法”)}↵
@Ovemidee
public void afterPropertiesSetO throws Exception{
System.out.println("正在执行--------afterPropertiesSet方法")
接着,在applicationContext.xml文件中配置Bean后处理器,其配置方法与普通Bean完全一样。但是如果程序无须获取Bean后处理器,在配置文件中就可以不用为该后处理器指定id属性。详细的配置文件如下:
<bean id="pl"class="dps.bean.Person"init-method="init">
<property name="name" value="张三"/>
</bean>
<!--所有Bean的默认后处理器-->
<bean id="beanPostProcessor"class="com.beanPostProcessor.MyBeanPostProcessor">
最后,写一个测试程序,代码如下:
public static void main(String[]args){//读取Spring配置文件↵
ApplicationContext act=new ClassPathXmlApplicationContext("applicationContext xml");
//从Spring容器中获取id为pl的beane
Person pl=act.getBean("pl",Person.class);
pl.informationO:↵
从上面结果可以看出,由于使用了Bean后处理器,因此不管Person bean如何初始化,总是将其name属性设置为“段鹏松”。
容器后处理器
- 和Bean后处理器不同的是,容器后处理器是对IoC容器进行特定的后处理。Bean后处理器负责后处理容器生成的所有Bean,而容器后处理器则负责后处理容器本身。
- 容器后处理器必须实现BeanFactoryPostProcessor接口。要实现该接口就必须实现如下一个方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
实现该方法的方法体就是对Spring容器进行的处理,这种处理可以对Spring容器进行任意的扩展,当然,也可以对Spring容器不进行任何处理。
类似于BeanPostProcessor
,ApplicationContext可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用BeanFactory作为Spring容器,则必须手动注册后处理器。
pring中提供了以下几个常用的容器后处理器:
- PropertyPlaceholderConfigurer:属性占位符配置器
- PropertyOverrideConfigurer:重写占位符配置器
- CustomAutowireConfigurer:自定义自动装配的配置器
- CustomScopeConfigurer:自定义作用域的配置器
首先,定义一个容器后处理器类,代码如下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor{
@Ovemide
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException{System.out.println("程序对Spring所做的BeanFactory的初始化没有改变"):
System.out.println("spring的容器是"+beanFactory)
↵
接着,将上述容器后处理器类配置到Spring容器中,具体代码如下:
<bean id="pl"class="dps.bean.Person"init-method="init">
<property name="name" value="张三">
<bean>
<!--配置容器后处理器-->
<bean class="com.beanFactoryProcessor.MyBeanFactoryPostProcessor"/>
Person类和测试代码还是使用与Bean后处理器例子中的相同代码。运行测试代码,结果如下:
- 从运行结果可以看出,在使用了
ApplicationContext
为Spring的容器之后,ApplicationContext自动搜索容器中所有实现了BeanPostProcessor和BeanFactoryPostProcessor接口的类,并将它们注册成为Bean或容器后处理器。因为在配置文件中去掉了Bean后处理器的配置,所以Person
bean的属性值没有改变。 - 如果有需要,程序可以配置多个容器后处理器,并用order属性来控制后处理器的执行次序。
Spring把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源。对Spring开发者来说,最常用的资源就是Spring配置文件(通常是一份XML格式的文件)。在Sun所提供的标准API中,资源访问通常由java.net.URL和文件IO来完成,当需要访问来自网络的资源时,通常会选择URL类。
- UrlResource:访问网络资源的实现类。
- ClassPathResource:访问类加载路径里资源的实现类。
- FileSystemResource:访问文件系统里资源的实现类。
- ServletContextResource:访问相对于ServletContext路径里的资源的实现类。
- InputStreamResource:访问输入流资源的实现类。
- ByteArrayResource:访问字节数组资源的实现类。
ClassPathResource类用来访问类加载路径下的资源。相对于其他的 Resource 实现类,其主要优势是方便访问类加载路径里的资源。尤其对于Web应用,ClassPathResource类可自动搜索位于 WEB-INF/classes 下的资源文件,无须使用绝对路径访问。
以下示例是使用ClassPathResource类访问类加载路径下的student.xml文件。
首先,定义待访问的student.xml文件,内容如下:
<?xml version="1.0"encoding="utf-8"?>
<学生列表》
<学生>4
<姓名>张三<姓名>
<学号>2012776001<学号>
<年龄>20</年龄>
</学生>
<学生>
<姓名>李四<姓名>
<学号>2012776002<学号>
年龄>21<年龄>
<学生/>
<学生列表>
其次,使用ClassPathResource类访问student.xml文件的代码如下:
//ClassPathResource访问资源↵
public class ClassPathResourceTest{↵
public static void main(Sting[]args) throws Exception{
//创建一个Resource对象,从类加载路径里读取资源↵
ClassPathResource cr = new ClassPathResource("student.xml"):
//获取该资源的简单信息↵
System.out.println(cr.getFilenameO):↵
System.out.println(c getDescription():↵
//创建Dom4j的解析器
SAXReaderreader=new SAXReader();
Document doc=reader.read(cr getFileO):↵
//获取根元素↵
Elementel el = doc.getRootElement();↵
List1 =el.elementsO;↵
//遍历根元素的全部子元素↵
for(Iterator it1 = 1.iterator();
it1 hasNext();)↵{
//获取节点↵
Element student=(Element)it1.next();↵
List ll=student.element();
//遍历每个节点的全部子节点↵
for(Iterator it2= 1l.iterator();
it2 hasNext();)↵{
Element eee= (Element)it2.next();
System.out.println(eee getText();
最后,进行验证,运行结果如下:
【注意】上述代码运行时,因为要解析xml文件,所以需要添加dom4j.jar包。
FileSystemResource类
FileSystemResource
类用于访问文件系统资源。使用FileSystemResource
类来访问文件系统资源并没有太大的优势,是因为Java提供的File类也可用于访问文件系统资源。不过,使用 FileSystemResource
类也可消除底层资源访问的差异,程序通过统一的ResourceAPI
来进行资源访问。
下面程序是使用FileSystemResource类来访问文件系统资源的示例程序(访问的仍然是上例中的student.xml文件),代码如下:
Spring的AOP机制
Spring中的AOP机制,也就是面向切面编程的技术。
AOP基于IoC基础,是对OOP(Object Oriented Programming)的有益补充,是代码之间解耦的一种实现。可以这样理解,面向对象编程是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。
AOP基础知识
- 在AOP编程机制中,将应用系统分为两部分:
- 核心业务逻辑(Core business concerns)。
- 横向的通用逻辑,也就是所谓的方面(Crosscutting enterprise concerns)。
- 在实际开发中,AOP编程机制的使用场景较多,如大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。
AOP的底层实现原理实际上是Java语言的动态代理机制。AOP代理是由AOP框架动态生成一个对象,该对象可作为目标对象使用。AOP代理包含了目标对象的全部方法,但代理中的方法与目标对象的方法存在差异。AOP方法在特定切入点添加了增强处理,并回调了目标对象的方法。Spring的AOP机制通常和IoC配合使用,这个过程中需要程序员参与的有三个部分:
定义普通业务组件。
- 定义切入点,一个切入点可以横切多个业务组件。
- 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作。
- 在Spring框架中,有如下2种方式来定义切入点和增强处理:
- Annotation配置方式:使用@Aspect、@Pointcut等注释来标注切入点和增强处理。
- xml配置方式;使用xml配置文件来定义切入点和增强处理。
AOP应用案例
介绍一个较复杂的AOP应用案例,假设本实例场景如下: 用户可以执行的操作有两种:read和write。
在执行相应操作之前,会通过AOP来判断用户的用户名,判断规则如下: 如果用户名为admin,则read和write操作均可执行。
如果用户名为register,则只能执行read操作。 如果是其他用户名,则没有任何操作权限。 用户操作之后,对用户操作行为进行日志记录。
定义用户类User,代码如下:
2. 定义用户操作的实际接口及其实现类,代码如下:
定义用户操作的代理接口及其实现类,代码如下:
定义用户操作权限拦截器,
代码如下:
定义用户操作日志拦截器,代码如下:
编辑Spring的配置文件,代码如下:
定义测试类,代码如下:
运行结果:
如果是Admin操作,则运行结果如下:
)如果是Register操作,则运行结果如下:
如果是other操作,则运行结果如下:
从上述代码中可以看出,使用了AOP,相当于是把权限验证、日志记录这两个通用操作通过Spring配置文件横切到代码中去。在测试代码中,虽然并不能看到这两个通用操作的显示调用,但是实际上每个操作之前都会调用这两个通用拦截器。通过AOP,实现了通用操作和业务逻辑的代码解耦,简化了客户端的代码逻辑,更有利于程序的模块化开发和后期的维护操作。
Spring的事务管理
通常来说,Java Web项目都会涉及到数据库操作,而数据库操作又离不开事务管理。优秀的数据库支持框架,一般都具有良好的事务管理功能,Spring也不例外。Spring框架属于“一站式解决方案”,其中也包括对数据库事务管理的支持。本节将对数据库事务的基本概念及Spring框架的事务管理机制进行简要介绍。
- 事务是逻辑上一组完整的数据库操作,要么都执行,要么都不执行。事务遵守ACID原则,具有以下四个特性:
原子性(Atomicity):事务的原子性是指事务中的操作不可拆分,只允许全部执行或者全部不执行。
一致性(Consistency):事务的一致性是指事务的执行不能破坏数据库的一致性,一致性也称为完整性。一个事务在执行后,数据库必须从一个一致性状态转变为另一个一致性状态。
隔离性(Isolation):事务的隔离性是指并发的事务相互隔离,不能互相干扰。
持久性(Durability):事务的持久性是指事务一旦提交,对数据的状态变更应该被永久保存。
Spring事务管理的本质其实就是依靠数据库本身对事务的支持,如果数据库引擎不支持事务,Spring本身也是不能提供事务功能的。在JDBC操作数据库时,如果需要用到事务,则操作步骤如下:
// 获取连接
Connection conn = DriverManager.getConnection();
// 开启事务
// 执行语句
// 提交事务、回滚事务
// 关闭连接
使用Spring事务管理功能之后,开发者可以不再需要手动在每个地方去处理事务的开启和关闭。在Spring中,通过注解@Transaction实现事务驱动:提交事务、异常回滚事务。Spring在启动的时候会去解析生成相关的Bean,这时会查看拥有相关注解的类和方法,并且为这些类和方法生成动态代理,再根据@Transaction的相关参数进行配置注入,这样Spring就在代理中把相关的事务处理完成了。
事务控制参数
针对数据库事务的不同控制策略,如事务的传播、事务的隔离和事务的嵌套等,Spring框架提供了相应的控制参数。
针对数据库事务的不同控制策略,如事务的传播、事务的隔离和事务的嵌套等,Spring框架提供了相应的控制参数。
1、事务的传播
当多个事务同时存在的时候,就需要涉及到事务传播属性这一部分,这些属性在TransactionDefinition中定义,具体常量值如下:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务,这是其默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务的隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:
该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,通常情况下也不会用到该级别。
3、事务嵌套 PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
3、事务嵌套 PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED –
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB
CMT,最后一个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量,它要求事务管理器或者使用JDBC 3.0
Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)。
作为企业级应用程序框架,Spring在不同的事务管理API之上又定义了一个抽象层。业务开发人员不必关注底层驱动事务管理的框架实现,就可以使用Spring的事务管理功能。Spring同时支持编程式事务管理和声明式事务管理两种事务管理方式。
编程式事务管理:直接在业务方法逻辑中嵌入事务管理的代码,显式控制事务的提交和回滚。
声明式事务管理:将通用的事务管理代码从业务代码中抽离出来,以声明方式来实现事务管理。
- 事务管理作为一种横切关注点,可以通过AOP方法模块化。Spring通过Spring
AOP框架支持声明式事务,并从不同的事务管理API中抽象出一整套的事务机制。因此,开发人员不必了解底层事务API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了。
Spring允许简单地通过使用@Transactional注解来标注事务方法。为了将方法定义为支持事务处理,可以为方法添加@Transactional注解,根据Spring AOP基于代理机制,只能标注public方法。同时也可以在方法或者类级别上添加@Transactional注解,当把这个注解应用到类上时,这个类中的所有public方法都会被定义为支持事务处理。
默认情况下,如果被注解的数据库操作方法发生了Unchecked Exception或Error,所有的数据库操作将回滚;如果发生的是Checked Exception,默认情况下数据库操作还是会提交的。这里主要取决于注解实现方式。这个事务是依赖数据库层的数据库表类型,需要时刻本身支持事务,回滚的操作也是数据库层控制的。
一般来说,要实现完整的事件机制需要三个主要元素:
事件源
事件监听器
事件
Java SE提供了一系列自定义事件的标准,EventObject为Java SE提供的事件类型基类,任何自定义事件都必须继承它。EventListener是事件监听器的基类,Java SE未提供事件源,应当由应用程序自行实现事件源角色。
Spring提供ApplicationEventPublisher接口作为事件源,ApplicationContext接口继承了该接口,担当事件源角色。ApplicationEventMulticaster接口负责管理ApplicationListener和发布ApplicationEvent,ApplicationContext接口把事件的相关工作委托给ApplicationEventMulticaster的实现类来完成。
使用事件机制能帮助开发者显著减少不同业务间代码的耦合度,
使代码结构更清晰。同时,事件可以配置异步,在部分情况下可以提高反馈效率。
相应的,Spring的事件机制也是由对应的三个部分组成:
ApplicationEvent:表示事件本身,自定义事件需要继承该类,可以用来传递数据。
ApplicationEventPublisherAware:事件发送器,通过实现这个接口来触发事件。
ApplicationListener:事件监听器接口,事件的业务逻辑封装在监听器里面。
下面通过实例来学习Spring的事件的使用流程。
首先创建事件类,继承自ApplicationContextEvent类。
- Application发布事件 在成员变量处引入ApplicationContext类型变量,并通过该变量发布事件。 @Resource ApplicationContext context; 方法业务逻辑中使用事件发布功能。 // 通过事件机制发送邮件
context.publishEvent(new UnlockRecordEvent(context));
通过注解监听事件触发,根据注解所在参数中的事件引用确定监听事件对象。
上述代码中,注释@ EnableAsync表示开启异步操作。上述实例可以看出,Spring框架提供的事件机制功能强大,并且使用流程也较为简单。