5.1 Spring_AOP_事务

本文详细介绍了AOP的基本概念、工作流程,涵盖动态代理、切入点表达式、通知类型及案例,包括业务接口执行效率提升和数据兼容处理。深入理解了Spring事务管理和AOP在实际项目中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AOP

1.1 AOP简介和作用【理解】

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP(Object Oriented Programming)面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
  • Spring理念:无入侵式/无侵入式

面向对象(OOP)->面向切面(AOP)

面向切面(AOP): 是一种编程思想

动态代理

动态代理: 其实就是一个(Proxy)类,使用这个类可以在不修改源代码的基础上对功能进行增强!!!

在这里插入图片描述

面向切面

面向切面(AOP):    底层就是使用动态代理实现的!!!
面向切面编程思想:  在不修改源代码的基础上对功能进行增强!!!

其他概念

连接点:  所有方法
切入点:  被增强的方法
通知:    增强的代码
通知类:  工具类 
切面:    绑定切入点和通知的关系

1.2 AOP入门

开发步骤

1)导包

2)开启AOP注解功能

3)编写工具类(通知类)

4)其他

【第一步】导入aop相关坐标
<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

在这里插入图片描述

【第二步】定义dao接口与实现类
public interface BookDao {
    public void save();
    public void update();
}

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
    public void update(){
        System.out.println("book dao update ...");
    }
}
【第三步】定义通知类,制作通知方法
//通知类必须配置成Spring管理的bean
@Component
public class MyAdvice {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}
【第四步】定义切入点表达式、配置切面(绑定切入点与通知关系)
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,@Pointcut注解要求配置在方法上方
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

在这里插入图片描述

【第五步】在配置类中进行Spring注解包扫描和开启AOP功能
@Configuration
@ComponentScan("com.itheima")
//开启注解开发AOP功能
@EnableAspectJAutoProxy
public class SpringConfig {
}
测试类和运行结果
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.update();
    }
}

在这里插入图片描述

1.3 AOP工作流程【理解】

疑难点

1)为什么要导入AspectJ包

spring  实现了面向切面这种编程思想
AspectJ 也实现了面向切面这种编程思想

AspectJ实现的比较好

2)@EnableAspectJAutoProxy的作用

在这里插入图片描述

3)强调什么是切面

在这里插入图片描述

4)切入点只能指定一个方法么?

//不是的, 可以使用通配符
@Pointcut("execution(void com.itheima.dao.BookDao.*())")

AOP工作流程

在这里插入图片描述

1.4 AOP详解

AOP切入点表达式

//1.严格匹配
@Pointcut("execution(void com.itheima.service.OrderService.placeOrder())")

//2.使用*匹配, 代表匹配单个  (返回值、包名、类名、方法名、参数 都可以匹配)
    //还可以匹配 add*   *Dao  以xxx开头/ xxx结尾
@Pointcut("execution(* com.itheima.service.OrderService.*())")

//3.使用..匹配,代表可以匹配多级, 0个、1个、多个 (一般用来匹配 包名、参数)
@Pointcut("execution(void com..service.OrderService.placeOrder(..))")

//4.常见写法
@Pointcut("execution(* com.itheima.service.*.*(..))")

AOP通知类型

AOP通知分类
  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型
    • 前置通知:在切入点方法执行之前执行
    • 后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行。
    • **环绕通知(重点):**手动调用切入点方法并对其进行增强的通知方式。
    • 返回后通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
    • 抛出异常后通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
AOP通知详解
前置通知
  • 名称:@Before
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
  • 范例:
@Before("pt()")
public void before() {
    System.out.println("before advice ...");
}
后置通知
  • 名称:@After
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
  • 范例:
@After("pt()")
public void after() {
    System.out.println("after advice ...");
}
返回后通知
  • 名称:@AfterReturning(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
  • 范例:
@AfterReturning("pt()")
public void afterReturning() {
    System.out.println("afterReturning advice ...");
}
抛出异常后通知
  • 名称:@AfterThrowing(了解)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
  • 范例:
@AfterThrowing("pt()")
public void afterThrowing() {
    System.out.println("afterThrowing advice ...");
}
环绕通知
  • 名称:@Around(重点,常用)
  • 类型:方法注解
  • 位置:通知方法定义上方
  • 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
  • 范例::
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("around before advice ...");
    Object ret = pjp.proceed();
    System.out.println("around after advice ...");
    return ret;
}

环绕通知注意事项

  1. 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
  2. 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。
AOP通知位置

脑海中要清楚try{}catch(){}代码块
在这里插入图片描述

执行时机
前置:在原始方法运行之前执行
后置:在原始方法运行之后执行  -  不管原始方法有没有出现异常都会执行
异常:在原始方法运行出现异常的情况下才会执行
返回后:在原始方法运行之后执行 -  但是原始方法出现异常的情况下就不会执行了

在这里插入图片描述

环绕(重点)
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    Object ret = null;
    System.out.println("前置通知 ...");

    try {
        //表示对原始操作的调用
        ret = pjp.proceed();

        System.out.println("返回后通知...");
    } catch (Throwable throwable) {
        System.out.println("异常通知...");
    }

    System.out.println("后置通知...");
    return ret;
}
pjp 就是对原始操作的一个封装, 比直接使用反射方便很多!!!

