Spring两大核心思想:IOC和AOP

目录

IOC:控制反转

Ioc概念

Ioc的优点

Spring Ioc

AOP:面向切面编程

AOP的优点

Spring AOP

1.添加依赖

2.核心概念

3.通知的类型

4.切点表达式

5.公共切点 @pointCut

6.切面优先级 @Order

7.使用自定义注解完成AOP的开发 

Spring AOP实现有几种方式?

Spring AOP 的原理,也就是Spring底层是如何实现AOP的?

Spring对于事物的实现

Spring事务隔离级别

事务传播机制

事务传播机制概念

Spring事务传播机制有哪些?

往外抛异常的几种情况

抛异常会影响的几种情况


IOC:控制反转

Ioc概念

先来说说IOC:IOC是一种思想。 控制反转,即对对象的控制权发生反转。即获取依赖对象的过程发生了反转。感觉就是传参的时候不再是单单传某个变量,而是直接传所要依赖的对象。这样不会再因为一处地方要增加参数或减少参数,而影响其它代码。耦合度降低。

Ioc的优点

  1. 集中管理
  2. 解耦合

Spring Ioc

Spring Ioc 则是实现了这种思想,获取依赖对象时 Spring Ioc 帮我们对对象做了管理。我们不再需要手动new对象,而是用的时候从Spring容器中取即可,即DI(依赖注入)。Spring ioc 可以理解为是 一个容器,可以用来存取对象。对对象的生命周期的控制权由程序员交给了Spring来管理。

对对象的控制权发生了变化,由之前的开发人员手动new对象,变为由Spring框架帮我们来管理对象,用的时候对象直接依赖注入(DI)。


AOP:面向切面编程

AOP是一种思想,是对一类事物的集中处理。比如:登录拦截请求,统一异常,同一结果返回等。而拦截器就是AOP的一种具体实现。AOP作用的维度更加细致(可以根据包、类、方法名、参数等进行拦截),能够实现更加复杂的业务逻辑。简单来说就是,AOP的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP就是代理模式的典型应用。Spring AOP就是AOP的一种实现方式,还有AspectJ,CGLIB。

AOP的优点

  1. 代码无入侵:不修改原始的业务方法,就可以对原始的业务方法进行功能的增强或者是功能的改变 。即解耦合。
  2. 减少了重复代码 。
  3. 提高开发效率 。
  4. 维护方便。

Spring AOP

1.添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

2.核心概念

  • 1.切点(PointCut):提供一组规则(切点表达式),告诉程序对哪些方法进行功能增强
  • 2.连接点(Join Point): 满足切点表达式规则的方法,就是连接点
  • 3.通知(Advice):对满足切点表达式的方法具体要做哪些事,比如下面的记录方法执行耗时    
  • 4.切面(Aspect):切面=切点+通知

切点和连接点的关系:切点就是满足切点表达式的所有方法,一个保存了许多连接点的集合,而连接点就是某一个方法,集合中的一个元素。

举例:全体同学上课

那切点就是:全体同学

连接点就是:张三,李四,某一个同学,其实就是执行目标方法

通知就是:上课

切面就是:整个定义的aop方法

  1. @Aspect:标识声明这是⼀个切面类
  2. @Around:环绕通知,在目标方法的前后都会被执行,后面的表达式表示对哪些方法进行增强
  3.  PrceedingJoinPoint.proceed()让原始方法执行

如下为记录一个方法执行耗时

@Aspect //声明该类为切面类
@Slf4j
@Component
@Order(2) //指定多个类的执行次序  值越小,优先级越高
//比如记录一个接口的执行时间
public class TestAspect {
   @Around("execution(* com.example.demo.controller.UserController.*.*(..))") //切点 生效 
                                          的范围 具体即某个接口,类或者包 自定义 切点表达式
    public Object record(ProceedingJoinPoint  pj) {  // pj 连接点
        /**
         * 以下为通知
         */
        // 记录开始时间
        long start = System.currentTimeMillis();
        // 目标方法执行 以下为标准写法
        Object result = null;
        try {
            result = pj.proceed(); //执行原方法
        } catch (Throwable e) {
            log.error(pj.toShortString()+"发生异常,e:",e);
        }
        // 日志打印切点执行时间
        log.info(pj.getSignature()+"cost time:"+(System.currentTimeMillis()-start)+"ms");
        return result;
    }

}

3.通知的类型

  1. @Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行 
  3. @After:后置通知,此注解标注的通知⽅法在目标方法后被执行,无论是否有异常都会执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

