当时明月在,曾照彩云归

Spring技术详解

Spring的特点

  • 轻量级

    从大小和开销两方面而言Spring都是轻量级的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外Spring还是非侵入式的,Spring应用中的对象不依赖于Spring的特定类

    非侵入式

    假设利用一个框架来开发,侵入式的做法就是要求用户代码知道框架代码,表现为用户代码需要继承框架提供的类。非侵入式则不需要用户代码引入框架代码的信息,从开发者的角度来看,察觉不到框架的存在。

  • 控制反转

    通过一种IOC技术进行松耦合。当应用了IOC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象

  • 面向切面

    允许通过分离应用的业务逻辑与系统服务进行内聚性开发

  • 容器

    Spring包含并管理应用对象的配置和生命周期

  • 框架集合

    Spring可以将简单的组件配置、组合成为复杂的应用

Spring的核心组件

g2GT76.jpg

  • Spring Core

    核心容器提供Spring框架的基本功能,核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转IOC模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

  • Spring Context

    Spring上下文是一个配置文件,向Spring框架提供上下文信息,Spring上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能

  • Spring Aop

    通过配置管理特性,Spring AOP模块直接将面向切面编程功能集成到了Spring框架中,可以将一些通用任务,如安全、事务、日志等集中进行管理,提高了复用性和管理的便携性

  • Spring DAO

    为JDBC DAO抽象层提供有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误信息。异常层次结构简化了错误处理,并且极大地境地了需要编写的异常代码数量。Spring DAO的面向JDBC异常遵从通用的DAO异常层次结构

  • Spring ORM

    Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构

  • Spring Web

    Web上下文模块建立在应用程序上下文模块之上,为基于Web的应用程序提供了上下文。所以Spring框架支持与Jakarta Struts的集成。Web模块化还简化了处理多部分请求以及将请求参数绑定到域对象的工作

  • Spring MVC

    MVC框架是一个全功能的构建Web应用程序的MVC实现,通过策略接口,MVC框架变成为高度可配置的,MVC容纳了大量视图技术,其中包括JSP、Velocity、Tiles、iText和POI

Spring IOC详解

IOC全称为Inversion of Control——控制反转,是说创建对象的控制权进行转移。以前创建对象的主动权和创建时机是由自己把控的,而现在这种权利转移到第三方,比如转移交给了IOC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就创建什么对象,有了IOC容器,依赖关系就变了,原先的依赖关系就变了,所有的对象都依赖于IOC容器,通过IOC容器来建立它们之间的关系。

Spring IOC的原理

Spring通过一个配置文件描述Bean与Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean与Bean之间的依赖关系。

Spring容器的实例化过程

Spring启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份Bean的配置注册表,然后根据这张表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。其中Bean缓存池由HashMap实现

BeanFactory是Spring框架的基础,更多的时候会使用到ApplicationContext而非底层的BeanFactory。

BeanFactor类继承图:

2QQezt.jpg

首先需要知道一个接口BeanDefinitionRegistry,在Spring中每一个Bean都是使用一个BeanDefinition类型的对象来表示,它描述了Bean的基本配置信息,而BeanDefinitionRegistry主要是将BeanDefinition注入到Spring容器中。

然后再看出于最顶端的接口BeanFactory,其中拥有一个最主要的方法就是getBean(String beanName),从容器中返回特定名称的Bean,BeanFactory的功能通过其他的接口得到不断的扩展。

  • ListableBeanFactory —— 定义了访问容器中Bean的若干方法,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法;
  • HierarchicalBeanFactory —— 父子级联Ioc容器的接口,子容器可以通过接口方法访问父容器;通过HierarchicalBeanFactory接口,Spring IOC容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的Bean,但父容器不能访问子容器的Bean
  • ConfigurabelBeanFactory —— 增强Ioc容器的可定制性,它定义设置类装载器、属性编辑器、容器初始化后置处理器等方法
  • AutowiredCapableBeanFactory —— 负责自动装配

Spring IOC的实现形式

Spring IOC的实现形式分为:Setter注入、构造器注入、方法注入

Setter注入

Setter注入就是指在属性的Setter方法中注入依赖选项。

假如要向User类中注入一个属性为Date的Bean,其在XML的配置中如下:

<bean class="com.ant.pojo.User" id="user">
        <property name="name" value="xx"/>
        <property name="age" value="22" />
        <property name="birth" ref="birth"/>
        <property name="balance" value="678.54" />
    </bean>
    <bean class="java.util.Date" id="birth" >
        <constructor-arg index="0" value="889771078000" type="long"/>
    </bean>

