Spring AOP–面向切面编程
面向对象(OOP)适合处理父子(纵向)关系,但是处理兄弟姐妹(横向)之间的关系不是很棒,导致了大量代码的重复,而不利于各个模块的重用。因此我们引用了横切技术(AOP),处理各个横切关系之间的调用,减少代码的重复使用,降低了模块之间的耦合度,利于可操作和可维护性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。
业务处理的主要流程是核心关注点
横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
AOP 的特性 (重点)
1、切面(Aspect):被抽取的公共模块,可能会横切多个对象。
2、连接点(Join point):触发时机:指方法在Spring AOP中,一个连接点总是代表一个方法的执行。
3、切入点(Pointcut):切入点是指我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。
**4、通知 (Advice):**当操作被触发要去执行的操作,所产生的日志 - 横切关注点。
5、引入 (Introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
6、目标对象 (Target Object):- 核心关注点:被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(adviced)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxy)对象
7、织入:目标对象被触发通知(Advice)的过程。
AOP 通知的分类
AOP 实现方式
xml 方式
1.导入依赖
<!--SpringAOP的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
2. 书写目标对象接口和实现类
目标对象接口
//在进行增删改时需要提交事务的方式设置成自动(触发)事务
//书写目标对象接口--核心关注点
public interface UserDAO {
void save();
void update();
void delete();
String find1();
String find2();
String find3();
}
实现类
@Repository
//书写实现类 new对象 实现UserDAO接口
public class UserDAOImpl implements UserDAO {
@Override
public void save() {
System.out.println("用户执行了save方法");
}
@Override
public void update() {
System.out.println("用户执行了update方法");
}
@Override
public void delete() {
System.out.println("用户执行了delete方法");
}
@Override
public String find1() {
System.out.println("用户执行了find1方法");
return null;
}
@Override
public String find2() {
System.out.println("用户执行了find2方法");
return null;
}
@Override
public String find3() {
System.out.println("用户执行了find3方法");
return null;
}
}
3.书写通知,这里写了前置和后置通知
新建一个advice包
//导入注解
@Component
//书写通知,这里写了前置和后置通知
public class FirstAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("这里是后置通知!我会在目标对象的方法执行后执行");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("这里是前置通知!我会在目标对象的方法执行前执行");
}
}
4.书写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"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!-- 等同于@Configuration 配置加载类 @ComponentScan() 手动扫描指定包-->
<context:annotation-config/>
<context:component-scan base-package="org.example.aop.xml.advice"/>
<context:component-scan base-package="org.example.aop.xml.target"/>
<!--书写 xml 的配置,设置切入点和切面等的关系-->
<!--定义一个切入点 pointcut-->
<bean id="pointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*find.*"/><!--规则:引入所有find(查询方法)-->
</bean>
<!--让切入点和通知关联-->
<bean id="pointcutAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="firstAdvice" /><!--当find方法被执行触发这个通知-->
<property name="pointcut" ref="pointcut"/>
</bean>
<!--设置代理 proxy代理了target指向的对象 在方法被执行之前|之后触发通知-->
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--被代理的对象-->
<property name="target" ref="userDAOImpl" />
<!--使用切面-->
<property name="interceptorNames" value="pointcutAdvisor"/>
<!--代理接口-->
<property name="proxyInterfaces" value="org.example.aop.xml.target.UserDAO" />
</bean>
</beans>
5.App测试
private static void a() {
ApplicationContext beans = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDAO userDAO = beans.getBean("proxy", UserDAO.class);
userDAO.find1();//执行find1方法
}
注解方式
@Aspect 注解
advice包中的类: @Before 前置触发 @AfterReturning 后置 @After 最终触发 @Around 环绕注解
@Component
@Aspect
public class SecondAdvice {
//这能是这个项目下在这个位置的包里的StudentDAOImpl的所有方法被触发
// @Before("execution(* org.example.aop.annotation.target.impl.StudentDAOImpl.*(..))")
//这能是这个项目下在这个位置的包里的StudentDAOImpl的find1()方法被触发
@Before("execution(* org.example.aop.annotation.target.impl.StudentDAOImpl.find1())")
public void before() {
System.out.println("我会在find1()方法执行之前触发");
}
@AfterReturning("execution(* org.example.aop.annotation.target.impl.StudentDAOImpl.find1())")
public void after() {
System.out.println("我会在find1()方法执行之后触发");
}
@After("execution(* org.example.aop.annotation.target.*.*.*(..))")
public void finally1(){
System.out.println("最终出发!");
}
@SneakyThrows
@Around("execute()")//环绕注解
public Object around(ProceedingJoinPoint pjp){
System.out.println("环绕之前");
Object obj = pjp.proceed();//执行目标对象的操作
System.out.println("环绕之后");
return obj;
}
}
定义切入点-== 给切入路径起一个别名
// 定义切入点 --- execute()是切入点的名字
@Pointcut("execution(* org.example.aop.annotation.target.*.*.*(..)))")
private void execute(){}
App测试
@Configuration
@EnableAspectJAutoProxy//自动产生引入 @Aspect 注解
Spring 的事务管理
**隔离性:**表示多个并发事务之间的数据要相互隔离
隔离级别:重点面试问题
隔离级别就是用来描述并发事务之间隔离程度的大小,在并发事务之间如果不考虑隔离性,会引发如下安全性问题:
- **脏读:**一个事务读到了没有提交的事务。没有提交的事务对数据库的影响是临时的
- **不可重复读:**一个事务读到了另一个事务已经提交的update的数据导致多次查询数据不一样。
- **幻读:**一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致
不可重复读和幻读的区别
不可重复读:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。也就是说,当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配,也就照应了不可重复读的语义。
幻读:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。也就是说,当前事务读第一次取到的数据比后来读取到数据条目少。
在 Spring 事务管理中,定义了如下的隔离级别:
- ISOLATION_DEFAULT:使用数据库默认的隔离级别。
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读。 - ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的。
传播行为
Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则。
是否只读
如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
事务超时
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒。
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚。
在框架中使用事务
1.在 xml 中注册事务管理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zugeNXYn-1676552763212)(C:\Users\49573\AppData\Roaming\Typora\typora-user-images\1676552397138.png)]
2.在 service 上引入注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6AommpdQ-1676552763214)(C:\Users\49573\AppData\Roaming\Typora\typora-user-images\1676552453399.png)]
使用默认传播行为:PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
1,其单位是秒。
回滚规则
回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚。
在框架中使用事务
1.在 xml 中注册事务管理
[外链图片转存中…(img-zugeNXYn-1676552763212)]
2.在 service 上引入注解
[外链图片转存中…(img-6AommpdQ-1676552763214)]
使用默认传播行为:PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。
回滚:当出现Exception异常,出现回滚:rollbackFor = Exception.class