• Spring使用(三)


    1. AOP简介

    AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式,指导开发者如何组织程
    序结构。
    其作用是 在不改原有代码的前提下对其进行增强。 代理模式也有类似的作用。

    2. AOP入门案例

    • 定义接口BookDao和实现类BookDaoImpl
    1. @Repository
    2. public class BookDaoImpl implements BookDao {
    3. public void update(){
    4. System.out.println("book dao update ...");
    5. }
    6. public void delete(){
    7. System.out.println("book dao delete ...");
    8. }
    9. public void select(){
    10. System.out.println("book dao select ...");
    11. }
    12. }
    • 定义切面类MyAdvice
    1. package com.lan.aop
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Aspect // 设置当前类为AOP切面类
    7. public class MyAdvice {
    8. }
    • 在切面类中定义切入点,表示要切入的方法
    1. package com.lan.aop
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Aspect
    7. public class MyAdvice {
    8. // 切入点表示要切入的方法,要切入的方法叫连接点
    9. @Pointcut("execution(void com.lan.dao.BookDao.delete())")
    10. private void pt(){} // 该方法名随意
    11. }
    • 在切面类中定义通知和切面,切面是用来描述通知和切入点之间的关系
    1. package com.lan.aop
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.stereotype.Component;
    5. @Component
    6. @Aspect
    7. public class MyAdvice {
    8. // 切入点表示要切入的方法,要切入的方法叫连接点
    9. @Pointcut("execution(void com.lan.dao.BookDao.delete())")
    10. private void pt(){} // 该方法名随意
    11. @Before("pt()") // 定义切面,@Before表示通知在连接点执行前执行
    12. public void before(){ // 定义通知
    13. System.out.println(System.currentTimeMillis());
    14. }
    15. @After("pt()") // 定义切面,@After表示通知在连接点执行后执行
    16. public void after() {
    17. System.out.println("after execution");
    18. }
    19. @Around("pt()") // 定义切面,@Around表示连接点在通知的中间某一步执行
    20. public Object around(ProceedingJoinPoint pjp) throws Throwable {
    21. System.out.println("------------------------------");
    22. Long startTime = System.currentTimeMillis();
    23. for (int i = 0 ; i<10 ; i++) {
    24. //调用原始操作
    25. pjp.proceed();
    26. }
    27. Long endTime = System.currentTimeMillis();
    28. Long totalTime = endTime-startTime;
    29. System.out.println("执行10次消耗时间:" + totalTime + "ms");
    30. return null;
    31. }
    32. }
    • 开启AOP功能
    1. package com.lan.config;
    2. import org.springframework.context.annotation.ComponentScan;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.context.annotation.EnableAspectJAutoProxy;
    5. @Configuration
    6. @ComponentScan("com.lan")
    7. @EnableAspectJAutoProxy // 开启AOP功能
    8. public class SpringConfig {
    9. }
    • 运行程序
    1. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    2. // 此处的参数必须是接口类,不能是实现类BookDaoImpl,因为JDK Proxy代理的只能是接口
    3. // JDK Proxy可以参考 https://blog.csdn.net/werewolf2017/article/details/124950536
    4. BookDao bookDao = ctx.getBean(BookDao.class);
    5. bookDao.delete();

    3. AOP注解讲解

    注解作用
    @EnableAspectJAutoProxy开启注解格式AOP功能
    @Aspect设置当前类为切面类
    @Pointcut在切面类中设置切入点方法,value为切入点表达式
    @Before设置前置通知方法
    @Around设置环绕通知方法
    @After设置后置通知方法
    @AfterReturning设置返回后通知方法
    @AfterThrowing设置抛出异常后通知方法

    4. AOP实现原理

    AOP实际上是通过代理模式实现的,匹配到切入点的bean,spring会为其生成代理对象,其class是Proxy;没匹配到切入点的bean,其class是对应接口的实现类。输出bean的class对象可以验证:

    1. ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
    2. BookDao bookDao = ctx.getBean(BookDao.class);
    3. System.out.println(bookDao); // 无论是否匹配到切入点,对象都是BookDaoImpl
    4. // 匹配到切入点的bean,其class是Proxy;没匹配到切入点的bean,则其class是BookDao对应的实现类
    5. System.out.println(bookDao.getClass());

    5. AOP切入点表达式

    表达式格式:

    动作关键字 ( 访问修饰符 返回值 包名 . / 接口名 . 方法名 ( 参数 ) 异常名)

    以这个为例子

    execution(public User com.lan.service.UserService.findById(int))

    execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
    public:访问修饰符,还可以是public,private等,可以省略
    User:返回值,写返回值类型
    com.lan.service:包名,多级包使用点连接
    UserService:类/接口名称
    findById:方法名
    int:参数,直接写参数的类型,多个类型用逗号隔开
    异常名:方法定义中抛出指定异常,可以省略

    5.1 通配符

    可以使用通配符描述切入点,简化切入点描述

    • * 单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
    execution (public * com.lan.*.UserService.find*(*))
    匹配 com.lan 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法
    • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution (public User com..UserService.findById(..))
    匹配 com 包下的任意包中的 UserService 类或接口中所有名称为 findById 的方法
    • + :专用于匹配子类类型
    execution(* *..*Service+.*(..))
    这个使用率较低,描述子类的,咱们做 JavaEE 开发,继承机会就一次,使用都很慎重,所以很少
    用它。 *Service+ ,表示所有以 Service 结尾的接口的子类。

    6. 通知中获取参数

    JoinPoint :适用于前置、后置、返回后、抛出异常后通知
    1. @Before("pt()")
    2. public void before(JoinPoint jp) {
    3. Object[] args = jp.getArgs();
    4. System.out.println(Arrays.toString(args));
    5. System.out.println("before advice ..." );
    6. }
    ProceedingJoinPoint:适用于环绕通知
    1. @Around("pt()")
    2. public Object around(ProceedingJoinPoint pjp) {
    3. Object[] args = pjp.getArgs();
    4. System.out.println(Arrays.toString(args));
    5. args[0] = 666;
    6. Object ret = null;
    7. try {
    8. ret = pjp.proceed(args);
    9. } catch (Throwable t) {
    10. t.printStackTrace();
    11. }
    12. return ret;
    13. }

    设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同

    1. @AfterReturning(value = "pt()",returning = "ret")
    2. public void afterReturning(JoinPoint jp,String ret) {
    3. System.out.println("afterReturning advice ..."+ret);
    4. }

    设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同

    1. @AfterThrowing(value = "pt()",throwing = "t")
    2. public void afterThrowing(Throwable t) {
    3. System.out.println("afterThrowing advice ..."+t);
    4. }

    7. Spring事务管理

    Spring的事务是基于AOP实现事务管理,为了实现事务管理,提供了一个平台事务管理器PlatformTransactionManager,该接口包含了commit和rollback方法,一般使用jdbc的事务。

    7.1 开启注解式事务驱动

    使用@EnableTransactionManagement注解开启注解式事务驱动

    1. @Configuration
    2. @ComponentScan("com.lan")
    3. @PropertySource("classpath:jdbc.properties")
    4. @Import({JdbcConfig.class,MybatisConfig.class})
    5. //开启注解式事务驱动
    6. @EnableTransactionManagement
    7. public class SpringConfig {
    8. }

    7.2 创建平台事务管理器PlatformTransactionManager

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

    7.3 使用@Transactional注解开启事务

    在AccountService接口类加@Transactional注解表示该方法内的jdbc操作处于同一个事务中,注解也可以在实现类上加

    1. public interface AccountService {
    2. /**
    3. * 转账操作
    4. * @param out 传出方
    5. * @param in 转入方
    6. * @param money 金额
    7. */
    8. //配置当前接口方法具有事务
    9. @Transactional
    10. public void transfer(String out,String in ,Double money) ;
    11. }

    7.3.1 @Transactional的属性

    • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
    • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超 时时间。
    • rollbackFor:当出现指定异常进行事务回滚。需要注意的是,默认不配置rollbackFor的情况下,只会对Error和RuntimeException类型(包括其子类)的异常进行回滚,其它类型的异常不会回滚
    • noRollbackFor:当出现指定异常不进行事务回滚
    • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
    • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
    • isolation 设置事务的隔离级别

    7.3.2 事务的传播

    场景:在转账操作中,不管成功或失败,都记录操作日志到数据库中。代码如下

    1. @Transactional(rollbackFor=Exception.class)
    2. public void transfer(String out,String in ,Double money) {
    3. try {
    4. // 转账的数据库操作
    5. } finally {
    6. // 记录日志的数据库操作
    7. log();
    8. }
    9. }
    10. public void log() {
    11. }

    以上代码中,转账发生异常时失败时,记录日志的数据库操作也会被回滚,若将日志记录的操作放在一个新的事务中,则可以避免,这就涉及到事务的传播。修改后的代码如下

    1. @Transactional(rollbackFor=Exception.class)
    2. public void transfer(String out,String in ,Double money) {
    3. try {
    4. // 转账的数据库操作
    5. } finally {
    6. // 记录日志的数据库操作
    7. log();
    8. }
    9. }
    10. @Transactional(propagation = Propagation.REQUIRES_NEW) // 表示需要一个新的事务
    11. public void log() {
    12. }

    @Transaction的propagation属性可以指定事务的传播方式,事务的传播方式有六种,分别是REQUIRED(默认)、REQUIRED_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED

    以上面的transfer调用log为例,讲解两个方法的事务传播的影响:

    log的传播属性transfer事务log事务结果
    REQUIRED开启事务T加入事务T
    无事务新建事务
    REQUIRED_NEW开启事务T新建事务
    无事务新建事务
    SUPPORTS开启事务T加入事务T
    无事务无事务
    NOT_SUPPORTED开启事务T无事务
    无事务无事务
    MANDATORY开启事务T加入事务T
    无事务ERROR
    NEVER开启事务TERROR
    无事务无事务
    NESTED设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交或回滚

  • 相关阅读:
    高教社杯数模竞赛特辑论文篇-2023年C题:商超蔬菜销售数据的统计分析及建模(附获奖论文及R语言和LINGO代码实现)(续)
    spark中生成时间序列数据的函数stack和sequence
    CSM会议室预约系统源码
    mongodb模糊查询
    根据PPG估算血压利用频谱谱-时间深度神经网络【翻】
    【Java基础(应用篇)】JDBC
    NLP之Bert实现文本多分类
    uniapp websocket原生服务(自动重连、心跳检测) Ba-Websocket
    计算机毕业设计Java物联网实验课程考勤网站(源码+系统+mysql数据库+Lw文档)
    九大装修收纳空间的设计,收藏备用!福州中宅装饰,福州装修
  • 原文地址:https://blog.csdn.net/werewolf2017/article/details/126155411