其在Java代码中的实现为:

 @Bean(name = "liuhuan")
    public User getUser(){
        User user=new User();
        user.setName("xx");
        user.setAge(24);
        user.setBirth(new Date());
        user.setBalance(new BigDecimal("357.45"));
        return user;
    }

构造器注入

通过容器触发一个类的构造器来实现,该类由一系列的参数,每个参数代表对一个其他的类的依赖。

package com.panhao.service;

import com.panhao.dao.UserDao;

public class UserServiceImpl {

    private UserDao userDao;

    public UserServiceImpl(UserDao userdao) {
        this.userDao=userdao;
    }

    public void getUser() {
        userDao.getUser();
    }
}

在XML中的配置为:

        <bean id="userDao" class="com.panhao.dao.impl.UserDaoImpl"></bean>
        <bean id="userService" class="com.panhao.service.UserServiceImpl">
            <constructor-arg ref="userDao"></constructor-arg>
        </bean>

IOC中常用的注解

@Configuration

​ 用于定义配置类,可替换XML配置文件,被注解的类内部包含有一个或者多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext类进行扫描,并用于构建Bean定义,初始化Spring容器。

@ComponentScan

​ 根据定义的扫描路径,把符合扫描规则的类装配到Spring的Bean容器中。

常用属性

  1. basePackage —— 设置要扫描的包

    @ComponentScan(basePackages = {"com.ant"})
  1. value —— 指定要扫描的包

    @ComponentScan(value = {"com.ant"})
  2. excludeFilters —— 设置过滤的规则,表示除了这些类或者注解都会被扫描

    //表示在com.ant包下,除了被@Service标注的类都会被扫描到
    @ComponentScan(basePackages = {"com.ant"},excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)
    },useDefaultFilters = false)
  3. includeFilters —— 设置过滤规则,表示只有这些类或者注解才会被扫描到

    //表示在com.ant包下,只有被@Service标注的类才会被扫描到
    @ComponentScan(basePackages = {"com.ant"},includeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)
    },useDefaultFilters = false)

FiltersType的种类以及含义

名称 含义 描述
ANNOTATION 表示按照注解类型来过滤 包含或者不包含特定的注解
ASSIGNABLE_TYPE 表示按照类来过滤 包含或者不包含特定的类
ASPECTJ 表示按照ASPECTJ表达式的方式来过滤
REGEX 表示按照正则表达式的方式来过滤
CUSTOM 自定义过滤准则 需要实现TypeFilter接口,实现该接口中的match()方法即可
@Component

​ 将被标注的类存入Spring容器中。

常用属性:

  1. value —— 用于设置Bean的ID,缺省的情况下,默认值为当前类名首写字母改为小写。(UserService => userService)

@Service、@Controller、@Repository与@Component注解是一样的意思

@Autowired

​ 实现Bean的自动装配。@Autowired会自动去IOC容器中寻找对应类型的Bean,然后将其注入。@Autowired在自动装配的时候回先按照对应参数的名称去寻找,当找到对应名称的Bean的时候,就返回该Bean;当没有找到的时候,就会按照类型去匹配Bean,当只有一个Bean满足条件的时候就会直接返回该Bean,当有多个或者没有找到的时候就会报错。

常用属性:

  1. required —— boolean类型的值,true表示该注入是必须的,如果没有找到就会报错;false表示该注入不是必须,不影响程序继续向下执行
@Resource

​ 与@Autowired注解作用一致,也是做依赖注入的。Spring在扫描到@Resource注解后,判断@Resource注解括号中的name属性是否为空:

1. 假如为空—— 在Spring容器中的bean的id与@Resource要注解的那个变量属性名是否相同
2. 假如不为空 —— 在Spring容器中bean的id对应的类型是否与@Resource要注解的那个变量属性对应的类型是否相等,如相等,匹配成功;若不相等,匹配失败。

常用属性:

  1. name —— 通过name属性去匹配Spring容器中的Bean的id
@Profile

​ 根据当前环境来注入Bean,用于与@ActiveProfile注解一起使用

常用属性:

  1. name—— 表示当前的环境名称
  2. @ActiveProfile —— 表示当前的生效的环境名称
@Primary

当一个接口有多个实现类的时候,在使用@Component注解标注了,然后使用的时候使用@Autowired注解进行注入时候,因为会有多个Bean都符合注入的要求,所以使用@Primary优先注入。与之相近的有一个@Qualifier注解,该注解将多个Bean都标注上不同的name属性,然后在注入的时候标记要注入哪一个Bean。

