图文结合分析Spring的面向切面编程--AOP

本文介绍了Spring中的面向切面编程(AOP),通过实例演示如何利用AOP减少代码重复,提高可维护性。详细解释了AOP的核心概念如切面、连接点、切入点等,并提供了XML配置及@AspectJ注解两种实现方式。

Spring还可以这么学–AOP

上一篇文章Spring还可以这么学–IoC(控制反转) / DI(依赖注入)理解

1. 什么是AOP?

AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。

2. AOP的作用?

利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

首先我们来看没有AOP时,如果我们要做日志处理,就得在每个方法中添加

这里写图片描述

但大多数日志处理代码都是相同的,所以我们将日志处理抽离成一个新的方法,但是尽管这样,我们还得手动插入这些方法。

这里写图片描述

但这样代码的耦合度很高,当我们要更改这个功能时,就得一个个更改

使用AOP后

这里写图片描述

为了在指定位置执行这些横向的功能,需要知道指定的是什么地方

这里写图片描述

例如上图,方法级别的aop实现,在一个程序执行链条中,把method2称为切点,也就是说在method2执行时会执行横切的功能,那么是在method2之前还是之后呢,又是执行什么呢?这些都由advice(通知)来指定。
Spring 方面可以使用下面提到的五种通知工作:

这里写图片描述

3. AOP的核心概念

  • 横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
  • 切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
  • 连接点(joinpoint):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
  • 切入点(pointcut):对连接点进行拦截的定义
  • 通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
  • 目标对象:代理的目标对象
  • 织入(weave):将切面应用到目标对象并导致代理对象创建的过程
  • 引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
    ###4. 实现方式
    ####Spring AOP的XML实现方式
    Employee.java文件
package com.wangc;

public class Employee {
    private String name;
    private int age;
    public String getName() {
        System.out.println("name = "+name);
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        System.out.println("age = "+age);
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void printThrowException() {
        System.out.println("发生异常");
        throw new IllegalArgumentException();
    }
}

Logging.java文件

package com.wangc;

public class Logging {
    //在一个方法执行之前,执行通知。
    public void beforeAdvice(){
        System.out.println("执行employee的方法之前执行.");
    }
    //在一个方法执行之后,不考虑其结果,执行通知。
    public void afterAdvice(){
        System.out.println("执行employee的方法之后执行.");
    }
    //在一个方法执行之后,只有在方法成功完成时,才能执行通知。
    public void afterReturningAdvice(Object retVal){
        System.out.println("返回:" + retVal.toString() );
    }
    //在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
    public void AfterThrowingAdvice(IllegalArgumentException ex){
        System.out.println("抛出了一个异常: " + ex.toString());   
    }  
}

Beans.xml文件

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

   <aop:config>
      <!-- 声明一个aspect,logging将被依赖注入 -->
      <aop:aspect id="log" ref="logging">
         <!-- 声明一个切入点 ,该切入点将与 com.wangc 包下的Logging类的方法相匹配,这里的“*”是通配符-->
         <aop:pointcut id="all" expression="execution(* com.wangc.*.*(..))"/>
         <!-- 定义一个前置通知 -->
         <aop:before pointcut-ref="all" method="beforeAdvice"/>
         <!-- 定义一个后置通知 -->
         <aop:after pointcut-ref="all" method="afterAdvice"/>
         <!-- 定义一个返回后通知 -->
         <aop:after-returning pointcut-ref="all" returning="retVal" method="afterReturningAdvice"/>
         <!-- 定义一个抛出异常后通知 -->
         <aop:after-throwing pointcut-ref="all" throwing="ex" method="AfterThrowingAdvice"/>
      </aop:aspect>
   </aop:config>

   <bean id="employee" class="com.wangc.Employee">
      <property name="name"  value="zhangsan" />
      <property name="age"  value="28" />      
   </bean>

   <bean id="logging" class="com.wangc.Logging"/> 

</beans>

MyApp.java文件

package com.wangc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        Employee employee = (Employee) context.getBean("employee");
        employee.getName();
        employee.getAge();
        employee.printThrowException();
    }
}

执行结果:

执行employee的方法之前执行.
name = zhangsan
执行employee的方法之后执行.
返回:zhangsan
执行employee的方法之前执行.
age = 28
执行employee的方法之后执行.
返回:28
执行employee的方法之前执行.
发生异常
执行employee的方法之后执行.
抛出了一个异常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......

