Spring相关知识【知识整理】

Spring 篇

前置知识

OCP开闭原则

在软件开发过程中应当对扩展开放,对修改关闭。也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。

依赖倒置原则DIP

依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务)

你可能会有大大的问号?以前的Servlet的dao接口service接口等不是面向接口的吗?Servlet是面向接口,但我们创建接口实现类的时候是直接通过new关键字关键字来创建然后直接赋值绑定的。一旦有变动就要更改源代码,显然这是不灵活的,如果我们以后只是调用接口而不再创建对象,这就规避了不灵活的缺点,但会出现null指针异常,而Spring框架就完美的解决对象创建、赋值绑定、管理等问题。

控制反转IoC

控制反转(Inversion of Control,简称:ioc),是一种面向对象编程的一种设计,主要用来降低代码之间的耦合度,符合依赖倒置原则(各个层次之间不直接依赖)

  • 核心:将对象创建的控制权、对象与对象之间的管理权交出去,由第三方容器来负责创建与维护。
  • 实现方式:依赖注入,常用的有以下两种方式
    • set方式注入
    • 构造方法注入

Spring就是一种实现IoC思想的框架

Spring的8大模块

Spring对IoC的具体实现

  • 控制反转,反转的是什么?
    • 将对象的控制权交、维护管理的权力交给spring容器解决,如何交呢?通过依赖注入(具体:在spring配置文件中配置bean标签绑定类)
  • 依赖注入:
    • 构造注入:spring通过解析这个配置文件来获取类信息,然后通过反射机制来创建该类的对象实例(调用构造方法),创建好后放入spring的Map容器里,我们只需要通过getBean()方法来获取实例
    • set注入:解析完配置文件后,通过bean标签的子标签property的属性name获取属性名,通过属性名知道set方法的名字,然后通过反射机制调用该方法给该对象的成员变量赋值