Bean的作用域

Spring一共为Bean定义了五种作用域:

  1. Singleton —— 单例模式

    IOC只会存在一个共享的Bean的实例,无论有多少个Bean引用它,始终指向一个对象,是Spring的模式Bean的模式。该模式在多线程下是不安全的。

  2. prototype —— 原型模式

    每次通过IOC容器获取Bean的时候,都会创建一个新的Bean的实例,每个Bean实例都有自己的属性和状态,通常对有状态的Bean使用Prototype,对无状态的Bean使用Singleton

  3. request —— 一个Request一个实例

    在一次Http请求中,容器会返回该Bean的同一个实例。而对不同的Http请求则会产生新的Bean,而且该Bean仅在当前Http Request内有效,当前Http请求结束,该Bean实例也将会销毁

  4. session —— 一个Session一个实例

    在 一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该Bean实例仅在当前Session内有效。同Http请求相同,每一次session请求创建新的实例,不同的实例之间不共享属性,且实例仅在自己的session请求内有效,session结束,则实例将被销毁

  5. global session

    在一个全局Http Session中有效,容器会返回同一个Bean,仅在使用protlet context有效

Bean的生命周期

2lZ8yD.jpg

假如一个Bean实现了BeanFactoryAware,BeanNameAware,InitializingBean,DisposableBean四个接口,其运行顺序为:

//调用Bean的构造器
【构造器】调用Person的构造器实例化
    //调用Bean的Setter方法
【属性注入】注入属性name
【属性注入】注入属性age
【属性注入】注入属性birth
【属性注入】注入属性balance
【BeanNameAware接口】调用BeanNameAware.setBeanName()
【BeanFactoryAware接口】调用BeanFactory.setBeanFactory()
【InitializingBean接口】调用InitializingBean.afterPropertiesSet()
【init-method】方法调用
    //容器关闭
【DisposableBean接口】调用DisposableBean.destroy()
【destroy-method】方法调用

Spring AOP详解

AOP(Aspect Oriented Programming)俗称面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能的同一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率。

AOP的三种织入时期:

  1. 编译期 —— 切面在目标类编译时织入,这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的
  2. 类加载期 —— 切面在类加载到JVM时被织入,这种方式需要特殊类加载器,它可以在目标类引入引用之前增强目标类的字节码
  3. 运行期 —— 切面在应用运行的某个时期被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象,这就是Spring AOP采用的织入方式。

AOP应用场景:权限验证、缓存、内容传递、错误处理、懒加载、调试、记录跟踪、优化、校准、性能优化、持久化、资源池、同步、事务

AOP的代理方法

Spring提供了两种方式来生成代理对象:JDK代理和CGLIB代理,默认策略是如果目标类是接口,则使用JDK代理,否则使用CGLIB来生成代理。

JDK代理

首先创建一个接口以及其实现类:

public interface Fruit {
    void eat();
}
--------------------------------------------------
public class Banana implements Fruit{

    @Override
    public void eat() {
        System.out.println("正在吃香蕉");
    }
}

然后创建一个代理的类,来对该方法进行增强:

@Component
public class FruitHandler implements InvocationHandler {

    private Object target;

    public Object bind(Object target){
        this.target=target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                this);
    }

    /*
    * method表示要增强的方法,invoke()表示调用之前的方法
    * args表示执行切点方法所需要的参数
    * **/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=null;
        System.out.println("吃水果之前需要洗一洗");
        result=method.invoke(target,args);
        System.out.println("吃完水果之后需要清理果核");
        return result;
    }
}

测试增强的方法:

    @Test
    public void test() {
        AbstractApplicationContext context=new AnnotationConfigApplicationContext(ContextConfiguration.class);
        Banana banana=new Banana();
        Fruit proxy= (Fruit) ((FruitHandler)context.getBean("fruitHandler")).bind(banana);
        proxy.eat();
    }

打印输出:

吃水果之前需要洗一洗
正在吃香蕉
吃完水果之后需要清理果核

JDK动态代理主要涉及到java.lang.reflect包中的ProxyInvocationHandler两个类,InvocationHanlder是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起,Proxy利用InvocationHandler动态创建一个符合某一个接口的实例,生成目标类的代理对象。

通过Proxy这个类的newProxyInstance方法,返回一个代理对象,生成的代理类实现了原来那个类的所有接口,并对接口的方法进行代理。当我们使用代理对象调用这个方法的时候,就会调用我们重写的invoke()方法。向上面的代码中我们通过bind()方法获得代理的Fruit类型对象,然后调用该对象的eat()方法,实际上就是调用FruitHandler中的invoke()方法,所有就会出现增强。

