Spring概述
Spring框架是什么?
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)
Spring的优点
- Spring是一个轻量级的框架 , 非侵入式的
- 对事务的支持 , 对框架整合的支持
- 针对接口编程,解耦合
Spring的体系结构
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。
- 核心容器(Spring Core):核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文(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。
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,DL(依赖查找)也是实现IOC的一种方法;
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
Spring容器是一个超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。Spring 容器管理着容器中Bean之间的依赖关系,Spring 使用**DI(依赖注入)**的方式来管理Bean之间的依赖关系。使用loC实现对象之间的解耦合。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
HelloSpring
准备
实现步骤:
1.创建maven项目
2.加入maven的依赖
spring的依赖
junit依赖
lombok依赖
3.创建Student类,和没有使用框架一样,就是普通的类。
4.创建spring需要使用的配置文件
声明类的信息。这些类由spring创建和管理
5.测试spring创建的对象。
Sprig的第一个程序
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--告诉spring创建对象
声明bean,告诉spring要创建某个类的对象
id是对象的自定义名称(唯一值)。spring通过这个名称找到对象
class:类的全限定名称(不能是接口,因为spring是反射机制)
spring就完成Student student = new Student();
spring是把创建好的对象放到了map中,spring框架中有一个map存放对象的。
springMap.put(id的值,对象)
例如 springMap.put("student",new Student());
一个bean标签只声明一个对象
-->
<bean id="student" class="com.test.dyh.Student">
<property name="id" value="1"/>
<property name="age" value="12"/>
<property name="name" value="段玉"/>
</bean>
</beans>
<!--spring的配置文件
1.beans是根标签,spring中把Java对象成为bean
2.spring-beans.xsd是约束文件,和mybatis指定的类似
-->
测试
package com.test.dyh;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.text.MessageFormat;
public class AppTest {
/*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/
@Test
public void shouldAnswerWithTrue() {
//使用spring容器创建的对象
//1.指定spring配置文件的名称
String config="bean.xml";
//2.创建表示spring容器的对象,ApplicationContext
//ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
ApplicationContext st = new ClassPathXmlApplicationContext(config);
//3.从容器中获取某个对象,你要调用对象的方法,
//getBean("配置文件中的bena的id值");
Student student = (Student) st.getBean("student");
//使用spring创建好的对象
System.err.println(MessageFormat.format("id={0}, age={1}, name={2}", student.getId(),
student.getAge(), student.getName()));
//输出:id=1, age=12, name=段玉
}
}
IOC创建对象的方式
使用无参构造创建对象,默认!
假设我们要使用有参构造创建对象。
- 下标赋值
<bean id="student" class="com.test.dyh.Student">
<constructor-arg index="0" value="1"></constructor-arg>
<constructor-arg index="1" value="14"></constructor-arg>
<constructor-arg index="2" value="name"></constructor-arg>
</bean>
- 构造参数类型
<bean id="student" class="com.test.dyh.Student">
<constructor-arg type="int" value="1"></constructor-arg>
<constructor-arg type="java.lang.Integer" value="14"></constructor-arg>
<constructor-arg type="java.lang.String" value="段"></constructor-arg>
</bean>
- 构造参数名
<bean id="student" class="com.test.dyh.Student">
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="age" value="14"></constructor-arg>
<constructor-arg name="name" value="段"></constructor-arg>
</bean>
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了
Spring配置
别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="student" alias="student2"></alias>
Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id : bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class : bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
import
这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个;
假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!
<import resource="{path}/beans.xml"/>
依赖注入
基于XML的DI
构造器注入
前面已经说过了
Set方式注入 【重点】
依赖注入:Set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入,spring调用类的set方法,在set方法可以实现属性的赋值
2个实体类
package com.test.dyh;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author dyh
* @date 2021/6/1 - 16:41
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private Integer age;
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
package com.test.dyh;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author dyh
* @date 2021/6/1 - 17:52
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
private String address;
}
配置 applicationContext.xml
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.test.dyh.Address">
<property name="address" value="广东深圳"></property>
</bean>
<!--声明student对象
注入:就是赋值的意思
简单类型:spring中规定Java的基本数据类型和string类型都是简单类型。
di:给属性赋值
1.set注入(设置注入):spring调用类的set方法,你可以在set方法中完成属性赋值
利用property
-->
<bean id="student" class="com.test.dyh.Student">
<!--第一种,普通值注入,value-->
<property name="id" value="1"></property>
<property name="age" value="14"></property>认准set方法
<property name="name" value="断浪"></property><!--调用setName...-->
<!--第二种,Bean注入-->
<property name="address" ref="address"></property>
<!--数组-->
<property name="books">
<array>
<value>水浒传</value>
<value>西游记</value>
</array>
</property>
<!--List-->
<property name="hobbys">
<list>
<value>music</value>
<value>swimming</value>
<value>coding</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="12312121212"/>
<entry key="银行卡" value="678112121111000"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>CF</value>
<value>LOL</value>
<value>GTA</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="学号">20190526</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
</beans>
测试
package com.test.dyh;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
/*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/
@Test
public void shouldAnswerWithTrue() {
//使用spring容器创建的对象
//1.指定spring配置文件的名称
String config = "bean.xml";
//2.创建表示spring容器的对象,ApplicationContext
//ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
ApplicationContext st = new ClassPathXmlApplicationContext(config);
//3.从容器中获取某个对象,你要调用对象的方法,
//getBean("配置文件中的bena的id值");
Student student = (Student) st.getBean("student");
//使用spring创建好的对象
System.err.println(student);
//输出:Student(id=1, age=14, name=断浪, address=Address(address=广东深圳),
// books=[水浒传, 西游记], hobbys=[music, swimming, coding],
// card={身份证=12312121212, 银行卡=678112121111000}, games=[CF, LOL, GTA],
// wife=null, info={password=root, 学号=20190526, username=root})
}
}
拓展方式注入
我们可以使用p命名空间和c命名空间进行注入
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="student" class="com.test.dyh.Student" p:name="张三" p:age="18"/>
<!--c命名空间注入,通过构造器注入:construt-args-->
<bean id="student2" class="com.test.dyh.Student" c:name="李四" c:age="11"/>
</beans>
注意点:p命名和c命名空间不能直接使用,需要导入xml约束!
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
基于注解的DI
通过注解完成Java对象的创建,属性赋值
使用注解的步骤
- 加入maven的依赖spring-context,在你加入spring-context的同时,间接加入spring-aop的依赖。
使用注解必须使用spring-aop依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
- 导入约束(xmlns:context)
- 在类中加入spring的注解(多个不同功能的注解)
- 在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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--声明组件扫描器(component-scan),
组件就是Java对象,所以就是找Java对象
base-package:指定注解在你的项目中的包名
component-scan工作方式:spring会扫描遍历base-package指定的包
把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象和给属性赋值。
-->
<context:component-scan base-package="com.test.dyh"></context:component-scan>
</beans>
1.@Component 2.@Repository 3.@Service 4.@Controller 5.@Value 6.@Autowired 7.@Resource
package com.test.dyh;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author dyh
* @date 2021/6/1 - 16:41
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
/*
* @Component:创建对象的,等同于<bean>的功能
* 属性value: 就是对象的名称,也就是bean的id值,value值是唯一的,创建的对象在整个spring容器中就一个
* 位置:在类的上面写注解
* */
//等同于<bean id="myStudent" class="com.test.dyh.Student"/>
//@Component(value = "myStudent")
//省略value
//@Component("myStudent")
//不指定对象名称,由spring提供默认名称(首字母小写类名)
@Component
public class Student {
private int id;
private Integer age;
//相当于 <property name="name" value="凯斯"></property>
@Value("凯斯")
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
测试
package com.test.dyh;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ApplicationContext ac=new ClassPathXmlApplicationContext ("bean.xml");
//从容器中获取对象
Student student=(Student) ac.getBean ("student");
System.out.println (student.getName());
//输出:Student(id=0, age=null, name=凯斯,
//address=null, books=null, hobbys=null,
//card=null, games=null, wife=null, info=null)
}
}
* spring中和@Component功能一致,创建对象的注解还有:
* @Repository(用在持久层上):放在dao的实现类上面,
* 表示创建dao对象,dao对象是能访问数据库的。(持久层注解)
*
* @Service(用在业务层类的上面):放在service的实现类上面,
* 创建service对象,service对象是做业务处理的,可以有事物等功能的。
*
* @Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
* 控制器对象可以接收用户提交的参数和显示请求的处理结果。
*
* 以上三个注解的使用语法和@Component是一样的,都能够创建对象,但是这三个注解还有额外的功能----给项目分层
指定多个包的三种方式
使用多次组件扫描器标签,指定不同的包
使用分隔符(分号或者逗号)分隔多个包名
指定父包
Bean的自动装配
Bean的自动装配(XML)
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
byName与byTpye自动装配
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.test.dyh.Address">
<property name="address" value="深圳"></property>
</bean>
<!--
byName(按名称注入): 会自动在容器上下文中查找,和自己对象set方法参数名对应的bean_id
且数据类型一致,这样的容器中的bean,spring能够自动注入
byType(按类型注入) : 会自动在容器上下文中查找,和自己对象属性类型相同的bean
-->
<bean id="student" class="com.test.dyh.Student" autowire="byName">
<property name="id" value="1"></property>
<property name="age" value="14"></property>
</bean>
</beans>
注意
byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致
测试
package com.test.dyh;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
/*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/
@Test
public void shouldAnswerWithTrue() {
//使用spring容器创建的对象
//1.指定spring配置文件的名称
String config = "bean.xml";
//2.创建表示spring容器的对象,ApplicationContext
//ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
ApplicationContext st = new ClassPathXmlApplicationContext(config);
//3.从容器中获取某个对象,你要调用对象的方法,
//getBean("配置文件中的bena的id值");
Student student = (Student) st.getBean("student");
//使用spring创建好的对象
System.err.println(student.getAddress().getAddress());
//输出:深圳
}
}
Bean的自动装配(注解)
前提
要使用注解须知:
- 导入约束(xmlns:context)
- 配置注解的支持(context:annotation-config)
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 比较少用的扫描标签,它只会扫描属性(即变量)
<context:annotation-config></context:annotation-config>
仅作了解即可。 -->
<context:annotation-config></context:annotation-config>
<bean id="address" class="com.test.dyh.Address">
<property name="address" value="深圳"></property>
</bean>
<bean id="student" class="com.test.dyh.Student"></bean>
</beans>
@Autowired与@Resource
package com.test.dyh;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author dyh
* @date 2021/6/1 - 16:41
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private Integer age;
private String name;
//如果显示定义了Autowired的required属性为false,说明这个对象可以为Null,否则不允许为空
//@Autowired(required = false)
//如果@Autowired自动装配的环境比较复杂,
//自动装配无法通过一个注解【@Autowired】完成的时候,
//我们可以使用==@Qualifier(value = “xxx”)==去
//配合@Autowire的使用,指定一个唯一的bean对象注入!
//@Autowired
//@Qualifier(value = "address")
@Resource( name = "address")
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
测试
package com.test.dyh;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
ApplicationContext ac=new ClassPathXmlApplicationContext ("bean.xml");
//从容器中获取对象
Student student=(Student) ac.getBean ("student");
System.out.println (student.getAddress().getAddress());
//输出:深圳
}
}
小结
@Resource和@Autowired的区别:
- 都是用来自动转配的,都可以放在属性字段上
- @Autowired 是通过byType的方式实现,而且必须要求这个对象存在!【常用】
- @Resource 默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
- 执行顺序不同: @Autowired 通过byType的方式实现。@Resource默认通过byName的方式实现。
使用Java的方式实现Spring
不再使用spring的xml配置,全权交给java
JavaConfig是Spring的一个子项目
实体类
package com.test.dyh;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
/**
* @author dyh
* @date 2021/6/1 - 16:41
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private int id;
private Integer age;
@Value("李四")
private String name;
}
JavaConfig类
package com.test.dyh;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author dyh
* @date 2021/6/2 - 17:32
* @description:
*/
//这个也会被Spring容器托管,注册到容器中,因为它本身就是一个@Component
//@Configuration代表这是一个配置类。就跟之前bean.xml是一样的
@Configuration
@ComponentScan("com.test.dyh")
@Import(JavaConfig2.class)
public class JavaConfig {
//注册一个bean,就相当于一个bean标签
//这个方法的名字,就相当于bean标签的id属性
//这个方法的返回值,就相当于bean标签的class属性
@Bean
public Student student() {
return new Student();
}
}
测试类
package com.test.dyh;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AppTest {
@Test
public void shouldAnswerWithTrue() {
//如果完全使用配置类做,就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
//从容器中获取对象
Student student = (Student) ac.getBean("student");
System.out.println(student.getName());
//输出李四
}
}
AOP面向切面编程
代理模式的分类:
- 动态代理
- 静态代理
动态代理
动态代理是指:程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成,代理对象与目标对象的代理关系在程序运行时确定。
实现方式
jdk动态代理, 使用jdk中的Proxy, Method, InvocationHanderl创建代理对象。jdk动态代理要求目标类必须实现接口。
cglib动态代理:第三方的工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的。
动态代理的作用
- 在目标类源代码不变的情况下,增强功能
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让你的业务功能和日志,事务非事务功能分离
AOP简介
AOP (Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。
AOP底层,就是采用动态代理模式实现的。采用了两种代理: JDK的动态代理,与CGLIB的动态代理。
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方法,使用动态代理)。
切面:给你的目标类增加的功能,就是切面,什么日志等(切面的特点:一般都是非业务方法,独立使用的。)
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。
若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变的混杂不清。
如何理解面向切面编程?
- 需要在分析项目功能时,找出切面。
- 合理的安排切面的执行时间(在目标方法前呢,还是后呢)
- 合理的安全切面执行位置,在哪个类,哪个方法增加增强
AOP编程术语
- Aspect:切面,表示增强的功能,就是一堆代码,完成某个功能。(非业务功能,可独立执行。如:日志,统计信息,权限验证等)
- JoinPoint:连接点,连接你的业务方法和切面位置。其实就是某个类中的业务方法。
- Pointcut:切入点,指多个连接点方法的集合。
- 目标对象:给哪个类的方法增加功能,这个类就是目标对象。
- Advice:通知,通知表示切面功能执行的时间。
一个切面有三个关键的要素
- 切面的功能代码,切面干什么
- 切面的执行位置,使用Pointcut表示切面执行位置
- 切面执行时间,使用advice表示时间。
AspectJ对AOP的实现
aop的实现
aop是一个规范,是一个动态的一个规范化,一个标准。
aop的技术实现框架:
spring:spring在内部实现了aop规范,能做aop的工作。
aspectJ:一个开源的专门做aop的框架。spring框架中集成了aspectJ框架,通过spring就可以使用aspectJ的功能了。
aspectJ框架实现有两种方式:1.使用xml的配置文件(配置全局事务) 2.使用注解,一般都用注解
学习AspectJ框架的使用
如何表示切面的执行时间?
注解表示:1.@Before 2.@AfterReturning 3.@Around 4.@AfterThrowing 5.@After
如何表示切面执行的位置?
使用的是切入点表达式。
表达式原型为:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
解释:
modifiers-pattern 访问权限类型
ret-type-pattern 返回值类型
declaring-type-pattern 包名类名
name-pattern(param-pattern) 方法名(参数类型和参数个数)
throws-pattern 抛出异常类型
?表示可选部分
使用Spring实现Aop(使用AspectJ实现Aop)
使用aspectJ框架实现aop
使用aop:目的是给已经存在的一些类和方法,增加额外的功能,前提是不改变原来的类的代码。
框架的使用步骤:
1.新建maven项目
2.加入依赖
spring依赖 aspectJ依赖 junit单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3.创建目标类:接口和他的实现类
要做的是给类中的方法去增加功能
4.创建切面类:普通类
1.在类的上面加入@Aspect
2.在类中去定义方法---要执行的功能代码
3.在方法的上面加入aspectJ中的通知注解,例如@Before
还需要指定切入点表达式execution()
5.创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理
声明对象可以使用注解或者xml配置文件
声明目标对象
声明切面类对象
声明aspectJ框架中的自动代理生成器标签(用来完成代理对象的自动创建功能的)
6.创建测试类,从spring容器中获取目标对象(实际上是代理对象)。
通过代理执行方法,实现aop的功能增强
@Before前置通知
接口
package cqutlc.ba01;
public interface SomeSevice {
void doSome(String name,Integer age);
}
目标类
package cqutlc.ba01;
//目标类
public class SomeSeviceImpl implements SomeSevice {
@Override
public void doSome (String name, Integer age) {
//给doSome方法增加一个功能,在doSome方法执行之前,输出方法的执行时间
System.out.println ("目标方法执行");
}
}
切面类
package cqutlc.ba01;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/*
*
@Aspect:aspectJ框架中的注解,用来表示当前类是切面类
* 位置:类定义的上面
* */
@Aspect
public class MyAspect {
/*
* 定义方法:实现切面功能的、
* 方法的定义要求:
* 1.公共方法
* 2.没有返回值3.方法名自定义4.方法可以有参数也可以没有参数,有几个参数类型可以使用()
* */
/*@Before 前置通知注解
* 属性:value,切入点表达式,表示切面的功能执行的位置
* 特点:在目标方法之前先执行,不会改变目标方法的执行结果,不会影响目标方法的执行
* */
@Before (value = "execution(public void *..SomeSeviceImpl.doSome(..))")
public void myBefore(){
//功能代码
System.out.println ("时间:"+new Date ());
}
}
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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给spring容器,由容器统一创建和管理对象-->
<!--声明目标对象-->
<bean id="someService" class="cqutlc.ba01.SomeSeviceImpl"/>
<!--声明切面类对象-->
<bean id="myAspect" class="cqutlc.ba01.MyAspect"/>
<!--声明自动代理生成器:使用aspectJ框架内部的功能,创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象的内存中的结构,创建为代理对象
所以目标对象就是被修改后的代理对象
aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
-->
<aop:aspectj-autoproxy/>
</beans>
测试类
package cqutlc.ba01;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test1 {
@Test
public void test1(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext (config);
SomeSevice proxy=(SomeSevice)ac.getBean ("someService");
//通过代理的对象执行方法,实现目标方法执行时,增强了功能。
proxy.doSome ("lisi",20);
}
}
JoinPoint
指定通知方法中的参数
joinpoint:业务方法,要加入切面功能的业务方法
作用是:可以在通知方法中获取方法执行时的信息,例如,方法名称,方法的实参。
如果你的切面功能中需要用到方法的信息,就加入joinpoint
这个joinpoint参数的值是由框架赋予,必须是第一个位置的参数。
@Aspect
public class MyAspect {
@Before (value = "execution(public void *..SomeSeviceImpl.doSome(..))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的定义:"+jp.getSignature());
System.out.println("方法的名称:"+jp.getSignature().getName());
//获取方法的实参
Object[] args= jp.getArgs();
for(Object arg: args){
System.out.println("参数="+arg);
}
//功能代码
System.out.println ("时间:"+new Date ());
}
}
@AfterReturning后置通知
package cqutlc.ba02;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/*
*
@Aspect:aspectJ框架中的注解,用来表示当前类是切面类
* 位置:类定义的上面
* */
@Aspect
public class MyAspect {
/*
* 后置通知定义方法:实现切面功能的、
* 方法的定义要求:
* 1.公共方法
* 2.没有返回值3.方法名自定义4.方法有参数,推荐使用object,参数名自定义
* */
/*
* @AfterReturning
* 属性:
* 1.value:切入点表达式
* 2.returning:自定义的变量,用来表示目标方法的返回值,自定义变量名必须和通知方法的形参名一样
* 特点:
* 1.在目标方法之后执行的
* 2.能够获得目标方法的返回值,可以根据这个返回值做不同的处理功能
* Object res = doOther();
* 3.可以修改这个返回值。
* */
@AfterReturning(value = "execution(* *..SomeSeviceImpl.doOther(..))",
returning = "res")
public void myAfterReturing(Object res){
//Object res :是目标方法执行后的返回值,根据返回值做你的切面的功能处理
System.out.println ("后置通知,获取的返回值是"+res);
if (res.equals ("abc")){
res="hi";
}else
{
}
}
}
@Around环绕通知
增强方法有ProceedingJoinPoint参数
package cqutlc.ba03;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/*
*
@Aspect:aspectJ框架中的注解,用来表示当前类是切面类
* 位置:类定义的上面
* */
@Aspect
public class MyAspect {
/*
* 环绕通知定义方法的格式
* 1.public
* 2.有一个返回值 object
* 3.方法名称自定义
* 4.方法有参数,固定的 ProceedingJoinPoint
*
* */
/*@Around
特点:
1.功能最强的通知
2.在目标方法前和后都可以加入功能
3.控制目标方法是否被调用实行
4.修改原来的目标方法的执行结果,影响最后的调用结果
环绕通知等同于jdk动态代理,InvocationHandler接口
参数:ProceedingJoinPoint 就等同于Method
作用:执行目标方法的执行结果,可以被修改。
环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务
* */
@Around (value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
String name="";
Object[] args =pjp.getArgs ();
if (args!=null&&args.length>1){
Object arg= args[0];
name=(String)arg;
}
//在目标方法前或者后加功能
//实现环绕通知
Object result=null;
System.out.println ("环绕通知在目标方法之前"+new Date ());
if ("zhangsan".equals (name)){
//1.实现目标方法的调用
result= pjp.proceed ();//method.invoke,object result=doFirst();
}
System.out.println ("环绕通知在目标方法之后,提交事务");
return result;
}
}
测试
package cqutlc.ba03;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test1 {
@Test
public void test1(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext (config);
SomeService proxy=(SomeService)ac.getBean ("someService");
String str=proxy.doFirst ("lc",19);
}
}
@Pointcut定义切入点
当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。
AspectJ提供了@Pointcut注解,用于定义execution切入点表达式。其用法是,将@Pointcut注解在一个方法之上,以后所有的execution的value属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用private 的标识方法,即没有实际作用的方法。
属性 :value切入点表达式
位置:自定义的方法的上面
特点:当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名,其他的通知中,value属性就可以使用这个方法名称代替切入点表达式。
@Pointcut(value="execution(* *..SomeServiceImpl.doOther(..))")
private void mypt(){
//无需代码
}
有接口也可以使用cglib代理
在xml中配置<aop:aspectj-autoproxy proxy-target-class=“true”/>表示告诉框架,你要使用cglib代理。JDK(默认 <aop:aspectj-autoproxy proxy-target-class=“false”/>)
Spring集成MyBatis
spring和mybatis集成
步骤
1.新建maven项目
2.加入依赖 spring依赖 mybatis依赖 mysql驱动 spring的事务依赖 mybatis和spring集成的依赖
3.创建实体类
4.创建mybatis主配置文件
5.创建接口和mapper文件
6.创建测试类
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test.dyh</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>
<name>spring</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
实体类
package com.test.dyh.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author dyh
* @date 2021/6/3 - 15:55
* @description:
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
mybatis.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--日志-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置别名-->
<typeAliases>
<!--name所在包的路径-->
<package name="com.test.dyh.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis
?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--sql mapper映射文件的位置-->
<mappers>
<mapper class="com.test.dyh.mapper.UserMapper"></mapper>
</mappers>
</configuration>
接口
package com.test.dyh.mapper;
import com.test.dyh.pojo.User;
import java.util.List;
/**
* @author dyh
* @date 2021/6/3 - 16:02
* @description:
*/
public interface UserMapper {
public List<User> listUser();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.dyh.mapper.UserMapper">
<select id="listUser" resultType="user">
select * from mybatis.user;
</select>
</mapper>
测试类
package com.test.dyh;
import com.test.dyh.mapper.UserMapper;
import com.test.dyh.pojo.User;
import lombok.val;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class AppTest {
@Test
public void shouldAnswerWithTrue() throws IOException {
String resources = "mybatis.xml";
InputStream resourceAsStream = Resources.getResourceAsStream(resources);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession(true);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.listUser();
for (User u : users) {
System.err.println(u);
}
}
}
User(id=1, name=张三, pwd=124)
Spring事务
- 声明式事务
- 编程式事务
编程式和声明式事务的区别
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。
事务的四个特性
事务有四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
事务的传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
传播行为 | 含义 |
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
事务的隔离级别
隔离级别 | 含义 |
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
编程式事务
如何实现编程式事务?
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager
使用TransactionTemplate
采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:
TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
Object result = tt.execute(
new TransactionCallback(){
public Object doTransaction(TransactionStatus status){
updateOperation();
return resultOfUpdateOperation();
}
}); // 执行execute方法进行事务管理
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
使用PlatformTransactionManager
示例代码如下:
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
try {
// 数据库操作
dataSourceTransactionManager.commit(status);// 提交
} catch (Exception e) {
dataSourceTransactionManager.rollback(status);// 回滚
}
声明式事务
配置方式
根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:
(1)每个Bean都有一个代理
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<property name="target" ref="userDaoTarget" />
<property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
(2)所有Bean共享一个代理基类
<?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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionBase"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
lazy-init="true" abstract="true">
<!-- 配置事务管理器 -->
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="userDao" parent="transactionBase" >
<property name="target" ref="userDaoTarget" />
</bean>
</beans>
(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-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<!-- 配置事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>*Dao</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
</list>
</property>
</bean>
<!-- 配置DAO -->
<bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
(4)使用tx标签配置的拦截器
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts"
expression="execution(* com.bluesky.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice"
pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
(5)全注解
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package="com.bluesky" />
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
此时在DAO上需加上@Transactional注解,如下:
package com.bluesky.spring.dao;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;
import com.bluesky.spring.domain.User;
@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
public List<User> listUsers() {
return this.getSession().createQuery("from User").list();
}
}
一个声明式事务的实例
首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)
然后是XML配置
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="applicationContext-db.xml" />
<context:component-scan
base-package="com.springinaction.transaction">
</context:component-scan>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
使用的类
BookShopDao
package com.springinaction.transaction;
public interface BookShopDao {
// 根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
// 更新书的库存,使书号对应的库存-1
public void updateBookStock(String isbn);
// 更新用户的账户余额:account的balance-price
public void updateUserAccount(String username, int price);
}
BookShopDaoImpl
package com.springinaction.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate JdbcTemplate;
@Override
public int findBookPriceByIsbn(String isbn) {
String sql = "SELECT price FROM book WHERE isbn = ?";
return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@Override
public void updateBookStock(String isbn) {
//检查书的库存是否足够,若不够,则抛出异常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if (stock == 0) {
throw new BookStockException("库存不足!");
}
String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
JdbcTemplate.update(sql, isbn);
}
@Override
public void updateUserAccount(String username, int price) {
//检查余额是否不足,若不足,则抛出异常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
if (balance < price) {
throw new UserAccountException("余额不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
JdbcTemplate.update(sql, price, username);
}
}
BookShopService
package com.springinaction.transaction;
public interface BookShopService {
public void purchase(String username, String isbn);
}
BookShopServiceImpl
package com.springinaction.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
/**
* 1.添加事务注解
* 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
* 默认取值为REQUIRED,即使用调用方法的事务
* REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
*
* 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
* 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
* 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true
* 5.使用timeOut 指定强制回滚之前事务可以占用的时间。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//1.获取书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//2.更新书的库存
bookShopDao.updateBookStock(isbn);
//3.更新用户余额
bookShopDao.updateUserAccount(username, price);
}
}
Cashier
package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
public void checkout(String username, List<String>isbns);
}
CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为
package com.springinaction.transaction;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopService bookShopService;
@Transactional
@Override
public void checkout(String username, List<String> isbns) {
for(String isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
BookStockException
package com.springinaction.transaction;
public class BookStockException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BookStockException() {
super();
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public BookStockException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public BookStockException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
UserAccountException
package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UserAccountException() {
super();
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1, boolean arg2,
boolean arg3) {
super(arg0, arg1, arg2, arg3);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO Auto-generated constructor stub
}
public UserAccountException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
public UserAccountException(Throwable arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
测试类
package com.springinaction.transaction;
import java.util.Arrays;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTransitionTest {
private ApplicationContext ctx = null;
private BookShopDao bookShopDao = null;
private BookShopService bookShopService = null;
private Cashier cashier = null;
{
ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
cashier = ctx.getBean(Cashier.class);
}
@Test
public void testBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@Test
public void testBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@Test
public void testBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@Test
public void testBookShopService(){
bookShopService.purchase("AA", "1001");
}
@Test
public void testTransactionPropagation(){
cashier.checkout("AA", Arrays.asList("1001", "1002"));
}
}