配置文件中的bean注入的写法

  • 属性注入时:

    • 若属性是普通的基本数据类型,则直接使用property标签的value=""属性赋值或property文本内容处用value标签
    • 若属性是自定义类型,则用property标签的ref属性或property文本内容中嵌套bean标签
    • 若属性是数组
      • 数组元素简单类型:使用value标签
      • 若数组元素非简单类型:使用ref标签
    • 若属性是List类型:使用list标签
  • 若属性是set集合 :使用set标签,set集合中元素是简单类型的使用value标签,反之使用ref标签。

  • 若属性是Map集合

  • 若属性是Properties :java.util.Properties继承java.util.Hashtable,所以Properties也是一个Map集合

  • 注入null和空字符串

    • 属性注入空字符串:
      • 法一:标签的属性value=""即可
      • 法二:value标签啥也不写
    • 属性注入null
      • 法一:不配置任何属性的标签,利用Java的成员变量初始化后有默认值
      • 法二:使用null标签
  • 注入的值有特殊符号时,解决方法

    • 法1:特殊符号使用转义字符代替
    • 法2:将含有特殊符号的字符串放到下图中位置,因为放在cdata区中的数据不会被XML文件解析器解析(使用CDATA时,不能使用value属性,只能使用value标签。)
  • p命名空间注入:目的是为了简化set注入方式的配置,使用该规则的有如下步骤

    • 第一步:开启命名空间(在xml头部信息中添加p命名空间的配置信息:xmlns:p="http://www.springframework.org/schema/p")
    • 第二步:p命名空间注入是基于setter方法的,所以需要对应的属性提供setter方法。
    • 语法:在bean标签中:p:属性名="值"
  • c命名空间注入:目的为了简化构造注入方式的配置

    • 第一步:开启命名空间...
    • 第二步:需要提高有参构造方法
    • 语法:c:_索引="值",索引从0开始,对应构造函数的参数(从左到右)
      注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。
  • util命名空间:目的为了配置的复用

    • 第一步:开启命名空间...
    • 第二步:在bean标签外单独使用util标签,id就是该标签的身份证,坐标。
  • 基于XML的自动装配

    • 理解:Spring还可以完成自动化的注入,自动化注入又被称为自动装配。它可以根据名字进行自动装配,也可以根据类型进行自动装配。(不管哪种装配,只要是自动装配都依赖实体类的setXxx方法,所以必须提供setXxx方法)
    • 根据属性名装配:使用bean的属性autowire="byName"来开启自动名字装配,用类的属性名去和xml中其他的bean的id值匹配,若一致则,把这个bean对象赋值给该属性。(下图中UserService类的UserDao类型的成员变量名为aaa)
    • 根据类型装配:使用bean的属性autowire="byType"来开启自动类型装配。这种方式会自动在spring容器中根据实体类属性的类型来查找到需要对象。
  • spring中引入外部属性配置文件:例jdbc.properties等,一般用于配置数据源什么的

    • 第一步:导入context命名空间,然后使用context标签来引入外部属性配置文件

    • <context:property-placeholder location="jdbc.properties"/>

    • 第二步:创建一个实体类实现DataSource接口

    • 第三步:在xml配置文件中采用${properties文件中的key}来获取配置文件中的value

  • 容器中注入一个工厂类

    • 注入静态工厂时:使用Bean标签的factory-method="静态工厂里的静态方法名",你通过bean的id获取实例时,工厂不被实例化,会调用factory-method指定的方法,返回一个其他的实例对象
    • 注入实例工厂时
      • 第一步:先把工厂在容器中实例化(配置bean标签)
      • 第二步:再写一个bean,同时添加factory-bean="实例工厂bean的id",factory-method="工厂里的实例方法名"
    • 替代上面两种的方法(不需使用上面两个属性):编写一个类实现FactoryBean接口并制定泛型,实现接口中getObject方法,然后把该类注入到容器中(配置Bean标签)
    • 区别FactoryBean和BeanFactory两个接口,前者叫工厂Bean用来辅助spring实例化其他Bean对象的一个Bean对象,后者叫Bean工厂,用来创建生产Bean对象的一个工厂,是工厂。
  • depends-on:这个是bean的一个属性,值是其他bean的id(可以有多个,逗号隔开),作用:depends-on属性可以显式强制初始化一个或多个 bean

  • 方法注入:(很少使用)

    • 理解:方法注入是 Spring 提供的一种特殊依赖注入方式,它允许容器在运行时动态替换或实现 Bean 的方法。当一个单例bean需要每次调用时,它的内部多例的实例成员能生效(因为单例的bean只会初始化一次,那么成员属性也只是赋值一次,那么想要每次调用这个bean中时,对应的实例属性值都一个新的对象)
    • 解决方法:
      • 法1:在一个bean标签里使用子标签:<lookup-method name="填可被重写的方法名(该方法返回实例成员属性)" bean="id",就是告诉spring,运行时请帮我生产一个实现类(cglib来做),该类重写name指定的方法,每次返回一个新的该实例成员对象。
      • 法2:用注解@Lookup,即查即用
      • 法3:其他替代方法:使用 ObjectFactory或 Provider(推荐)
  • 注入Date

    • 在spring中,java.util.Date类型被当作字符串来处理,而在spring中对于简单类可以使用property标签的value属性或者value标签来进行属性注入。对于Date注入时,value的值必须为"Mon Oct 10 14:30:26 CST 2002"这种格式,那么如何自定这个Date?使用FactoryBean接口
    • 第一步:写一个类实现该接口,定义字符串属性,实现getObject方法,在该方法里自定义Date格式,方法返回一个Date对象,把该类注入容器成为一个Bean对象
    • 第二步:其他Bean对象的实例成员ref该对象时,会得到自定义格式的Date实例

GOF设计模式

分为三大类,总计23个设计模式

  • 建造型(5):解决对象创建问题
    ■ 单例模式
    ■ 工厂方法模式
    ■ 抽象工厂模式
    ■ 建造者模式
    ■ 原型模式
  • 结构型(7):一些类和结构组合的经典结构
    ■ 代理模式
    ■ 装饰模式
    ■ 适配器模式
    ■ 组合模式
    ■ 享元模式
    ■ 外观模式
    ■ 桥接模式
  • 行为型(11):解决类和对象之间的交互问题
    ■ 策略模式
    ■ 模板方法模式
    ■ 责任链模式
    ■ 观察者模式
    ■ 迭代子模式
    ■ 命令模式
    ■ 备忘录模式
    ■ 状态模式
    ■ 访问者模式
    ■ 中介者模式
    ■ 解释器模式