注意:上面的例子只有一个横切关注点,如果有多个横切关注点,可以使用aspect里的order属性来控制横切关注点的顺序。

......
   <aop:config>
      <aop:aspect id="log1" ref="logging1" order="1">
         <aop:pointcut id="addTime" expression="execution(* com.wangc.*.*(..))"/>
         <aop:before pointcut-ref="addTime" method="beforeAdvice"/>
      </aop:aspect>
      <aop:aspect id="log2" ref="logging2" order="2">
         <aop:pointcut id="printLog" expression="execution(* com.wangc.*.*(..))"/>
         <aop:before pointcut-ref="printLog" method="beforeAdvice"/>
      </aop:aspect>
   </aop:config>
   ......
Spring AOP 的 @AspectJ实现方式

这里只需要在上面的基础上修改以下两个文件就可实现,修改后的文件如下:
Beans.xml文件

<?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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

    <!-- 自动扫描被@Aspect标注的切面 -->
    <aop:aspectj-autoproxy />

    <bean id="employee" class="com.wangc.Employee">
       <property name="name"  value="zhangsan" />
       <property name="age"  value="28" />      
    </bean>

    <bean id="logging" class="com.wangc.Logging"/> 

</beans>

Logging.java文件

package com.wangc;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Logging {
    @Pointcut("execution(* com.wangc.*.*(..))")
    private void all() {
    }
    //在一个方法执行之前,执行通知。
    @Before("all()")
    public void beforeAdvice(){
        System.out.println("执行employee的方法之前执行.");
    }
    //在一个方法执行之后,不考虑其结果,执行通知。
    @After("all()")
    public void afterAdvice(){
        System.out.println("执行employee的方法之后执行.");
    }
    //在一个方法执行之后,只有在方法成功完成时,才能执行通知。
    @AfterReturning(pointcut = "all()", returning="retVal")
    public void afterReturningAdvice(Object retVal){
        System.out.println("返回:" + retVal.toString() );
    }
    //在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。
    @AfterThrowing(pointcut = "all()", throwing="ex")
    public void AfterThrowingAdvice(IllegalArgumentException ex){
        System.out.println("抛出了一个异常: " + ex.toString());   
    }  
}

这里做个简单的说明: 用@Aspect的注解来标识切面,注意不要把它漏了,否则Spring创建代理的时候会找不到它,@Pointcut注解指定了切点,@Before、@After、@AfterReturning和@AfterThrowing指定了运行时的通知。

运行结果:

执行employee的方法之前执行.
name = zhangsan
执行employee的方法之后执行.
返回:zhangsan
执行employee的方法之前执行.
age = 28
执行employee的方法之后执行.
返回:28
执行employee的方法之前执行.
发生异常
执行employee的方法之后执行.
抛出了一个异常: java.lang.IllegalArgumentException
Exception in thread "main" java.lang.IllegalArgumentException
......

上一篇文章Spring还可以这么学–IoC(控制反转) / DI(依赖注入)理解