由上执行结果可知,执行顺序为:

1.当目标方法执行无异常时:

2.当目标方法执行有异常且throws,往出抛时:

3.当目标方法执行有异常且try...catch...,捕获异常时:

2和3区别在于手动捕获异常时环绕通知前后都会执行,而直接往出抛异常时环绕后的代码不会执行。


4.切点表达式

切点表达式有两种:

  1. execution(...) 根据方法匹配
  2. @annotation(...) 根据注解匹配

区别是:execution表达式更适用有规则的,如果我们要匹配多个无规则的方法,更适合用注解。比如仅仅要匹配两个不同类中的的某个方法,直接在该方法上加注解。

示例

  • 匹配UserController下的所有无参方法
execution(* com.example.demo.controller.UserController.*())
  •  UserController下的 public 修饰,返回类型为 String 方法名为 getUserInfo ,无参方法
execution(public String com.example.demo.controller.UserController.getInfo())

5.公共切点 @pointCut

一个切面类中可以包含多个切点,多个切点的切点表达式可能会重复,我们可以采用@PointCut把共同的切点表达式提取出来,使用时直接调用即可。

@Aspect
@Slf4j
@Component
public class TestAspect {

    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pt() {}

    @After("pt()")
    public void doAfter() {
        log.info("执行After...");
    }

    @Before("pt()")
    public void doBefore() {
        log.info("执行Before...");
    }

}

还有一种情况,当公共切点和想要调用的切点不在同一个切面类中时:

@Aspect
@Slf4j
@Component
public class TestAspect2 {
    /**
     * 当公共切点和调用处不在同一个切面类中时,要写出该公共切点的全限定方法名
     * 全限定方法名 = 路径(包名) + 类名 + 方法名
     */
    @After("com.example.demo.aspect.TestAspect3.pt()")
    public void doAfter() {
        log.info("执行After2...");
    }

    @Before("com.example.demo.aspect.TestAspect3.pt()")
    public void doBefore() {
        log.info("执行Before2...");
    }

}

6.切面优先级 @Order

当我们在项目中定义了多个切面类时,并且这个切面类的多个切点都匹配到了同一个目标方法。如果我们对执行顺序有要求的话,可以在切面类上使用@Order()注解来设置执行优先级。

@Aspect //声明为一个切面类
@Slf4j //日志打印
@Component //对象添加到Spring框架中
@Order(3) //切面优先级
public class TestAspect1 {

    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pt() {}

    @After("pt()")
    public void doAfter() {
        log.info("执行After1...");
    }

    @Before("pt()")
    public void doBefore() {
        log.info("执行Before1...");
    }

}

@Order()括号中的数字越小,优先级越高。越先执行。


7.使用自定义注解完成AOP的开发 

  • 1.先自定义一个注解
@Target(ElementType.METHOD) //作用范围 此处是方法
@Retention(RetentionPolicy.RUNTIME) //生命周期 此处是运行时
public @interface TestAnnotation {
}
  • 2.在切点表达式中加入该注解
@Aspect
@Component
@Slf4j
public class TestAspect {
    @Around("@annotation(com.example.demo.aspect.TestAnnotation)") //此处加自定义注解
    public Object record(ProceedingJoinPoint joinPoint) {
        log.info("around继续");
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            log.error("joinPoint.toShortString()+"发生异常,e:",e");
        }
        return result;
    }
}
  • 3.在目标方法上加自定义注解,这种就适合不规则的,想要匹配哪个方法就在哪个方法上添加注解。
    @TestAnnotation //在目标方法上添加自定义注解
    @RequestMapping("/getuserinfo")
    public void getUserinfo(HttpServletRequest request) {
        log.info("执行目标方法");
    }

Spring AOP实现有几种方式?

1.基于Aspect注解

    @Aspect注解是aspectJ提供的,Spring中也用到了这个注解,修饰类,声明这是一个切面类

2.基于自定义注解

  • 第一步:自定义一个注解,设置词注解的作用范围,生命周期,
  • 第二步:在切点表达式中加入该注解,
  • 第三步:在目标方法上加入自定义注解。想匹配哪个方法就在哪个方法上加注解,适合不规                   则的。

3.基于代理来实现

      具体是基于动态代理实现的,两种方式,一种是JDK动态代理,另一种是CGlib动态代理。两种都使用了,具体使用哪种方式,与代理对象(是否是一个实现了接口的类)和项目配置有关。


Spring AOP 的原理,也就是Spring底层是如何实现AOP的?