CGLIB代理

创建一个类作为增强的对象、

public class Cat {

    public void cry(){
        System.out.println("小猫喵喵喵~");
    }

}

创建一个代理处理器

public class CatInterceptor implements MethodInterceptor {


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("====前置通知====");
        Object object=methodProxy.invokeSuper(o,objects);
        System.out.println("====后置通知====");
        return object;
    }
}

测试代理增强类:

      Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(Cat.class);
        enhancer.setCallback(new CatInterceptor());
        Cat cat= (Cat) enhancer.create();
        cat.cry();

打印输出:

====前置通知====
小猫喵喵喵~
====后置通知====

Spring在使用CGLIB代理的时候首先需要添加CGLIB的依赖,才可以使用的到对应包下的MethodInterceptor接口,实现其中的intercept()方法,对原有方法进行增强。

Spring中的AOP的实现方式

在Spring中AOP的实现方式使用起来通常是更加简单的。首先在Spring配置类中使用@EnableAspectAutoProxy注解开启Spring的自动织入功能,然后通过自动织入来实现JDK代理和CGLIB代理

JDK代理

创建一个类并实现一个接口

@Component
public class Banana implements Fruit{
    @Override
    public void eat() {
        System.out.println("正在吃香蕉");
    }
}

然后创建该类切面

@Component
@Aspect
public class FruitHandler{

    @Pointcut("execution(* com.ant.pojo.Fruit.*(..))")
    public void eatFruit(){}

    @Before("eatFruit()")
    public void before(){
        System.out.println("====前置通知====");
    }

    @After("eatFruit()")
    public void after(){
        System.out.println("====后置通知====");
    }

}

测试:

        AbstractApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);
    //这里返回的必须是实现的接口的类型,因为代理的对象是Fruit接口,而不是Banana类
        Fruit banana = (Fruit) context.getBean("banana");
        banana.eat();

CGLIB代理

使用CGLIB代理之前需要设置Spring的代理模式,否则默认的代理模式就是JDK代理。只需要在@EnableAspectJAutoProxy注解中设置proxyTargetClass参数值为true即可,如:@EnableAspectJAutoProxy(proxyTargetClass = true)

创建一个类来做为代理的对象

@Component
public class Banana {

    public void eat() {
        System.out.println("正在吃香蕉");
    }
}

创建一个切面对该类进行增强

@Component
@Aspect
public class FruitHandler{

    @Pointcut("execution(* com.ant.pojo.Banana.*(..))")
    public void eatFruit(){}

    @Before("eatFruit()")
    public void before(){
        System.out.println("====前置通知====");
    }

    @After("eatFruit()")
    public void after(){
        System.out.println("====后置通知====");
    }

}

测试:

        AbstractApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);
    //这里返回的就是类的类型
        Banana banana = (Banana) context.getBean("banana");
        banana.eat();

打印输出:

====前置通知====
正在吃香蕉
====后置通知====

JDK代理与CGLIB代理的区别

  1. JDK代理

    优点:JDK动态代理是原生的,不需要任何依赖即可。相比于通过反射机制生成代理类的速度比CGLIB操作字节码生成代理类的速度更快

    缺点:如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;

    ​ JDK动态代理无法为没有在接口中定义的方法实现代理,即假设类A实现接口B,在类A除了实现了接口B的方法外,还拥有自己的方法,此时就无法对类A中其它的方法进行增强;

    ​ JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低

  2. CGLIB代理

    优点:使用CGLIB代理的类,不需要实现接口,因为CGLIB生成的代理类是直接继承自需要被代理的类;

    ​ CGLIB生成的代理类是原来那个类的子类,这就意味着这个代理类可以为原来那个类中,所有能够被子类重写的方法进行代理;

    ​ CGLIB生成的代理类,和我们自己编写并编译的类没有太大的区别,对方法的调用和直接调用普通类方式一致,所以CGLIB执行代理方法的效率更高;

    缺点:CGLIB代理类使用的继承的方式,所有被代理类中子类无法继承的方法都是无法进行增强的。例如被final修饰的方法,或者是一个final修饰的类,都是无法使用CGLIB的

    ​ CGLIB生成代码类的方式是通过操作字节码,这种方式生成代理类的速度比JDK使用反射机制来的慢

Spring中通知的执行顺序