Bean的作用域(scope属性指定)

  • singleton(单例):每次spring初始化的时候都创建bean实例对象(这时未调用),spring中默认情况下是bean对象是单例的
  • prototype(多例):每次从容器里获取该实例对象都会创建一个新的实例对象(调用时才创建,即使用getBean方法时,而容器初始化阶段不创建)
  • request:一个请求对应一个Bean。仅限于在WEB应用中使用
  • session:一个会话对应一个Bean。限于web应用中使用
  • application:一个应用对应一个Bean。仅限于在WEB应用中使用。
  • websocket:一个websocket生命周期对应一个Bean。仅限于在WEB应用中使用。
  • global session:portlet应用中专用的。如果在Servlet的WEB应用中使用global session的话,和session一个效果。(portlet和servlet都是规范。servlet运行在servlet容器中,例如Tomcat。portlet运行在portlet容器中。)
  • 自定义scope:很少使用。

Bean的生命周期

生命周期的本质是:在哪个时间节点上调用了哪个类的哪个方法。我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点。只有我们知道了特殊的时间节点都在哪,到时我们才可以确定代码写到哪。我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。

生命周期粗略分为五步:

  • 实例化Bean(构造方法执行)
  • Bean属性赋值(容器自动调用setXxx方法注入)
  • 初始化Bean(使用init-method方法指定Bean中的方法名)
  • 使用Bean(用户操作)
  • 销毁Bean(使用destory-method方法指定Bean中的方法名)

生命周期7步:

  • 如果想要初始化Bean之前和之后执行某段代码,则需要编写一个类实现BeanPostProcessor接口并实现它的方法(before方法和after方法里嵌入你的代码)

生命周期10步:

  • 在七步的中的后处理器before方法执行之前留个时间点,之后也留个时间点;执行销毁destory方法之前留个时间点,共计三个,加上之前的7步,总共10步。很少用,用时即查

88Bean的作用域不同spring的管理方式也有区别

  • Singleton:对于单例作用域的bean,spring能够精确的知道何时创建、何时初始化完成、以及何时被销毁
  • 对于prototype作用域的Bean,spring只是负责创建,之后就交给用户了,spring不再跟踪其生命周期

自己new的对象如何交给spring管理?

  • 1,创建DefaultListableBeanFactory对象,调用它的registerXxx("bean的id",自己new的对象),这样就把自己new的对象交给spring了
  • 2,从容器中获取:调用DefaultListableBean对象的getBean方法获取

Bean的循环依赖