1.5 AOP案例

案例-业务接口执行效率

@Component
@Aspect
public class ProjectAdvice {
    //匹配业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private  void servicePt(){}

    //设置环绕通知,在原始操作的运行前后记录执行时间
    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        //获取执行的签名对象
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = pjp.getArgs(); //参数


        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
    }

}
pjp对原始操作进行了封装,  封装了原始方法的类名、方法名、参数、返回值  还可以使用pjp执行原始方法
AOP通知获取数据
1)ProceedingJoinPoint 只能在环绕通知中使用
2)每一种通知都可以拿到参数
3)返回值,只有环绕通知和返回后通知可以拿到返回值
4)异常,只有环绕通知和异常可以拿到异常信息
//JoinPoint:用于描述切入点的对象,必须配置成通知方法中的第一个参数,可用于获取原始方法调用的参数
@Before("pt()")
public void before(JoinPoint jp) {
    Object[] args = jp.getArgs();
    System.out.println(Arrays.toString(args));
    System.out.println("before advice ..." );
}

@After("pt()")
public void after(JoinPoint jp) {
    Object[] args = jp.getArgs();
    System.out.println(Arrays.toString(args));
    System.out.println("after advice ...");
}

//ProceedingJoinPoint:专用于环绕通知,是JoinPoint子类,可以实现对原始方法的调用
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));
    args[0] = 666;
    Object ret = null;
    try {
        ret = pjp.proceed(args);
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return ret;
}

//设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
    System.out.println("afterReturning advice ..."+ret);
}

//设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
    System.out.println("afterThrowing advice ..."+t);
}

案例-百度网盘密码数据兼容处理

@Component
@Aspect
public class DataAdvice {

    @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
    private void servicePt() {
    }

    @Around("servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            //判断参数是不是字符串
            if (args[i].getClass().equals(String.class)) {
                args[i] = args[i].toString().trim();
            }
        }
        
        Object ret = pjp.proceed(args);
        return ret;
    }

}
1)理解AOP思想:  在不修改源代码的基础上对功能进行增强!!!
2)理解入门案例:  为什么导入aspectj包, @EnableAspectJAutoProxy注解的作用, @Aspect注解的作用
3)环绕通知的应用

切入点表达式  、 其他的通知类型  了解就行!!!  百度查资料能用就行了!!!

Spring事务

什么是事务?

mysql事务,是mysql提供的一个功能
功能:可以保证多条SQL语句同时成功同时失败!!!

spring事务

spring事务 : 就是对mysql事物的一个封装

spring事务开发步骤

步骤:

1, 开启事务功能

2, 配置事务管理器

3, 在需要事务的方法上面添加注解

1, 开启事务功能

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

2, 配置事务管理器

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

3, 在需要事务的方法上面添加注解

@Transactional
public void transfer(String out,String in ,Double money) ;

事务管理器

1)事务管理器是个什么东西

在这里插入图片描述

2)事务管理器的作用

在这里插入图片描述

spring事务角色

事务控制是在service层
在这里插入图片描述

事务管理员: 外层的事务方法,我们起了一名字事务管理员
事务协调员: 里层的事务方法,我们起了一名字事务协调员

spring事务属性

@Transactional注解的属性

@Transactional(readOnly = true,timeout = -1,rollbackFor = IOException.class)

//1)readOnly = true   只读,适用于查询操作
//2)timeout = 2       和数据库连接的超时时间,单位S.  默认-1,代表永不超时,永久等待
//3)rollbackFor       指定回滚的异常
//                    默认并不是所有的异常都会回滚,默认RuntimeException本身和RuntimeException子类的异常
//                    才会回滚

事务的传播行为(了解)

在这里插入图片描述
在这里插入图片描述

案例:转账业务追加日志

需求和分析

  • 需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
  • 需求微缩:A账户减钱,B账户加钱,数据库记录日志
  • 分析:
    ①:基于转账操作案例添加日志模块,实现数据库中记录日志
    ②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能
  • 实现效果预期:
    无论转账操作是否成功,均进行转账操作的日志留痕
  • 存在的问题:
    日志的记录与转账操作隶属同一个事务,同成功同失败
  • 实现效果预期改进:
    无论转账操作是否成功,日志必须保留
  • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度
    在这里插入图片描述

【准备工作】环境整备

USE spring_db;
CREATE TABLE tbl_log(
	id INT PRIMARY KEY AUTO_INCREMENT,
	info VARCHAR(255),
	createDate DATE
);
public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional
    void log(String out, String in, Double money);
}

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

public interface LogDao {
    @Insert("insert into tbl_log (info,createDate) values(#{info},now())")
    void log(String info);
}

【第一步】在AccountServiceImpl中调用logService中添加日志的方法

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LogService logService;

    public void transfer(String out,String in ,Double money) {
        try{
            accountDao.outMoney(out,money);
            int i = 1/0;
            accountDao.inMoney(in,money);
        }finally {
            logService.log(out,in,money);
        }
    }
}

【第二步】在LogService的log()方法上设置事务的传播行为

public interface LogService {
    //propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money);
}

【第三步】运行测试类,查看结果

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",50D);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值