代理模式概念

也叫委托模式,就是提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。就好比生活中的明星都有经纪人,一些人想要找明星拍戏,代言等,都不是直接与明星谈的,而是找其经纪人。 代理模式又分为静态代理和动态代理。

静态代理:在程序运行前代理类就已经固定好了,即代理类的.class文件已经存在了。使用起来不灵活。

动态代理:与静态代理相比,动态代理更加灵活。不再是提前写好代理,而是由JVM运行时,程序运行过程中根据需要动态生成。即创建代理对象的时机是在运行时。动态代理底层又是基于反射实现的。

Spring AOP是基于动态代理实现的 ,有两种方式:

1.基于JDK动态代理。有缺陷,只能代理实现了接口的类,不能代理普通类。

2.基于CGlib动态代理。接口和普通类都可以代理。


运行时使用哪种方式与项目配置和代理对象有关。

存在一个属性 proxyTargetClass 可以使用注解或配置设置它,默认是false。

proxyTargetClass目标对象代理方式
false实现了接口JDK代理
false未实现接口(只有实现类)CGlib代理
true实现了接口CGlib代理
true未实现接口(只有实现类)CGlib代理

由上表可知,当属性为默认值false时,目标对象实现了接口的类就用JDK代理,未实现接口的类(普通类)就用CGlib代理。当属性值被设置为true时,无论是接口还是普通类都使用CGlib代理。

可以通过如下注解设置默认值:

@EnableAspectJAutoProxy(proxyTargetClass = true) //作用于类上

Springboot2.x版本以前默认是JDK代理,Springboot2.x版本开始,默认使用CGlib代理。可以在yml文件配置中修改,如下默认是true,即CGlib代理。修改为false,则是JDK代理。


Spring对于事物的实现

1.编程式(手动式):开启事务,提交事务,回滚事务

开启事务,相当于新开了一条分支,在分支上进行增删改查,并不会影响到主干,只有提交事务操作,才会合并到主干上,实现真正的增删改查。而回滚事务也是在分支上进行增删改查,不过并没有合并,销毁了这个分支。说是没有合并,但是自增主键上却是留有痕迹的。 从日志来看,回滚是比提交少了一条提交日志。 

2.声明式(注解) : 方法上添加 @Transactional 注解,将整个方法看做一个原子性操作,执行成功自动提交事务,发生异常等自动回滚。默认异常是属于运行时异常或Error时,才进行回滚。也可以如下设置。

@Transactional(rollbackFor = {Exception.class})  // 设置回滚的异常类型
  • 发生异常时,如果异常往外抛了,事务会进行回滚
  • 发生异常时,如果异常进行处理(即捕获 try...catch... )了(虽然捕获了异常,但若是throw异常,还是会回滚,重点是没有往外抛),事务会进行提交

总结:发生异常时,不考虑异常类型的情况下,事务提交还是回滚,就看异常有没有往外抛。就好比往外抛时注解感知到了,才回滚,如果内部发现并处理,并没有往外抛,外面也感知不到,所以就正常提交了。

那如果我们既想捕获,又不想事务提交,有两种办法:

  • 1.手动重新往外抛异常
    @Transactional
    @RequestMapping("/test")
    public String test() {
        // 捕获异常
        try {
           int a = 1/0; 
        }catch (Exception e) {
            log.error("发生了异常");
            throw e;  // 第一种 捕获到异常再往出抛异常
        }
        return "test";
    }
  • 2.手动回滚事务
    @Transactional
    @RequestMapping("/test")
    public String test() {
        // 捕获异常
        try {
           int a = 1/0; 
        }catch (Exception e) {
            log.error("发生了异常");
            //第二种 手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
        }
        return "test";
    }

@Transactional执行流程

  1. 方法执行前,开启事务
  2. 执行方法
  3. 若方法正常执行,事务进行提交;若方法出现异常,事务进行回滚

Spring事务隔离级别

Spring中事务隔离级别有5种:

  1. Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。Spring默认隔离级别
  2. Isolation.READ_UNCOMMITTE:读未提交
  3. Isolation.READ_COMMITTED:读已提交
  4. Isolation.REPEATABLE_READ:可重复读
  5. Isolation.SERIALIZABLE:串行化
@Transactional(isolation = Isolation.DEFAULT) // 手动设置隔离级别

事务传播机制

事务传播机制概念