<think>首先,用户的问题是关于AOP注解中@AfterReturning等其他注解的详细介绍。用户指定了参考站内引用[1]、[2]和[3],这些引用提供了相关信息。 回顾引用内容: - 引用[1]:讨论了使用@Before和@AfterReturning拦截单个方法的入参和出参,纯注解方式,并解决单个方法不生效的问题。提到了@Before和@AfterReturning的具体应用。 - 引用[2]:介绍了AOP的概念,包括@Around、@Before的使用,并解释了AOP的作用,如日志记录、事务处理等。 - 引用[3]:提供了几种注解的介绍:@Before、@After、@AfterReturning、@AfterThrowing、@Around,以及它们的执行顺序。 用户的问题是:“我想获取AOP注解中AfterReturning等其他注解的介绍 请问AOP注解AfterReturning等其他注解的详细介绍”。这表明用户想了解@AfterReturning和其他相关注解,如@Before、@After等。 我需要结构化地回答这个问题。用户指定了回答格式:使用中文、结构清晰、逐步解题、生成相关问题。另外,系统级指令要求行内数学表达式使用$...$,独立公式用$$...$$,但这里可能不涉及数学表达式;引用段落末尾添加引用标识如[^1]。 核心内容:详细介绍AOP注解中的@AfterReturning和其他注解。 基于引用,常见的AOP注解包括: - @Before: 在方法执行前运行。 - @After: 在方法执行后运行,无论方法是否正常结束。 - @AfterReturning: 在方法正常返回后运行。 - @AfterThrowing: 在方法抛出异常后运行。 - @Around: 环绕通知,可以控制方法的执行(调用或跳过)。 引用[3]特别提到了这些注解的顺序:Around(前部分)-> Before -> 目标方法 -> Around(后部分)-> AfterReturning或AfterThrowing -> After。 用户强调“AfterReturning等其他注解”,所以我应该先介绍@AfterReturning,然后扩展到其他注解。 结构回答: 1. **介绍AOP概念**:简要解释AOP是什么,以便背景理解。 2. **详细介绍@AfterReturning**:解释其作用、用法、示例代码。 3. **介绍其他相关注解**:包括@Before、@After、@AfterThrowing、@Around。 4. **执行顺序解释**:引用[3]中提到顺序。 5. **代码示例**:提供简单的Spring Boot使用示例。 6. **常见问题解决**:如引用[1]提到的单个方法不生效问题,简要说明解决方案。 7. **生成相关问题**:在末尾列出相关问题。 确保语言中文,结构清晰。既然用户提到“尽可能多的参考用户上一次的问题”,但系统消息中用户上一次问题是关于AOP注解的介绍,所以我直接回答。 最后,生成相关问题:基于内容,如如何自定义注解、顺序调整、性能分析等。 大纲: - **开头**:简述AOP和注解方式。 - **详细介绍每个注解**: - @AfterReturning: 用于方法正常返回后处理,如日志记录返回结果。 - @Before: 方法执行前。 - @After: 方法执行后always。 - @AfterThrowing: 异常抛出后。 - @Around: 环绕控制。 - **执行顺序**。 - **代码示例**。 - **常见问题**:如切入点表达式错误导致不生效。 - **结尾**:总结。 - **相关问题**。 引用标识:在引用段落末尾添加[^1]、[^2]、[^3]。例如,当提到引用内容时。 现在,撰写回答。 </think>### AOP注解详解:@AfterReturning 及其他相关注解 在面向切面编程AOP)中,Spring框架通过注解提供了一种优雅的方式来实现横切关注点(如日志记录、事务管理),而无需修改业务逻辑代码。基于您提供的引用内容,我将详细介绍@AfterReturning注解及其他常见AOP注解,包括其作用、用法、执行顺序和常见问题解决方案。内容参考了引用资料[^1][^2][^3],并结合实际应用进行说明。 #### 1. **AOP 概念回顾** AOP(Aspect Oriented Programming,面向切面编程)的核心思想是将横切逻辑(如日志、安全控制)与业务逻辑分离。通过切面(Aspect)来定义这些横切行为,并通过注解绑定到特定方法上。这种方式避免了代码重复,提高了可维护性[^2]。 #### 2. **@AfterReturning 注解详解** - **作用**:该注解用于定义一个后置通知(advice),在目标方法**正常返回后执行**(即方法执行完成且未抛出异常)。常用于处理方法的返回结果,例如日志记录、结果验证或数据转换。 - **使用场景**: - 记录方法的返回值。 - 对返回结果进行后处理(如加密或格式化)。 - 引用[1]中提到,它可以拦截单个方法的出参,解决需要获取方法结果的场景。 - **语法和参数**: - 基本语法:`@AfterReturning(pointcut = "切入点表达式", returning = "返回值变量名")` - `pointcut`:指定切入点的表达式,用于匹配目标方法。 - `returning`:定义一个变量来接收方法的返回值(必须与通知方法参数名一致)。 - 通知方法签名示例: ```java @Aspect @Component public class LoggingAspect { @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("方法正常返回,结果: " + result); } } ``` - 说明:`returning = "result"` 对应方法参数 `Object result`,其中 `result` 存储方法的返回值[^1][^3]。 #### 3. **其他常见 AOP 注解介绍** 以下注解通常与@AfterReturning结合使用,形成一个完整的切面生命周期: - **@Before**: - **作用**:在目标方法**执行前运行**。常用于前置检查(如权限验证)、参数预处理。 - **示例**: ```java @Before("execution(* com.example.service.*.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("方法执行前,参数: " + Arrays.toString(joinPoint.getArgs())); } ``` - 引用[1]强调,它常用于拦截方法的入参。 - **@After**: - **作用**:在目标方法**执行后始终运行**,无论方法是否正常结束或抛出异常。常用于资源清理(如关闭连接)。 - **区别**:与@AfterReturning不同,@After不关心方法是否异常,始终执行。 - **示例**: ```java @After("execution(* com.example.service.*.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("方法执行完成"); } ``` - **@AfterThrowing**: - **作用**:在目标方法**抛出异常后执行**。用于异常处理(如错误日志记录、发送警报)。 - **参数**:`throwing` 指定异常变量名。 - **示例**: ```java @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex") public void logAfterThrowing(JoinPoint joinPoint, Exception ex) { System.out.println("方法抛出异常: " + ex.getMessage()); } ``` - **@Around**: - **作用**:**环绕通知**,可以控制目标方法的执行(调用、跳过或修改)。功能最强大,能处理方法执行的全过程。 - **使用**:通常用于性能监控、事务管理。参数为 `ProceedingJoinPoint`,需调用 `proceed()` 来执行目标方法。 - **示例**: ```java @Around("execution(* com.example.service.*.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("方法执行前"); Object result = joinPoint.proceed(); // 执行目标方法 System.out.println("方法执行后,结果: " + result); return result; } ``` - 引用[2]指出,@Around 可以在不修改源码的情况下添加额外功能。 #### 4. **注解执行顺序** 在切面中,多个注解的执行顺序按以下规则进行(这在引用[3]中明确指出): 1. @Around(前半部分):在目标方法执行前。 2. @Before:紧接着运行。 3. 目标方法执行。 4. @Around(后半部分):目标方法执行后。 5. @AfterReturning(如果正常返回)或 @AfterThrowing(如果异常)。 6. @After:最后执行,无论是否异常。 顺序示例流程: ``` @Around (start) -> @Before -> 目标方法 -> @Around (end) -> @AfterReturning -> @After ``` 或 ``` @Around (start) -> @Before -> 目标方法(异常) -> @AfterThrowing -> @After ``` #### 5. **常见问题与解决方案** - **单个方法拦截不生效**:引用[1]中提到,使用纯注解方式时,常见问题包括切入点表达式错误或未启用AOP。解决方案: - 确保 `@EnableAspectJAutoProxy` 注解添加到配置- 检查切入点表达式是否精确匹配目标方法(如使用 `execution(* package.Class.method(..))`)。 - 避免在同一个方法上多个切面冲突。 - **性能考虑**:AOP注解会增加少量运行时开销,但通过合理使用(如限制切入点范围)可忽略不计[^2]。 #### 6. **完整示例代码** 以下是一个Spring Boot中使用多个AOP注解的简单示例: ```java import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class ExampleAspect { @Before("execution(* com.example.service.MyService.*(..))") public void beforeAdvice(JoinPoint joinPoint) { System.out.println("Before: 方法执行前"); } @AfterReturning(pointcut = "execution(* com.example.service.MyService.*(..))", returning = "result") public void afterReturningAdvice(JoinPoint joinPoint, Object result) { System.out.println("AfterReturning: 正常返回,结果=" + result); } @After("execution(* com.example.service.MyService.*(..))") public void afterAdvice(JoinPoint joinPoint) { System.out.println("After: 方法执行完成"); } @AfterThrowing(pointcut = "execution(* com.example.service.MyService.*(..))", throwing = "ex") public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) { System.out.println("AfterThrowing: 异常=" + ex.getMessage()); } @Around("execution(* com.example.service.MyService.*(..))") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Around: 开始"); Object result = joinPoint.proceed(); System.out.println("Around: 结束"); return result; } } ``` 运行上述代码时,日志输出会遵循第4部分的顺序规则。 #### 总结 @AfterReturning 是AOP中用于处理正常返回结果的关键注解,常与@Before、@After、@AfterThrowing和@Around协同工作。通过纯注解方式,您可以轻松实现方法拦截和增强,而无需入侵业务代码。如果您遇到单个方法不生效的问题,请检查切入点表达式和AOP配置。AOP的优势在于解耦横切逻辑,使代码更清晰可维护[^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C语言中文社区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值