问题:

  • 容器实例化A时,发现需要B实例,那么在容器中去找B实例,没有就实例化B,发现实例化B有需要A...依次无限套娃循环
    spring如何解决的?
  • 采用缓存和暴露机制
  • 理解:
    • Spring 通过三级缓存的机制来解决单例 Bean 的循环依赖问题,但仅限于字段注入和 Setter 注入,构造器注入的循环依赖 Spring 会直接抛出异常。
    • 具体来说:调用A的构造器实例化A创建A的原始对象(这时属性还未赋值),然后把这个对象封装成ObjectFactory放入三级缓存,接着填充A的属性时发现需要B,于是去创建B并把它放入三级缓存,然后去填充B,发现B需要A,于是从1->2->3级缓存里找A,在三级缓存中拿到A(通过早期工厂得A的早期对象-半成品),把这个半成品A放入二级缓存(并删除三级里的A),然后把它注入到B中完成B的实例化,然后B移动到一级缓存(三级中的删除了),B构建完了回到A,A拿到完整B注入属性中,完成A的初始化并把A放入一级缓存

      (重点关注:实例化和属性赋值是分开完成

三级缓存的作用:

  • 一级缓存:存放完好的Bean实例
  • 二级缓存:存放提前暴露的早期半成品引用
  • 三级缓存:存放创建Bean的工厂,用于生产早期引用

    以上的三个缓存本质上就是三个Map集合(不可能是os系统里的cache)

Spring注解开发

负责声明Bean的注解有四个:

  • @Component
  • @Controller(用在控制器类(该层次负责处理请求和校验参数))
  • @Service(用在处理业务逻辑的层次上(service),比如事务、加密...
  • @Repository(用在持久化层(dao类上)

注:后面三个注解其实都是注解Component的别名,也就是说这四个注解用哪个功能都一样,只是为了增强程序的可读性,从而在对应的层次位置使用对应的注解。这几个注解都只有一个value成员属性,用来指定bean的id,若省略不写,则默认id为首字母小写类名

以上注解如何使用?

  • 第一步:加入aop依赖(这个依赖被spring-context依赖关联,其实只需加入spring-context依赖即可
  • 第二步:在配置文件中添加context的命名空间
  • 第三步:在配置文件中指定扫描的包
    <context:component-scan base-package="Bean类所在包路径1,路径2,路径3"/>
  • 第四步:在Bean类上使用注解

选择性实例化Bean

  • 假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?
  • 使用context标签的use-default-filters="true/false",默认不写表示true,该属性控制开启或者关闭Spring默认的实例Be化an的规则(只要含有前文的四大注解之一,就会被容器接管实例化,即true决定注解生效,false决定注解失效),也可以在context标签文本内容中使用<context:include-filter type="annotation" expression="想要生效的注解的全限定名"/>来过滤筛选,使哪些注解生效

负责Bean实例的属性注入的四大注解

  • @Value
  • @Autowired
  • @Qualifier
  • @Resource

解析:

  • @Value

    • 当属性的类型是简单类型时,可以使用@Value注解进行注入,该注解可以使用在类中的属性变量上或者setter方法上,都可以完成属性值的注入
  • @Autowired

    • 该注解可以用来注入非简单类型,也就是自定义的类型,被翻译为:自动装配。单独使用该注解表示通过类型(byType)来进行自动装配。
    • 该注解可以出现在构造方法、属性、方法上、形参上、注解上等位置
    • 该注解在使用的时候,有一个required属性,默认是true表示为这个属性注入值的时候,必须要求容器中有该属性对应的Bean实例,若没有则报错,false表示有没有这个Bean实例没关系,有就给属性注入赋值,没有也不报错,程序正常执行。
    • 当有参数的构造方法只有一个时,@Autowired注解可以省略。当构造方法多个时,则不能省略
    • 当容器中同一个类型的Bean实例有多个时,这时单纯采用@Autowired注解会出现问题,它根据类型装配发现该类型的Bean有多个,就报错了,这时要采用根据名字装配(byName),即联合@Qualifier("指定Bean实例的id")注解来完成值的注入
  • @Resource

    • 可以出现在类上和其属性、setter方法上,要使用该注解需要引入jarkarta.annotation.api依赖包
    • 完成一些非简单类型的注入,那它和@Autowired注解的区别?
      • @Resource注解默认根据byName来进行自动装配,未指定name时会默认以Bean的属性名来在容器中寻找,若找不到则自动通过byType装配
    • 总结:当使用@Resource注解时默认根据byName注入,若没有指定name时把属性名当作name,根据name在容器中找不到时,才会byType注入。byType注入时,要求某种类型的Bean实例在容器中只有一个(有多个时要明确指定)

自己new的对象交给spring管理-注解版(前文有xml配置版的)

在一个方法上使用@Bean注解,这个注解必须有返回所需类型的实例对象,这样这个对象就交给spring管理了

  • 该注解等价于<bean id="默认为方法名" class = "全限定类名"/>
  • 该注解只能用在带返回值的方法上

全注解开发

所谓全注解开发就是不实用spring配置文件的了,写一个配置类来替代配置文件。使用以下两个注解配合等价于写有<context:component-scan base-package=""/>的spring配置文件

java 复制代码
@configuration
@ComponentScan("包路径1","包路径2"...)
@EnableAspectJAutoProxy(proxyTargeClass=true)//手动开启动态代理(AOP编程用到)
public class Spring6Configuration{

}
//测试程序也不再使用ClassPathXmlApplicationContext()对象解析xml文件了,使用对应的注解解析对象-AnnotationConfigApplicationContext

JdbcTemplate

JdbcTemplate是Spring提供的一个Jdbc模版类(不是接口),是对JDBC的封装,简化使用JDBC,也可以不采用Spring自己的jdbc,采用Spring集成的其他ORM框架(mybatis、hibernate等)

使用步骤:

  • 导入spring-jdbc依赖、JDBC依赖
  • 把spring提供的模版类(JdbcTemplate)注入到容器中交给spring管理,该实例Bean需要注入一个DataSource类型的属性(下方的就是)
  • 编写一个类实现DataSource接口(连接池),并把这个类注入到容器中。或者不使用自定义的数据源的实现类,而使用Druid连接池(只需在spring配置文件中注入com.alibaba.druid.pool.DruidDataSource,然后把这个Bean的id配置到JdbcTemplate的DataSouce属性上)
  • 现在我们只需要从容器中获取JdbcTemplate实例对象,该实例有增删改查等方法,即可完成对数据库的操纵。
    • 该实例对象中有:单对象的增删改查、批量的增删改查、查完后执行回调函数等功能的函数

GoF之代理模式

代理模式角色分配:

  • 目标类(客户端要使用的最终对象)
  • 代理类(代理目标接触客户端的对象,避免了客户直接使用目标类)
  • 目标类和代理类的公共接口(既然代理类要代替目标类,那么它们就要有共同的行为,也就是实现共同的接口)

代理模式类型:

  • 静态代理:自己编写代理类,一个业务需求就可能要编写一个代理类,可能会产生类爆炸
  • 动态代理:不需要自己编写代理类,在程序运行中动态的自动的生成代理类,我们直接操作公共接口就行
    • JDK动态代理(只能代理接口)
    • CGlib动态代理(可代理一个类也可代理接口,且效率优于JDK代理)
    • Javassist动态代理

JDK动态代理:

  • 使用jdk自带的Proxy的静态方法newProxyInstance(参数1,参数2,参数3)
  • 参数1:目标对象的类加载器
  • 参数2:目标对象的接口类型
  • 参数3:调用处理器对象(当客户使用接口操纵目标对象时,实际会执行处理器里的invoke方法)
  • 如何编写处理器类
    • 编写一个类实现InvocationHandler接口,并重写invoke(参数1,参数2,参数3)方法
    • 参数1:Object proxy(代理对象)
    • 参数2:Method method(目标方法对象),用户调用目标类的什么方法,这里的就对应该方法的封装的一个对象
    • 参数3:Object [] args(目标方法参数),客户传递过来的方法参数

注:这样我们只需要写一个处理器类,想代理谁就使用Proxy,传入相应的参数即可。

CGLib代理:

  • 这种方式即可代理类也可代理接口。底层采用继承的方式,因此被代理的类不能是final修饰。 和JDK动态代理的步骤差不多,在CGLib中我们提供的不是InvocationHandler接口,而是:net.sf.cglib.proxy.MethodInterceptor
  • 编写一个类实现net.sf.cglib.proxy.MethodInterceptor接口并实现intercept(参数1,2,3,4)方法
    • 参数1:Object target(目标对象)
    • 参数2:Method method(目标对象的方法)
    • 参数3:Object []args(目标方法调用时的实参)
    • 参数4:MethodProxy methodProxy(代理方法 )

注:对应高版本的 jdk,想要使用CGLib则需要在启动项中配置两个参数(如上图)

面向切面编程(AOP)

面向切面编程是一种对OOP编程的补充,底层就是采用动态代理来实现的。Spring的Aop使用的是:JDK动态代理+CGLib动态代理技术。这两种代理在Aop中是灵活切换的,如果代理的是接口(指实现接口的这种类),则使用JDK代理,若代理的是类(这个类没有实现接口),则使用CGLib。

AOP介绍

一般一个系统中都会有一些系统服务,例如:日志、事务、安全管理等,这些系统服务被称为交叉服务 。这些交叉业务几乎是通用的,不管是做银行转账、用户数据删除。日志、事务管理、安全,这些都是要做的。因此,若每一个业务中都要掺杂这些业务代码的话,就会出现代码臃肿,重复的代码写一堆得不到复用,若交叉代码出现调整则需要大量修改源代码,不符合OCP原则,且开发人员会无法专心编写业务相关的代码。采用AOP编程就很轻松解决以上问题

总结:将一堆与核心业务逻辑无关的代码抽取出来形成一个独立的组件,再以横向交叉的方式应用到业务流程中的过程就叫做AOP

AOP的七大术语

  • 连接点 Joinpoint
    • 可以插入动作的"时机点"。体现为程序的整个执行流程中,可以织入切面的位置。方法被执行之前和之后,异常抛出之后等位置
  • 切点 Pointcut
    • 在程序的执行流程中,真正织入切面的方法。即具体要增强的地方(比如指定某个类的某个方法,一个切点对应多个连接点)
  • 通知 Advice
    • 通知又叫增强,就是具体你要织入的代码。
    • 通知包含 :(粗体的常用)
      • 前置通知
      • 后置通知
      • 返回通知
      • 环绕通知
      • 异常通知
      • 最终通知
  • 切面 Aspect
    • 封装的附加功能模块(比如定义日志类、事务类、权限校验类)
    • 整个aop过程所需的基本都在切面类里写
    • 切点+通知就是切面
  • 织入 Weaving
    • 把通知应用到具体目标对象上去
  • 代理对象Proxy
    • 一个目标对象被织入通知后产生的新对象
  • 目标对象 Targe
    • 被织入通知的对象,即被增强的核心业务对象

重点解析:切面 Aspect

一个完整的切面类的核心组成(必须写的内容)

  • 标记该类是一个切面和组件:类上使用@Component和@Aspect,注意在spring配置文件中要手动开启动态代理(因为aop用到动态代理技术)
  • 切点的定义:写一个方法然后在方法上指定哪些执行核心业务类的哪些的方法要做增强(采用扫描包/类的形式)
  • 通知方法:也是自定义的方法,注意参数不要乱写,方法体为具体增强的代码,在方法签名上使用注解标明这是什么类型的通知。aop中,增强方法一般就两个参数JoinPoin和ProceedingJoinPoint类型,后者用来控制核心的业务逻辑方法是否执行(指:被增强的方法)

切点表达式如何写?:用于匹配具体的核心业务方法

java 复制代码
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
  • 可选,访问修饰符不写则表示所有修饰符的方法都被选中
  • 返回值类型:必填,*表示任意返回类型
  • 全限定类名:可选,例com.example.demo.service.MilkTeaService
  • 方法名:必写,.*表示所有方法
  • 形式参数列表:...表示任意参数列表
  • 异常:可选,不写时表示任意异常的方法都被选中

案例:

java 复制代码
// 1. 标记为组件(让Spring管理)
@Component
// 2. 标记为切面
@Aspect
public class MilkTeaAspect {

    // 3. 定义切入点:注解Pointcut("切入表达式"),其中切入表达式指定要增强的方法(这里是MilkTeaService的所有方法)
    @Pointcut("execution(* com.example.demo.service.MilkTeaService.*(..))")
    public void 切入点名称() {}//切入点名称供下方注解使用,方法体必须为空

    // ========== 1. 前置通知 ==========
    @Before("切入点名称()")//
    public void beforeMake(JoinPoint joinPoint) {
        // JoinPoint能获取核心方法的信息(比如参数、方法名)
        String type = (String) joinPoint.getArgs()[0];
        System.out.println("前置通知:制作" + type + "前,洗手消毒✅");
    }

    // ========== 2. 后置通知 ==========
    @After("切入点名称()")
    public void afterMake(JoinPoint joinPoint) {
        System.out.println("后置通知:制作完成,清洁操作台🧹");
    }

    // ========== 3. 返回通知 ==========
    @AfterReturning(value = "切入点名称()", returning = "result")
    public void afterReturn(JoinPoint joinPoint, Object result) {
        System.out.println("返回通知:" + result + ",记录销售额📝");//result获取到核心业务方法执行后返回内容
    }

    // ========== 4. 异常通知 ==========
    @AfterThrowing(value = "切入点名称()", throwing = "e")
    public void afterThrow(JoinPoint joinPoint, Exception e) {
        String type = (String) joinPoint.getArgs()[0];
        System.out.println("异常通知:制作" + type + "失败,原因:" + e.getMessage() + ",重新制作🔄");
    }

    // ========== 5. 环绕通知(最灵活) ==========
    //包裹核心方法,能控制核心方法 "是否执行""执行前做什么""执行后做什么"
    @Around("切入点名称()")
    public Object aroundMake(ProceedingJoinPoint joinPoint) throws Throwable {
        
        String type = (String) joinPoint.getArgs()[0];
        // 前置逻辑
        long startTime = System.currentTimeMillis();
        System.out.println("环绕通知前置:开始制作" + type + ",计时开始⏱️");

        Object result = null;
        try {
            // 调用proceed方法,会执行核心业务方法,该方法返回值用result接收
            result = joinPoint.proceed();
        } catch (Exception e) {
            // 异常逻辑(可选)
            throw e;
        } finally {
            // 后置逻辑
            long endTime = System.currentTimeMillis();
            System.out.println("环绕通知后置:制作" + type + "耗时" + (endTime - startTime) + "ms⏳");
        }
        return result;
    }
}

控制多个切面执行顺序

在具体的程序开发中,业务流不一定只有一个切面,可能有事务控制、安全控制、日志等不限。如果多个切面,那么如何控制这些切面的执行顺序:来切面类上使用==@Order(数字)==注解来控制,其中数字越小表示该切面执行优先级越高。

基于XML配置方式的AOP

步骤:

  • 1、创建一个切面类(该类中定义一系列的增强方法),然后通过配置Bean标签的形式把切面对象注入到spring容器中
  • 2、在xml文件中引入aop命名空间
  • 3、xml文件中配置aop
xml 复制代码
<aop:config>
	//配置切点表达式
	<aop:pointcut id="jayce" expression=""/>
	//配置切面类
	<aop:aspect ref="切面类Bean的id">
		//内嵌标签的方式:切面=通知+切点
		<aop:around method="切面类中的增强方法名" pointcut-ref="jayce" />
		...
		//或者在配置独立的通知,这里使用advisor标签引入,
		<aop:advisor advice-ref="通知id" pointcut-ref="jayce"/>
	</aop:aspect>
</aop:config>

//以事务管理为例
<tx:advice id="txAdvice" transaction-manager="txManager">
	<tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="transfer*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
     </tx:attributes>
</tx:advice>

AOP之事务管理

  • 注解版:
    • 编写一个类,在类上使用
      • @EnableTransactionManager
      • @Configuration
      • 在这个类中编写方法(方法实用@Bean注解),在方法中配置数据源和事务管理器等
      • 在服务层的业务逻辑方法上使用@Transactional注解即可,它的相关属性有:事务的传播行为、事务隔离级别、是否只读、事务超时、指定触发事务回滚的异常类型等
  • 还可配置多数据源事务管理,业务层使用时用@Transactional的value属性指定id

未完。。。

相关推荐
Boop_wu1 小时前
[Java EE 进阶] 一文吃透 Spring IoC&DI:核心概念 + 实战用法 + 面试考点(上篇)
spring·面试·java-ee
巫山老妖1 小时前
多 Agent 协作实战:我用 3 只龙虾组了个「AI小分队」,效率直接翻倍
java·前端
神奇小汤圆1 小时前
一篇搞懂:HashMap 如何用扰动函数解决 hash 冲突?
后端
xienda1 小时前
Spring Boot 核心定义与用处
java·spring boot·后端
DyLatte1 小时前
理性到最后,其实是一场下注
前端·后端·程序员
Java编程爱好者2 小时前
OpenClaw跟Skills、MCP、RAG和Agent有什么关系?
后端
Roc.Chang2 小时前
Rust 入门 - RustRover 新建项目时四种项目模板对比
开发语言·后端·rust
直有两条腿2 小时前
【Spring Boot】原理
java·spring boot·后端
一只叫煤球的猫2 小时前
用这个框架彻底摆脱Controller,从此专注业务——ArcRoute
java·spring·开源