其实就是多个加了事物的方法之间存在调用关系,多个事务方法存在调用关系时,事务是如何在这些方法间进行传播的。比如有两个方法A,B都被 @Transacional 修饰,且A调用了B,那么B方法在运行时,是加入A的事务,还是重新创建一个新事务呢?

Spring事务传播机制有哪些?

Spring事务传播机制共有7种,默认是Propagation.REQUIRED,可以通过Propagation修改传播机制规则。我的理解是其实主要给被调用的方法的事务设置参数的。

  1. Propagation.REQUIRED(加入事务):如果当前存在事务,则加入该事务。如果当前没有事务,则创建⼀个新的事务。即多个存在调用关系的方法都在同一个事务中,一起成功,一起失败Spring默认传播机制。
  2. Propagation.SUPPORTS (支持当前事务):如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。
  3. Propagation.MANDATORY :如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW (新建事务):如果当前存在事务,则把当前事务挂起。无论当前存不存在事务,只要被 Propagation.REQUIRES_NEW 修饰的方法都会开启一个新事物。且开启的事务相互独立,互不干扰,互不影响。比如有A,B两个方法,A调用了B,A虽然调用了B,但A,B是在两个不同的事务中,提交还是回滚取决于自己,互不影响。
  5. Propagation.NOT_SUPPORTED(不支持当前事务):以非事务方式,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED(嵌套事务):如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行。如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED 。多个存在调用关系且被 @Transcational 修饰的方法,其中某一个方法出现异常,在不考虑异常类型的情况下。若是异常抛出,那么全部都会回滚,此时作用和默认传播事务 REQUIRED 一样。若是内部处理,没让异常抛出并且手动进行了回滚, 那么出现异常的方法才会回滚,其他都正常提交。即实现局部回滚。若仅仅只是内部处理了异常,既没抛出,也没手动回滚,还是会正常提交的。
@Transactional(propagation = Propagation.REQUIRED) // 手动设置事务隔离级别

往外抛异常的几种情况

  1. 对异常没有处理。
  2. 虽然try...catch...捕获到了异常,但是又重新throw抛出了异常。

抛异常会影响的几种情况

  1. 如果是被调用方有异常抛出了,会影响调用方。
  2. 会影响自身,往外抛,自身也会感知到,并进行回滚。

一个方法内有异常但不抛异常,则自身感知不到异常,就会正常提交,不会回滚。想要进行回滚可以通过手动代码回滚。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/397368.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】类与对象(构造函数、析构函数、拷贝构造函数、常引用)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 类的6个默认成员函数 构造函数 特性 析构函数 特性 析构的顺序 拷贝构造函数 特性 常引用 前言 &…

家用电器液位开关有哪些

在当代家庭中&#xff0c;随着科技的进步和生活水平的提升&#xff0c;各种智能化家用电器成为家庭生活的重要组成部分。这些家用电器如空调、除湿机、加湿器、洗碗机、净水器等&#xff0c;在运作过程中往往需要对液体的高低位进行精确控制&#xff0c;以保证其正常、高效的运…

SpringBoot + Nacos 实现动态化线程池

1.背景 在后台开发中&#xff0c;会经常用到线程池技术&#xff0c;对于线程池核心参数的配置很大程度上依靠经验。然而&#xff0c;由于系统运行过程中存在的不确定性&#xff0c;我们很难一劳永逸地规划一个合理的线程池参数。 在对线程池配置参数进行调整时&#xff0c;一…