在使用Spring AOP的时候一共有五种通知机制:前置通知,环绕通知,异常通知,返回值通知和后置通知。

首先创建一个类,使用JDK代理进行演示:

@Component
public class Banana implements Fruit{

    @Override
    public String eat() throws Exception {
        System.out.println("正在吃香蕉");
        return "Banana";
    }
}

创建一个代理的类对其进行增强:

package com.ant.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @program:SpringStudy
 * @description:
 * @author:Mr.Pan
 * @create:2021-06-03 16:16:22
 */
@Component
@Aspect
public class FruitHandler{

    @Pointcut("execution(* com.ant.pojo.Banana.*(..))")
    public void eatFruit(){}

    /*
    *  @Before代表前置通知,使用前置通知的时候,可以添加参数JointPoint,该类可以获得代理对象的相关信息
    * 在目标方法执行前执行
    * **/
    @Before("eatFruit()")
    public void before(){
        System.out.println("====前置通知====");
    }

    /*
    * @After代表后置通知,使用后置通知的时候,可以添加参数JoinPoint,该类可以获得代理对象的相关信息
    * **/
    @After("eatFruit()")
    public void after(){
        System.out.println("====后置通知====");
    }


    /*
    * @AfterThrowing代表异常通知,使用异常通知的时候,可以添加JoinPoint和一个与目标方法异常类型一致的参数
    * 该注解有两个参数,value表示该切点,throwing表示异常的
    * 使用的时候值得注意的是,afterThrowing方法的异常的类型必须与目标方法一致,参数名必须与@AfterThrowing的throwing的值一致
    * **/
    @AfterThrowing(value = "eatFruit()",throwing = "exception")
    public void afterThrowing(Exception exception){
        System.out.println("====异常通知====");
    }

    /*
    * @AfterReturning代表后置返回通知,使用后置返回通知的时候,可以添加参数JoinPoint和与目标方法类型一致的参数
    * 该注解有两个参数:value设置切点,res表示目标方法的返回值
    * 使用的需要注意的是,returning参数的值必须与后置返回通知方法中的参数名一致
    * 后置通知并不会改变原来的返回值,只是获得原来的返回值
    * **/
    @AfterReturning(value = "eatFruit()",returning = "res")
    public void afterReturning(JoinPoint joinPoint,String res){
        System.out.println("====返回通知====");
        res=res+"AfterReturning";
        System.out.println("【后置通知返回值】 返回值:"+res);
    }

    /*
     * 环绕通知:
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     * **/
    @Around("eatFruit()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        Object object=null;
        try {
            Object[] args=proceedingJoinPoint.getArgs();
            System.out.println("====环绕通知的前置通知====");
            object=proceedingJoinPoint.proceed(args);
            System.out.println("====环绕通知的返回通知====");
            return "Apple";
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("====环绕通知的异常通知====");
        }finally {
            System.out.println("====环绕通知的后置通知====");
        }
        return object;
    }
}

测试增强类:

      AbstractApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);
        Fruit banana = (Fruit) context.getBean("banana");
        String result = banana.eat();
        System.out.println("【最终返回值】result="+result);

打印输出:

====环绕通知的前置通知====
====前置通知====
正在吃香蕉
====环绕通知的返回通知====
====环绕通知的后置通知====
====后置通知====
====返回通知====
【后置通知返回值】 返回值:BananaAfterReturning
【最终返回值】result=Apple

因为在执行目标的方法的时候并没有出现异常,所以AfterThrowing并没有执行,所以在没有异常发生的情况下通知的执行顺序为:

环绕通知的前置通知=>前置通知=>目标方法=>环绕通知的返回通知=>环绕通知的后置通知=>后置通知=>返回通知

修改Banana类中的方式,使其发生异常

@Override
    public String eat(){
        System.out.println("正在吃香蕉");
        int a=1/0;
        return "Banana";
    }

此时需要注意的是我们在Around中对方法执行产生的异常进行了处理,所以此事并不会调用@AfterThrowing的方法,所以需要将@Around方法注释掉才可以看到@AfterThrowing的执行。

此时打印输出为:

====前置通知====
正在吃香蕉
====后置通知====
====异常通知====

java.lang.ArithmeticException: / by zero

可以在看到在将环绕通知注释掉之后的执行顺序是:

前置通知=>目标方法=>后置通知=>异常通知

总结:首先@Around的级别很高,会优先与其他的通知执行,其中@Around的前置通知会在@Before之前执行;@Around的后置通知会在@After之前执行;其次就是@Around的捕获异常会导致@AfterThrowing不会执行