spring的事务管理&spring框架的第二大核心AOP面向切面编程
spring框架的第一大核心是前面讲的IOC控制反转
事务管理
事务回顾
概念:事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。
Spring事务管理
@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。
@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。
@Transactional注解书写位置:
-
方法
-
当前方法交给spring进行事务管理
-
-
类
-
当前类中所有的方法都交由spring进行事务管理
-
-
接口
-
接口下所有的实现类当中所有的方法都交给spring 进行事务管理
-
一般选择加在业务层的增删改这一类方法上,准确来说,是加在业务层执行多次数据访问操作增删改这一类方法上
无需控制事务: 查询操作,一步简单的增删改操作(mysql数据库的事务是自动提交的
可以在application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了
事务进阶
rollbackFor
异常回滚的属性
默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。
假如我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。
propagation
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
AOP基础
概述
AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
而AOP面向方法编程,就可以做到在不改动这些原始方法的基础上,针对特定的方法进行功能的增强。
AOP的作用:在程序运行期间在不修改源代码的基础上对已有方法进行增强(无侵入性: 解耦)
不论运行的是那个业务方法,最后其实运行的就是我们定义的模板方法,而在模板方法中,就完成了原始方法执行耗时的统计操作 。(那这样呢,我们就通过一个模板方法就完成了指定的一个或多个业务方法执行耗时的统计)
这个流程,似曾相识,和我们之前所学习的动态代理技术是非常类似的。 我们所说的模板方法,其实就是代理对象中所定义的方法,那代理对象中的方法以及根据对应的业务需要, 完成了对应的业务功能,当运行原始业务方法时,就会运行代理对象中的方法,从而实现统计业务方法执行耗时的操作。
其实,AOP面向切面编程和OOP面向对象编程一样,它们都仅仅是一种编程思想,而动态代理技术是这种思想最主流的实现方式。而Spring的AOP是Spring框架的高级技术,旨在管理bean对象的过程中底层使用动态代理机制,对特定的方法进行编程(功能增强)。
动态代理技术是Java中一种非常强大的设计模式,它允许在运行时创建一个实现了一组给定接口的新类。动态代理的主要用途是在不修改原始对象的情况下,对方法调用进行增强或拦截。这种技术在AOP(面向切面编程)、事务管理、权限控制等方面有广泛的应用。
动态代理的基本概念
- 代理对象:动态生成的代理对象实现了与目标对象相同的接口。
- InvocationHandler:代理对象在调用方法时,会委托给一个实现了
InvocationHandler
接口的对象来处理。- Proxy 类:Java 提供了一个
Proxy
类,用于创建动态代理对象。工作原理
- 创建接口:定义一个或多个接口,这些接口将由目标对象和代理对象共同实现。
- 实现 InvocationHandler:创建一个实现了
InvocationHandler
接口的类,重写invoke
方法。- 创建代理对象:使用
Proxy.newProxyInstance
方法创建代理对象。示例代码
假设我们有一个简单的接口
UserService
和一个实现类UserServiceImpl
。1. 定义接口
public interface UserService {void addUser(String user);void deleteUser(String user); }
2. 实现接口
public class UserServiceImpl implements UserService {@Overridepublic void addUser(String user) {System.out.println("Adding user: " + user);}@Overridepublic void deleteUser(String user) {System.out.println("Deleting user: " + user);} }
3. 实现 InvocationHandler
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;public class UserServiceInvocationHandler implements InvocationHandler {private final UserService userService;public UserServiceInvocationHandler(UserService userService) {this.userService = userService;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before method: " + method.getName());Object result = method.invoke(userService, args);System.out.println("After method: " + method.getName());return result;} }
4. 创建代理对象
public class DynamicProxyDemo {public static void main(String[] args) {// 创建目标对象UserService userService = new UserServiceImpl();// 创建 InvocationHandlerUserServiceInvocationHandler handler = new UserServiceInvocationHandler(userService);// 创建代理对象UserService proxyUserService = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(),new Class[]{UserService.class},handler);// 调用代理对象的方法proxyUserService.addUser("Alice");proxyUserService.deleteUser("Bob");} }
输出
Before method: addUser Adding user: Alice After method: addUser Before method: deleteUser Deleting user: Bob After method: deleteUser
关键点解释
Proxy.newProxyInstance:
loader
:类加载器,通常使用目标对象的类加载器。interfaces
:代理对象需要实现的接口列表。h
:InvocationHandler
实例,用于处理方法调用。InvocationHandler.invoke:
proxy
:代理对象。method
:被调用的方法。args
:方法参数。动态代理的优势
- 灵活性:可以在运行时动态生成代理对象,无需在编译时确定。
- 扩展性:可以通过代理对象轻松添加新的功能,而不需要修改原始对象。
- 解耦:原始对象和代理对象之间解耦,使得代码更加模块化和可维护。
应用场景
- AOP(面向切面编程):在方法调用前后添加日志记录、事务管理等功能。
- 权限控制:在方法调用前检查用户权限。
- 缓存:在方法调用前检查缓存,避免重复计算。
- 性能监控:记录方法调用的时间,用于性能分析。
通过动态代理技术,可以灵活地扩展和增强现有功能,而无需修改原始代码,使得系统更加健壮和可维护。
模板方法当中定义的逻辑其实就是创建出来的代理对象方法中的逻辑
SpringAOP的底层就是通过动态代理技术完成针对于特定方法的编程
动态代理技术回头可能要补一下【立马补别拖】
AOP的优势:
-
减少重复代码
-
提高开发效率
-
维护方便
动态代理特点:无侵入式的给代码增加额外的功能
快速入门
java">@Component
@Aspect //当前类为切面类,即AOP类
@Slf4j
public class TimeAspect {@Around("execution(* com.itheima.service.*.*(..))") //切入点表达式public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行开始时间long begin = System.currentTimeMillis();//执行原始方法Object result = pjp.proceed();//记录方法执行结束时间long end = System.currentTimeMillis();//计算方法执行耗时log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);return result;}
}
-
事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务
没跟、后面项目实际运用到再写
面向特定的方法进行编程,可以在不改动原始方法的基础上,针对于原始的方法进行编程
动态代理技术是面向切面编程这种思想最主流的实现方式
SpringAOP底层就是基于动态代理技术针对于特定的方法进行编程
核心概念
1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的方法。
在SpringAOP提供的JoinPoint当中,封装了连接点方法在执行时的相关信息。
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
在入门程序中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。
但是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。
在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。
切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)
5. 目标对象:Target,通知所应用的对象
目标对象指的就是通知所应用的对象,我们就称之为目标对象。
无论你加不加aop注解,他都会创建一个代理,代理执行的方法和原来的方法内容一样,如果你加了aop注解,那么他会把你写的方法覆盖掉他的代理方法,执行
Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。
AOP进阶
通知类型
通知顺序
多个切面类当中的通知执行顺序
同一个切面类当中不同类型的通知执行顺序后面自己在研究
不用死记硬背,以项目的实际运行效果为准
切入点表达式
切入点表达式的语法规则:
-
方法的访问修饰符可以省略
-
返回值可以使用
*
号代替(任意返回值类型) -
包名可以使用
*
号代替,代表任意包(一层包使用一个*
) -
使用
..
配置包名,标识此包以及此包下的所有子包 -
类名可以使用
*
号代替,标识任意类 -
方法名可以使用
*
号代替,表示任意方法 -
可以使用
*
配置参数,一个任意类型的参数 -
可以使用
..
配置参数,任意个任意类型的参数
切入点表达式示例
-
省略方法的修饰符号
execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替返回值类型execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替包名(一层包使用一个*
)execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
-
使用
..
省略包名execution(* com..DeptServiceImpl.delete(java.lang.Integer))
-
使用
*
代替类名execution(* com..*.delete(java.lang.Integer))
-
使用
*
代替方法名execution(* com..*.*(java.lang.Integer))
-
使用
*
代替参数execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
-
使用
..
省略参数execution(* com..*.*(..))根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))
实现步骤:
-
编写自定义注解
-
在业务类要做为连接点的方法上添加自定义注解
java">@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
-
execution切入点表达式
-
根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
-
如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐
-
-
annotation 切入点表达式
-
基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了
-
连接点
java">@Slf4j
@Component
@Aspect
public class MyAspect7 {@Pointcut("@annotation(com.itheima.anno.MyLog)")private void pt(){}//前置通知@Before("pt()")public void before(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");}//后置通知@Before("pt()")public void after(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");}//环绕通知@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取目标类名String name = pjp.getTarget().getClass().getName();log.info("目标类名:{}",name);//目标方法名String methodName = pjp.getSignature().getName();log.info("目标方法名:{}",methodName);//获取方法执行时需要的参数Object[] args = pjp.getArgs();log.info("目标方法参数:{}", Arrays.toString(args));//执行原始方法Object returnValue = pjp.proceed();return returnValue;}
}
AOP案例
-
准备工作
-
引入AOP的起步依赖
-
导入资料中准备好的数据库表结构,并引入对应的实体类
-
-
编码实现
-
自定义注解@Log
-
定义切面类,完成记录操作日志的逻辑
-
代码实现细节: 获取request对象,从请求头中获取到jwt令牌,解析令牌获取出当前用户的id。
java">package com.wujiao.aop;import com.alibaba.fastjson.JSONObject;
import com.wujiao.mapper.OperateLogMapper;
import com.wujiao.pojo.OperateLog;
import com.wujiao.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.Arrays;@Slf4j
@Component
@Aspect
public class LogAspect {@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.wujiao.anno.Log)")public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人ID - 当前登录员工ID//获取请求头中的jwt令牌, 解析令牌String jwt = request.getHeader("token");Claims claims = JwtUtils.parseJWT(jwt);Integer operateUser = (Integer) claims.get("id");//操作时间LocalDateTime operateTime = LocalDateTime.now();//操作类名String className = joinPoint.getTarget().getClass().getName();//操作方法名String methodName = joinPoint.getSignature().getName();//操作方法参数Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);long begin = System.currentTimeMillis();//调用原始目标方法运行Object result = joinPoint.proceed();long end = System.currentTimeMillis();//方法返回值String returnValue = JSONObject.toJSONString(result);//操作耗时Long costTime = end - begin;//记录操作日志OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);operateLogMapper.insert(operateLog);log.info("AOP记录操作日志: {}" , operateLog);return result;}
}
也算是把昨天欠的债还上了