一.什么是Spring?
Spring是分层的 Java SE/EE应用 full-stack(全栈式) 轻量级开源框架。 提供了表现层 SpringMVC和持久层 Spring JDBC Template以及 业务层 事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
两大核心:以 IOC(Inverse Of Control:控制反转)和 AOP(Aspect Oriented Programming:面向切面编程)为内核。
二.Spring相关API
1.API继承体系介绍
Spring的API体系异常庞大,我们现在只关注两个BeanFactory和ApplicationContext。
2.BeanFactory
BeanFactory 是IOC容器的核心接口,它定了IOC的基本功能。
特点:在第一次调用getBean()方法时,创建指定对象的实例
BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); |
3. ApplicationContext
代表应用上下文对象,可以获得spring中IOC容器的Bean对象。
特点:在spring容器启动时,加载并创建所有对象的实例
常用实现类
1. ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件 推荐使用这种。 2. FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。 3. AnnotationConfigApplicationContext 当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。 |
ApplicationContext app= new ClassPathXmlApplicationContext("applicationContext.xml"); app.getBean("id"); app.getBean(Class); |
常用方法
1. Object getBean(String name);
根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
2. <T> T getBean(Class<T> requiredType);
根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
3. <T> T getBean(String name,Class<T> requiredType);
根据Bean的id和类型获得Bean实例,解决容器中相同类型Bean有多个情况
三.Spring配置文件 (IOC)
1.Bean标签基本配置
<bean id="" class=""></bean> * 用于配置对象交由Spring来创建。 * 基本属性: id:Bean实例在Spring容器中的唯一标识 class:Bean的全限定名 * 默认情况下它调用的是类中的 无参构造函数,如果没有无参构造函数则不能创建成功。 |
2. Bean标签范围配置(Bean的作用域)
<bean id="" class="" scope=""></bean> |
scope属性指对象的作用范围,取值如下:
取值范围 | 说明 |
Singleton | 默认值,单例的. Bean的实例化个数:1个 Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例 Bean的生命周期: 对象创建:当应用加载,创建容器时,对象就被创建了 对象运行:只要容器在,对象一直活着 对象销毁:当应用卸载,销毁容器时,对象就被销毁了 |
Prototype | 多例的。 Bean的实例化个数:多个 Bean的实例化时机:当调用getBean()方法时实例化Bean。 Bean的生命周期: 对象创建:当使用对象时,创建新的对象实例。 对象运行:只要对象在使用中,就一直活着。 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了。 |
Request | WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中 |
Session | WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中 |
globalsession | WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession 相当 于 session |
3. Bean生命周期配置
<bean id="" class="" scope="" init-method="" destroy-method=""></bean> * init-method:指定类中的初始化方法名称 * destroy-method:指定类中销毁方法名称 |
4.Bean实例化三种方式
4.1无参构造方法实例化。
它会根据默认无参构造方法来创建类对象,如果bean中没有默认无参构造函数,将会创建失败。
代码示例:
<bean id="userDao" class="com.yang.dao.impl.UserDaoImpl"/> |
4.2工厂静态方法实例化。
应用场景
假如:依赖的jar包中有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得。
public class StaticFactoryBean { public static UserDao createUserDao(){ return new UserDaoImpl(); }} |
<bean id="userDao" class="com.yang.factory.StaticFactoryBean" factory-method="createUserDao" /> |
4.3工厂普通方法实例化
应用场景
例如:依赖的jar包中有个A类,A类中有个普通方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得。
public class DynamicFactoryBean { public UserDao createUserDao(){ return new UserDaoImpl(); } } |
<bean id="dynamicFactoryBean" class="com.yang.factory.DynamicFactoryBean"/> <bean id="userDao" factory-bean="dynamicFactoryBean" factory- method="createUserDao"/> |
5. Bean依赖注入概述
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用我们自己去获取。
6. Bean依赖注入方式
共三种方式如下:
6.1构造方法注入
在UserServiceImpl中创建有参构造
public class UserServiceImpl implements UserService { private UserDao userDao; public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } } |
配置Spring容器调用有参构造时进行注入
<bean id="userDao" class="com.yang.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.yang.service.impl.UserServiceImpl"> <!--<constructor-arg index="0" type="com.yang.dao.UserDao" ref="userDao"/>- -> <constructor-arg name="userDao" ref="userDao"/> </bean> |
6.2 set方法注入
在UserServiceImpl中创建set方法
public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } } |
配置Spring容器调用set方法进行注入
<bean id="userDao" class="com.yang.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.yang.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> |
6.3P命名空间注入
P命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件
中,如下:
首先,需要引入P命名空间:
xmlns:p="http://www.springframework.org/schema/p" |
其次,需要修改注入方式:
<bean id="userDao" class="com.yang.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.yang.service.impl.UserServiceImpl" p:userDao-ref="userDao"/> |
(上面操作,都是注入Bean对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。注入数据的三种数据类型:1. 普通数据类型2. 引用数据类型3. 集合数据类型)
7. 配置文件模块化
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。
并列的多个配置文件:
ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","..."); |
主从配置文件
<import resource="applicationContext-xxx.xml"/> |
什么是同名bean ?它是如何产生的?
同一个xml中不能出现相同名称的bean,如果出现会报错
多个xml如果出现相同名称的bean,不会报错,但是后加载的会覆盖前加载的bean
应该如何避免同名Bean?
8.重点配置总结
<bean>标签:创建对象并放到spring的IOC容器 id属性:在容器中Bean实例的唯一标识,不允许重复 class属性:要实例化的Bean的全限定名 scope属性:Bean的作用范围,常用是Singleton(默认)和prototype <constructor-arg>标签:属性注入 name属性:属性名称 value属性:注入的普通属性值 ref属性:注入的对象引用值 <property>标签:属性注入 name属性:属性名称 value属性:注入的普通属性值 ref属性:注入的对象引用值 <list> <set> <array> <map> <props> <import>标签:导入其他的Spring的分文件 |
四.Spring注解开发
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
1. Spring常用注解分类
注解 | 说明 |
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用,根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
说明:
JDK11以后完全移除了javax扩展导致不能使用@resource注解。
需要maven引入依赖
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency |
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--注解的组件扫描--> <context:component-scan base-package="com.yang"></context:component-scan> |
2.注解实现
2.1 Bean实例化(IOC)
<bean id="userDao" class="com.yang.dao.impl.UserDaoImpl"></bean> |
使用@Compont或@Repository标识UserDaoImpl需要Spring进行实例化。
// @Component(value = "userDao") @Repository // 如果没有写value属性值,Bean的id为:类名首字母小写 public class UserDaoImpl implements UserDao { } |
2.2属性依赖注入(DI)
<bean id="userService" class="com.yang.service.impl.UserServiceImpl"> <property name="userDao" ref="userDaoImpl"/> </bean> |
使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; // <property name="userDao" ref="userDaoImpl"/> // @Autowired // @Qualifier("userDaoImpl") // @Resource(name = "userDaoImpl") public void setUserDao(UserDao userDao) { this.userDao = userDao; } } |
2.3 @Value
使用@Value进行字符串的注入,结合SPEL表达式获得配置参数
@Service public class UserServiceImpl implements UserService {
@Value("注入普通数据") private String str; @Value("${jdbc.driver}") private String driver; } |
2.4 @Scope
<bean scope=""/> |
使用@Scope标注Bean的范围
@Service @Scope("singleton") public class UserServiceImpl implements UserService {{
} |
2.5 Bean生命周期
<bean init-method="init" destroy-method="destory" /> |
使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法
@PostConstruct public void init(){ System.out.println("初始化方法...."); } @PreDestroy public void destroy(){ System.out.println("销毁方法....."); } |
五. Spring整合Junit
1. 普通Junit测试问题
在普通的测试类中,需要开发者手动加载配置文件并创建Spring容器,然后通过Spring相关API获得Bean实例;如果不这么做,那么无法从容器中获得对象。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = applicationContext.getBean(AccountService.class); |
我们可以让SpringJunit负责创建Spring容器来简化这个操作,开发者可以直接在测试类注入Bean实例;但是需要将配置文件的名称告诉它。
2.Spring整合Junit
步骤分析:
1. 导入spring集成Junit的坐标 2. 使用@Runwith注解替换原来的运行器 3. 使用@ContextConfiguration指定配置文件或配置类 4. 使用@Autowired注入需要测试的对象 5. 创建测试方法进行测试 |
2.1导入spring集成Junit的坐标
<!--此处需要注意的是,spring5 及以上版本要求junit 的版本必须是4.12 及以上--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> |
2.2使用@Runwith注解替换原来的运行器
@RunWith(SpringJUnit4ClassRunner.class) public class SpringJunitTest {
} |
2.3使用@ContextConfiguration指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class) //@ContextConfiguration(value = {"classpath:applicationContext.xml"}) 加载spring 核心配置文件 @ContextConfiguration(classes = {SpringConfig.class}) // 加载spring核心配置类 public class SpringJunitTest { } |
2.4使用@Autowired注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class}) public class SpringJunitTest {
@Autowired private AccountService accountService; } |
2.5创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class}) public class SpringJunitTest {
@Autowired private AccountService accountService;
//测试查询 @Test public void testFindById() { Account account = accountService.findById(3); System.out.println(account); } } |
六.AOP的理解
1.什么是AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程。
AOP 是 OOP(面向对象编程) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内。容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
这样做的好处是:
一. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
二. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
三.减少重复代码,提高开发效率,便于后期维护
2. AOP底层实现
实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
3. AOP相关术语
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
* Target(目标对象):代理的目标对象 * Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类 * Joinpoint(连接点):所谓连接点是指那些可以被拦截到的点。在spring中,这些点指的是方法,因为 spring只支持方法类型的连接点 * Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 * Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 分类:前置通知、后置通知、异常通知、最终通知、环绕通知 * Aspect(切面):是切入点和通知(引介)的结合 * Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织 入,而AspectJ采用编译期织入和类装载期织入 |
4.AOP开发明确事项
4.1.开发阶段(我们做的)
1. 编写核心业务代码(目标类的目标方法)切入点。
2. 把公用代码抽取出来,制作成通知(增强功能方法)通知。
3. 在配置文件中,声明切入点与通知间的关系,即切面。
4.2.运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
4.3.底层代理实现
在 Spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
当bean实现接口时,会用JDK代理模式
当bean没有实现接口,用cglib实现( 可以强制使用cglib(在spring配置中加入<aop:aspectj-
autoproxy proxyt-target-class=”true”/>)
5.总结
* aop:面向切面编程 * aop底层实现:基于JDK的动态代理 和 基于Cglib的动态代理 * aop的重点概念: Pointcut(切入点):真正被增强的方法 Advice(通知/ 增强):封装增强业务逻辑的方法 Aspect(切面):切点+通知 Weaving(织入):将切点与通知结合,产生代理对象的过程 |
七. XML配置AOP详解
1.快速入门
1.1步骤分析:
1. 创建java项目,导入AOP相关坐标 2. 创建目标接口和目标实现类(定义切入点) 3. 创建通知类及方法(定义通知) 4. 将目标类和通知类对象创建权交给spring 5. 在核心配置文件中配置织入关系,及切面 6. 编写测试代码 |
1.2. 创建java项目,导入AOP相关坐标
<dependencies> <!--导入spring的context坐标,context依赖aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <!-- aspectj的织入(切点表达式需要用到该jar包)--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!--spring整合junit--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> |
1.3. 创建目标接口和目标实现类
public interface AccountService { public void transfer(); } |
public class AccountServiceImpl implements AccountService {
@Override public void transfer() { System.out.println("转账业务..."); } } |
1.4. 创建通知类
public class MyAdvice { public void before() { System.out.println("前置通知..."); } } |
1.5. 将目标类和通知类对象创建权交给spring
<!--目标类交给IOC容器--> <bean id="accountService" class="com.yang.service.impl.AccountServiceImpl"> </bean> <!--通知类交给IOC容器--> <bean id="myAdvice" class="com.yang.advice.MyAdvice"></bean> |
1.6. 在核心配置文件中配置织入关系,及切面
导入aop命名空间
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--目标类交给IOC容器--> <bean id="accountService" class="com.yang.service.impl.AccountServiceImpl"> </bean> <!--通知类交给IOC容器--> <bean id="myAdvice" class="com.yang.advice.MyAdvice"></bean> <aop:config> <!--引入通知类--> <aop:aspect ref="myAdvice"> <!--配置目标类的transfer方法执行时,使用通知类的before方法进行前置增强--> <aop:before method="before" pointcut="execution(public void com.yang.service.impl.AccountServiceImpl.transfer())"></aop:before> </aop:aspect> </aop:config> </beans> |
1.7. 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws Exception { accountService.transfer(); } } |
2. XML配置AOP详解
2.1切点表达式
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数)) |
访问修饰符可以省略 返回值类型、包名、类名、方法名可以使用星号 * 代替,代表任意 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表 |
例如:
execution(public void com.yang.service.impl.AccountServiceImpl.transfer()) execution(void com.yang.service.impl.AccountServiceImpl.*(..)) execution(* com.yang.service.impl.*.*(..)) execution(* com.yang.service..*.*(..)) |
切点表达式抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替pointcut 属性来引用抽取后的切点表达式。
<aop:config> <!--抽取的切点表达式--> <aop:pointcut id="myPointcut" expression="execution(* com.yang.service..*.* (..))"> </aop:pointcut> <aop:aspect ref="myAdvice"> <aop:before method="before" pointcut-ref="myPointcut"></aop:before> </aop:aspect> </aop:config> |
2.2通知类型
通知的配置语法:
<aop:通知类型 method=“通知类中方法名” pointcut=“切点表达式"></aop:通知类型> |
名称 | 标签 | 说明 |
前置通知 | <aop:before> | 用于配置前置通知。指定增强的方法在切入点方法之前执 行 |
后置通知 | <aop:afterReturning> | 用于配置后置通知。指定增强的方法在切入点方法之后执 行 |
异常通知 | <aop:afterThrowing> | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | <aop:after> | 用于配置最终通知。无论切入点方法执行时是否有异常, 都会执行 |
环绕通知 | <aop:around> | 用于配置环绕通知。开发者可以手动控制增强代码在什么 时候执行 |
(注意:通常情况下,环绕通知都是独立使用的)
2.3总结
* aop织入的配置 <aop:config> <aop:aspect ref=“通知类”> <aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before> </aop:aspect> </aop:config>
* 通知的类型 前置通知、后置通知、异常通知、最终通知 环绕通知 * 切点表达式 execution([修饰符] 返回值类型包名.类名.方法名(参数)) |
八.注解配置AOP详解
1. 快速入门
步骤分析:
1. 创建java项目,导入AOP相关坐标 2. 创建目标接口和目标实现类(定义切入点) 3. 创建通知类(定义通知) 4. 将目标类和通知类对象创建权交给spring 5. 在通知类中使用注解配置织入关系,升级为切面类 6. 在配置文件中开启组件扫描和 AOP 的自动代理 7. 编写测试代码 |
1.1.创建java项目,导入AOP相关坐标
<dependencies> <!--导入spring的context坐标,context依赖aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.5.RELEASE</version> </dependency> <!-- aspectj的织入--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!--spring整合junit--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> |
1.2. 创建目标接口和目标实现类
public interface AccountService { public void transfer(); } |
public class AccountServiceImpl implements AccountService {
@Override public void transfer() { System.out.println("转账业务..."); } } |
1.3.创建通知类
public class MyAdvice { public void before() { System.out.println("前置通知..."); } } |
1.4. 将目标类和通知类对象创建权交给spring
@Service public class AccountServiceImpl implements AccountService {} @Component public class MyAdvice {} |
1.5. 在通知类中使用注解配置织入关系,升级为切面类
@Component @Aspect public class MyAdvice { @Before("execution(* com.yang..*.*(..))") public void before() { System.out.println("前置通知..."); } } |
1.6. 在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描--> <context:component-scan base-package="com.yang"/> <!--aop的自动代理--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> |
1.7. 编写测试代码
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws Exception { accountService.transfer(); } } |
2.注解配置AOP详解
2.1切点表达式
切点表达式抽取
@Component @Aspect public class MyAdvice { @Pointcut("execution(* com.yang..*.*(..))") public void myPoint(){} @Before("MyAdvice.myPoint()") public void before() { System.out.println("前置通知..."); } |
2.2通知类型
通知的配置语法:@通知注解(“切点表达式")
名称 | 标签 | 说明 |
前置通知 | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
异常通知 | @AfterThrowing | 用于配置异常通知。指定增强的方法出现异常后执行 |
最终通知 | @After | 用于配置最终通知。无论切入点方法执行时是否有异常,都会 执行 |
环绕通知 | @Around | 用于配置环绕通知。开发者可以手动控制增强代码在什么时候 执行 |
注意:
当前四个通知组合在一起时,执行顺序如下:
@Before -> @After -> @AfterReturning(如果有异常:@AfterThrowing)
2.3纯注解配置
@Configuration @ComponentScan("com.yang") @EnableAspectJAutoProxy //替代 <aop:aspectj-autoproxy /> public class SpringConfig { } |
2.4知识小结
* 使用@Aspect注解,标注切面类 * 使用@Before等注解,标注通知方法 * 使用@Pointcut注解,抽取切点表达式 * 配置aop自动代理 <aop:aspectj-autoproxy/> 或 @EnableAspectJAutoProxy |
九. Spring事务
1.Spring中的事务控制方式
Spring的事务控制可以分为编程式事务控制和声明式事务控制。
编程式
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
声明式
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。
2.编程式事务控制相关对象(不常用)
2.1 PlatformTransactionManager
PlatformTransactionManager接口,是spring的事务管理器,里面提供了我们常用的操作事务的方法。
方法 | 说明 |
TransactionStatus getTransaction(TransactionDefinition definition); | 获取事务的状态信息 |
void commit(TransactionStatus status); | 提交事务 |
void rollback(TransactionStatus status); | 回滚事务 |
注意:
* PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类。 * Dao层技术是jdbcTemplate或mybatis时: DataSourceTransactionManager * Dao层技术是hibernate时: HibernateTransactionManager * Dao层技术是JPA时: JpaTransactionManager |
2.2 TransactionDefinition
TransactionDefinition接口提供事务的定义信息(事务隔离级别、事务传播行为等等)
方法 | 说明 |
int getIsolationLevel() | 获得事务的隔离级别 |
int getPropogationBehavior() | 获得事务的传播行为 |
int getTimeout() | 获得超时时间 |
boolean isReadOnly() | 是否只读 |
2.3代码实现
2.3.1配置文件
<!--事务管理器交给IOC--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean |
2.3.2业务层代码
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Autowired private PlatformTransactionManager transactionManager; @Override public void transfer(String outUser, String inUser, Double money) { // 创建事务定义对象 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // 设置是否只读,false支持事务 def.setReadOnly(false); // 设置事务隔离级别,可重复读mysql默认级别 def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); // 设置事务传播行为,必须有事务 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 配置事务管理器 TransactionStatus status = transactionManager.getTransaction(def); try { // 转账 accountDao.out(outUser, money); accountDao.in(inUser, money); // 提交事务 transactionManager.commit(status); } catch (Exception e) { e.printStackTrace(); // 回滚事务 transactionManager.rollback(status); } } } |
2.3.3小结
Spring中的事务控制主要就是通过这三个API实现的
* PlatformTransactionManager 负责事务的管理,它是个接口,其子类负责具体工作 * TransactionDefinition 定义了事务的一些相关参数 * TransactionStatus 代表事务运行的一个实时状态 |
理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事务状态。
3. 基于XML的声明式事务控制(重中之中)
3.1介绍
在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。底层采用AOP思想来实现的。
声明式事务控制明确事项:
核心业务代码(目标对象) (切入点是谁?)
事务增强代码(Spring已提供事务管理器))(通知是谁?)
切面配置(切面如何配置?)
3.2快速入门
需求:
使用spring声明式事务控制转账业务。
步骤分析
1. 引入tx命名空间 2. 事务管理器通知配置 3. 事务管理器AOP配置 4. 测试事务控制转账业务代码 |
3.2.1引入tx命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w2.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/s chema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> </beans> |
3.2.2事务管理器通知配置
<!--事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--通知增强--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--定义事务的属性--> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> |
3.2.3事务管理器AOP配置
<!--aop配置--> <aop:config> <!--切面配置--> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.yang.serivce..*.*(..))"> </aop:advisor> </aop:config> |
3.2.4测试事务控制转账业务代码
@Override public void transfer(String outUser, String inUser, Double money) { accountDao.out(outUser, money); // 制造异常 int i = 1 / 0; accountDao.in(inUser, money); } |
3.3事务参数的配置详解
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/> * name:切点方法名称 * isolation:事务的隔离级别 * propogation:事务的传播行为 * timeout:超时时间 * read-only:是否只读 |
CRUD常用配置
<tx:attributes> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> |
4. 基于注解的声明式事务控制(重中之中)
4.1注解(注:配置用的xml)
步骤分析:
1. 修改service层,增加事务注解 2. 修改spring核心配置文件,开启事务注解支持 |
修改service层,增加事务注解
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false) @Override public void transfer(String outUser, String inUser, Double money) { accountDao.out(outUser, money); int i = 1 / 0; accountDao.in(inUser, money); } } |
修改spring核心配置文件,开启事务注解支持
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w2.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--省略之前datsSource、jdbcTemplate、组件扫描配置--> <!--事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
<!--事务的注解支持--> <tx:annotation-driven/> </beans> |
4.2纯注解
核心配置类
@Configuration // 声明为spring配置类 @ComponentScan("com.yang") // 扫描包 @Import(DataSourceConfig.class) // 导入其他配置类 @EnableTransactionManagement // 事务的注解驱动 public class SpringConfig { @Bean public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean("transactionManager") public PlatformTransactionManager getPlatformTransactionManager(@Autowired DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } } |
数据源配置类
@PropertySource("classpath:jdbc.properties") public class DataSourceConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public DataSource getDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } } |