Spring 6(二)

基于注解管理 Bean

从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。

Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。

Spring 通过注解实现自动装配的步骤如下:

  1. 引入 Spring 依赖
  2. 开启组件扫描
  3. 使用注解定义 Bean
  4. 依赖注入

开启组件扫描

Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 context:component-scan 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。一个简单的配置如下所示,确保要添加对应的命名空间:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.zhumingjian.spring"/>
</beans>

如果需要在扫描的时候排除某些类,需要像下面那样配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.zhumingjian.spring">
        <!-- context:exclude-filter标签:指定排除规则 -->
        <!--
            type:设置排除或包含的依据
            type="annotation",根据注解排除,expression 中设置要排除的注解的全类名
            type="assignable",根据类型排除,expression 中设置要排除的类型的全类名
        -->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="assignable" expression="com.zhumingjian.spring.controller.UserInfoController"/>
    </context:component-scan>
</beans>

如果需要指定仅扫描哪一些类,需要像下面这样配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.zhumingjian.spring" use-default-filters="false">
        <!-- context:include-filter 标签:指定在原有扫描规则的基础上追加的规则 -->
        <!-- use-default-filters 属性:取值 false 表示关闭默认扫描规则 -->
        <!-- 此时必须设置 use-default-filters="false",因为默认规则即扫描指定包下所有类 -->
        <!-- 
            type:设置排除或包含的依据
            type="annotation",根据注解排除,expression 中设置要排除的注解的全类名
            type="assignable",根据类型排除,expression 中设置要排除的类型的全类名
        -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="assignable" expression="com.zhumingjian.spring.controller.UserInfoController"/>
    </context:component-scan>
</beans>

Component 相关注解

Spring 提供了多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean

注解说明
@Component该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,该注解本身也有@Component注解,因此其功能与 @Component 相同。
@Service该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,该注解本身也有@Component注解,因此其功能与 @Component 相同。
@Controller该注解通常作用在控制层(如 SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,该注解本身也有@Component注解,因此其功能与 @Component 相同。

@Autowired 注入

当你在一个 bean 中使用 @Autowired 注解时,Spring 会尝试在 Spring 容器中找到一个匹配的 bean,默认通过类型匹配,并将其注入到被注解的字段、构造器或方法参数中。

Autowired 注解可以标注在以下几个地方:

  • 构造方法上
    @Service
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        @Autowired
        public UserServiceImpl(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void out() {
            userDao.print();
            System.out.println("Service层执行结束");
        }
    }
    
  • 方法上
    @Service
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        @Autowired
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void out() {
            userDao.print();
            System.out.println("Service层执行结束");
        }
    }
    
  • 形参上
    @Service
    public class UserServiceImpl implements UserService {
    
        private UserDao userDao;
    
        public UserServiceImpl(@Autowired UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void out() {
            userDao.print();
            System.out.println("Service层执行结束");
        }
    }
    
  • 属性上
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserDao userDao;
    
        @Override
        public void out() {
            userDao.print();
            System.out.println("Service层执行结束");
        }
    }
    
  • 和 @Qualifier 联合实现 byName 的注入
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        @Qualifier("userDaoImpl") // 指定bean的名字
        private UserDao userDao;
    
        @Override
        public void out() {
            userDao.print();
            System.out.println("Service层执行结束");
        }
    }
    

总结:

  • @Autowired 注解可以出现在:属性上、构造方法上、构造方法的参数上、setter 方法上。
  • 当带参数的构造方法只有一个,@Autowired 注解可以省略。
  • @Autowired 注解默认根据类型注入。如果要根据名称注入的话,需要配合 @Qualifier 注解一起使用。

@Resource 注入

@Resource注解是 JDK 扩展包中的,也就是说属于JDK 的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250 标准中制定的注解类型。JSR 是 Java 规范提案)

@Resource 注解也可以完成属性注入,@Resource 注解默认根据名称装配 byName,未指定 name 时,使用属性名作为 name。通过 name 找不到的话会自动启动通过类型 byType 装配。

使用 @Resource 可以减少程序和 Spring 的耦合程度,也就是说将来不想使用 Spring 了,切换成本也不会很高。

如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖:

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
</dependency>

全注解开发

全注解开发就是不再使用 Spring 配置文件了,写一个配置类来代替配置文件。

首先编写一个配置类:

@Configuration
@ComponentScan("com.zhumingjian.spring")
public class Config {
}

创建容器时,使用配置类:

ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
UserController userController = context.getBean("userController", UserController.class);

AOP

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它能在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。Spring AOP 是基于代理模式实现的。

AOP 可以简单地理解为就是给程序中的某一个功能点做增强而不用更改这个功能的代码

代理模式

代理模式是二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
代理模式
代理模式在 Java 中分为两种,分别是静态代理和动态代理。

静态代理

在静态代理中,代理类和目标类都实现同一个接口或继承同一个父类,并且代理类持有对目标类的引用。

静态代理的基本结构包括三个角色:

  • 抽象角色(Subject):定义了代理类和真实对象的公共接口,这样一来在任何使用真实对象的地方都可以使用代理对象。
  • 真实角色(RealSubject):实现了抽象角色定义的接口,是代理对象所代表的真实对象,是业务逻辑的具体执行者。
  • 代理角色(Proxy):持有一个真实角色的引用,可以控制对真实对象的访问,负责在调用真实对象之前或之后做一些额外的处理。

以下是一个简单的静态代理的例子:

首先定义接口 Subject:

public interface Subject {
    void request();
}

编写真实的业务逻辑实现类 RealSubject:

public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("真实业务逻辑");
    }
}

创建代理类 ProxySubject,代理类执行增强的方法:

public class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        // 代理类可以在调用实际方法之前做一些额外的处理
        System.out.println("执行真实的方法前做一些事情!!");
        realSubject.request();
        // 代理类还可以在调用实际方法之后做一些额外的处理
        System.out.println("执行真实的方法后做一些事情!!");
    }
}

最后,通过使用 ProxySubject,就可以执行到 RealSubject 增强后的方法,当不需要增强时,可以直接用 RealSubject 的方法。

静态代理的优点:

  • 易于理解和实现,适合简单的代理场景。
  • 可以在不改变目标对象的情况下增强目标对象的功能。

静态代理的缺点:

  • 代理类和目标类实现了相同的接口,导致代理类和目标类的代码量增加。
  • 如果接口增加方法,代理类和目标类都要维护,工作量较大。
  • 每个接口的方法都需要在代理类中进行实现,导致代码重复。

动态代理

动态代理是一种在运行时动态创建代理对象的技术,它可以在不知道具体类型的情况下创建代理类的对象。与静态代理不同,动态代理不需要为每个被代理的类编写一个代理类,而是在运行时动态地创建代理类和对象。

JDK 动态代理

JDK 动态代理主要依靠以下两个类:

  • java.lang.reflect.Proxy 类:这是实现动态代理的主要类之一。它提供了创建动态代理类和对象的静态方法。
  • java.lang.reflect.InvocationHandler 接口:该接口定义了一个方法 invoke(),在动态代理对象调用方法时会被调用,可以在该方法中对方法的调用进行增强。

以下是一个简单的动态代理案例:

定义接口:

interface Subject {
    void request();
}

实现目标对象,也就是真正执行核心业务的对象:

class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

实现 InvocationHandler 接口:

public class MyInvocationHandler implements InvocationHandler {
    private Object realSubject;

    public MyInvocationHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * @param proxy 通过 Proxy 生成的代理对象。通常情况下,在 invoke 方法中不会直接使用这个对象,因为它会导致递归调用。但是有些特殊情况下,可能会需要获取代理对象本身,例如对代理对象的某些特定方法调用进行特殊处理。
     * @param method 表示被调用的方法。通过这个对象可以获取方法的名称、参数类型等信息,进而进行相关的处理。
     * @param args 表示方法调用时的参数列表。如果方法没有参数,则该数组可以为空。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法执行前可以做一些额外的处理
        System.out.println("Before calling method: " + method.getName());
        // 调用目标对象的方法
        Object result = method.invoke(realSubject, args);
        // 在方法执行后可以做一些额外的处理
        System.out.println("After calling method: " + method.getName());
        return result;
    }
}

创建代理对象并测试:

public static void main(String[] args) {
    // 创建真实对象
    RealSubject realSubject = new RealSubject();
    // 创建 InvocationHandler 对象
    InvocationHandler handler = new MyInvocationHandler(realSubject);
    // 创建动态代理对象
    Subject proxySubject = (Subject) Proxy.newProxyInstance(
        Subject.class.getClassLoader(),
        new Class[] { Subject.class },
        handler
        );
    // 调用动态代理对象的方法
    proxySubject.request();
}
CGLIB 动态代理

CGLIB(Code Generation Library)是一个强大的,高性能的代码生成库,它扩展了 Java 语言,为没有实现接口的类提供了动态代理的能力。相比于 JDK 的动态代理,CGLIB 使用字节码生成技术,因此能够代理类而不仅仅是接口,这使得它在某些情况下更加灵活和强大。

关键概念

  • 横切关注点(Cross-cutting Concerns):指那些影响应用程序各个部分的功能需求,这些功能需求不属于任何单个类或模块,而是横跨整个应用程序。它们是与应用程序的核心业务逻辑无关但又必须被应用程序所使用或者关注的功能点。例如:日志记录、安全验证、事务管理等。

  • 通知(Advice):通知是面向切面编程中定义在横切关注点具体执行时执行的代码,简单来说就是具体的增强代码。横切关注点关注的是概念,通知关注的是实现,两者都有增强的意思。通知主要分为以下几种类型:

    • 前置通知(Before Advice):在目标方法调用之前执行,通常用于权限检查或者日志记录等。
    • 后置通知(After Returning Advice):在目标方法成功执行后执行,通常用于记录返回值或清理工作。
    • 异常通知(After Throwing Advice):在目标方法抛出异常后执行,通常用于异常处理和日志记录。
    • 最终通知(After Finally Advice):无论目标方法是否成功执行,都在方法返回或抛出异常后执行,通常用于资源清理。
    • 环绕通知(Around Advice):包围目标方法调用,在方法调用前后执行自定义的逻辑,可以控制目标方法的执行逻辑,包括是否执行目标方法、如何处理返回值等。

    通知执行顺序

    环绕通知
    前置通知
    proceed() 
    后置通知或异常通知
    环绕通知
    最终通知
    
  • 切面(Aspect):封装通知方法的类

  • 目标对象(Target Object):被一个或多个切面所增强的对象。

  • 代理(Proxy):代理之后的对象

  • 连接点(Joinpoint):连接点是应用执行过程中能够插入切面的一个点。在Spring AOP中,连接点总是方法的执行点(例如,方法的调用或执行)。

  • 切入点(Pointcut):定位连接点的方式,切入点表达式用于匹配通知应附加到的连接点。

切入点表达式

切入点表达式用于定义哪些连接点(通常就是指方法)将被增强(即执行通知 Advice)。execution() 是最常用的切入点函数,用于匹配方法执行。其语法如下:

execution(<修饰符模式>?<返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
  • 修饰符模式:可选,如 public、protected 等。
  • 返回类型模式:* 表示任意返回类型,或指定具体的返回类型。
  • 方法名模式:* 表示任意方法名,或使用具体的方法名。
  • 参数模式:(…) 表示任意参数,(String, int) 表示具体参数类型和数量。
  • 异常模式:可选,表示方法可能抛出的异常类型。

示例:

execution(* com.example.service.*.*(..)):匹配 com.example.service 包及其子包中所有类的所有方法。
execution(public * com.example.service.UserService.*(..)):匹配 UserService 类中所有的公共方法。
execution(* com.example.service.*.find*(String, ..)):匹配 com.example.service 包及其子包中所有以 find 开头且第一个参数为 String 类型的方法。

基于注解实现 AOP

依赖

Spring 中需要使用 AOP 相关功能需要添加以下依赖:

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
</dependency>

核心注解

以下是 AOP 相关的核心注解:

注解作用
@Aspect该注解用于声明一个类为切面类,切面类中可以定义切入点(Pointcut)和通知(Advice)。
@Pointcut该注解用于声明一个切入点表达式,该表达式用于指定哪些方法将被增强(即哪些方法将执行通知中的代码)。切入点表达式通常基于方法签名来定义。
@Before该注解用于声明一个前置通知(Before Advice),即在目标方法执行之前执行的代码。
@After该注解用于声明一个后置通知(After Advice),即在目标方法执行之后执行的代码,无论目标方法是否成功执行。
@AfterReturning该注解用于声明一个返回通知(After Returning Advice),即在目标方法正常执行完成后执行的代码。可以通过returning属性访问目标方法的返回值。
@AfterThrowing该注解用于声明一个异常通知(After Throwing Advice),即在目标方法执行过程中抛出异常时执行的代码。可以通过throwing属性访问抛出的异常对象。
@Around该注解用于声明一个环绕通知(Around Advice),这是功能最强大的通知类型。它可以在目标方法执行前后进行增强,并且可以决定是否执行目标方法以及何时执行。

举例

首先准备业务接口:

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

实现类:

@Component
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

创建切面类并配置:

@Aspect
@Component
public class LogAspect {
    @Before("execution(public int com.zhumingjian.spring.aop.CalculatorImpl.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    }

    @After("execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))")
    public void afterMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->后置通知,方法名:"+methodName);
    }

    @AfterReturning(value = "execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
    }

    @AfterThrowing(value = "execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    }

    @Around("execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        Object result = null;
        try {
            System.out.println("环绕通知-->目标对象方法执行之前");
            //目标对象(连接点)方法的执行
            result = joinPoint.proceed();
            System.out.println("环绕通知-->目标对象方法返回值之后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知-->目标对象方法出现异常时");
        } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
        }
        return result;
    }
}

Spring 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd 
       http://www.springframework.org/schema/aop 
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--
        基于注解的AOP的实现:
        1、将目标对象和切面交给IOC容器管理(注解+扫描)
        2、开启AspectJ的自动代理,为目标对象自动生成代理
        3、将切面类通过注解@Aspect标识
    -->
    <context:component-scan base-package="com.zhumingjian.spring.aop"/>
    <aop:aspectj-autoproxy/>
</beans>

测试:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Calculator calculator = context.getBean(Calculator.class);
    int add = calculator.add(1, 1);
    System.out.println("执行成功:" + add);
}

测试结果:

环绕通知-->目标对象方法执行之前
Logger-->前置通知,方法名:add,参数:[1, 1]
方法内部 result = 2
Logger-->返回通知,方法名:add,结果:2
Logger-->后置通知,方法名:add
环绕通知-->目标对象方法返回值之后
环绕通知-->目标对象方法执行完毕
执行成功:2

重用切入点表达式

首先定义切入点:

@Pointcut("execution(* com.zhumingjian.spring.aop.*.*(..))")
public void pointCut(){}

在同一个切面中使用:

@Before("pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

在不同切面中使用:

@Before("com.zhumingjian.spring.aop.CommonPointCut.pointCut()")
public void beforeMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

获取通知相关信息

获取连接点信息可以在通知方法的参数位置设置 JoinPoint 类型的形参:

@Before("execution(public int com.zhumingjian.spring.aop.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
    //获取连接点的签名信息
    String methodName = joinPoint.getSignature().getName();
    //获取目标方法到的实参信息
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
}

获取目标方法的返回值可以使用 @AfterReturning 中的属性 returning 来通知方法的某个形参,接收目标方法的返回值:

@AfterReturning(value = "execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
}

获取目标方法的异常, @AfterThrowing中的属性 throwing,用来将通知方法的某个形参,接收目标方法的异常:

@AfterThrowing(value = "execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
}

环绕通知的注意事项

目标方法的执行,目标方法的返回值一定要返回给外界调用者

@Around("execution(* com.zhumingjian.spring.aop.CalculatorImpl.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    Object result = null;
    try {
        System.out.println("环绕通知-->目标对象方法执行之前");
        //目标方法的执行,目标方法的返回值一定要返回给外界调用者
        result = joinPoint.proceed();
        System.out.println("环绕通知-->目标对象方法返回值之后");
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        System.out.println("环绕通知-->目标对象方法出现异常时");
    } finally {
        System.out.println("环绕通知-->目标对象方法执行完毕");
    }
    return result;
}

切面的优先级

相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
在这里插入图片描述
使用 @Order 注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值