BioTech - 交联质谱 (Crosslinks) 的常见数据格式说明

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136190750 交联质谱 (Crosslinking Mass Spectrometry&#xff0c;Crosslinks) 技术 是一种结合化学交联剂和质谱仪的方法&#xff0c;用于研究蛋…

第十篇:node处理404和服务器错误

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录</

Vue3+vite搭建基础架构(10)--- 使用less和vite-plugin-vue-setup-extend

Vue3vite搭建基础架构&#xff08;10&#xff09;--- 使用less和vite-plugin-vue-setup-extend 说明官方文档安装less测试less表达式安装vite-plugin-vue-setup-extend 说明 这里记录下自己在Vue3vite的项目使用less来写样式以及使用vite-plugin-vue-setup-extend直接定义组件…

【8】知识加工

一、概述 对信息抽取/知识融合后得到的“事实”进行知识推理以拓展现有知识、得到新知识。 知识加工主要包括三方面内容&#xff1a;本体构建、知识推理和质量评估。 二、本体构建 1.本体 定义&#xff1a;本体是用于描述一个领域的术语集合&#xff0c;其组织结构是层次结…

设计模式四:适配器模式

1、适配器模式的理解 适配器模式可以理解为有两个现成的类Adaptee和Target&#xff0c;它们两个是不能动的&#xff0c;要求必须使用B这个类来实现一个功能&#xff0c;但是A的内容是能复用的&#xff0c;这个时候我们需要编写一个转换器 适配器模式 Adaptee&#xff1a;被适…

Java Web(一)--介绍

Java Web 技术体系图 三大组成部分&#xff1a; 前端&#xff1a; 前端开发技术工具包括三要素&#xff1a;HTML、CSS 和 JavaScript&#xff1b;其他高级的前端框架&#xff0c;如bootstrap、jquery&#xff0c;VUE 等。 后端&#xff1a; 后端开发技术工具主要有&am…

剑指offer刷题笔记-链表

少年何妨梦摘星 敢挽桑弓射玉衡 解决与链表相关的问题总是有大量的指针操作&#xff0c;而指针操作的代码总是容易出错的。很多面试官喜欢出与链表相关的问题&#xff0c;就是想通过指针操作来考察应聘者的编码功底。 题目链接来自于 AcWing 、Leetcode&#xff08;LCR&#xf…

燃气企业须知 :智慧燃气信息化管理平台的作用 ?

关键词&#xff1a;智慧燃气、燃气信息化、智慧燃气平台、智慧燃气建设、智慧燃气解决方案 智慧燃气信息化管理平台通过城市输气管网作为主要基础&#xff0c;各个终端用户进行协调&#xff0c;依托信息化数据进行管理&#xff0c;从而让整个工作环节流程达到高效智能优势。通…

【LeetCode】树的BFS(层序遍历)精选6题

目录 1. N 叉树的层序遍历&#xff08;中等&#xff09; 2. 二叉树的锯齿形层序遍历&#xff08;中等&#xff09; 3. 二叉树的最大宽度&#xff08;中等&#xff09; 4. 在每个树行中找最大值&#xff08;中等&#xff09; 5. 找树左下角的值&#xff08;中等&#xff09…

leetcode(动态规划)53.最大子数组和(C++详细解释)DAY12

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 提示 2.解答思…

数论 - 博弈论(Nim游戏)

文章目录 前言一、Nim游戏1.题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 2.算法 二、台阶-Nim游戏1.题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 2.算法 三、集合-Nim游戏1.题目描述输入格式输出格式数据范围输…

vue使用Nprogress进度条功能实现

下图中的这种顶部进度条是非常常见的&#xff0c;在vue项目中有对应的插件&#xff1a;Nprogress。 实现效果&#xff1a; csdn也在使用&#xff1a; 或者这样自己使用 1、安装 NProgress可以通过npm安装。 npm install --save nprogress 注意此处的--save等同于-s,就是将…

echats 时间直方图示例

需求背景 某订单有N个定时任务&#xff0c;每个任务的执行时间已经确定&#xff0c;希望直观的查看该订单的任务执行趋势 查询SQL&#xff1a; select UNIX_TIMESTAMP(DATE_FORMAT(exec_time,%Y-%m-%d %H:%i)) execTime, count(*) from order_detail_task where order_no 2…

【C++】vector模拟实现+迭代器失效

vector模拟实现 成员变量定义默认成员函数构造函数 迭代器范围for、对象类型匹配原则 容量操作sizeemptycapacityreserve成员变量未更新memcpy值拷贝 resize内置类型的构造函数 数据访问frontbackoperator[ ] 数据修改操作push_backpop_backswapclearinsertpos位置未更新无返回…

el-button 选择与非选择按钮批量处理

el-button 选择与非选择按钮批量处理 <el-button v-for"(voyage,i) in data[voyages][nowVoyage]":key"i"class"c-work-bts"type"primary":plain"nowWorkSpace!i"click"chooseWorkSpace(i)"size"small&qu…

C#快速配置NLog日志使用

首先我们需要在Nuget中安装Nlog和Nlog-Schema。 添加配置文件&#xff1a;NLog.config <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"http://www.nlog-project.org/schemas/NLog.xsd"xmlns:xsi"http://www.w3.org/2001…

CSS弹性布局

CSS弹性布局 一、概念 ​ 弹性盒子是 CSS3 的一种新的布局模式。 ​ CSS3 弹性盒&#xff08; Flexible Box 或 flexbox&#xff09;&#xff0c;是一种当页面需要适应不同的屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式。 ​ 引入弹性盒布局模型的目的是提供一…