IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java-SpringBoot AOP-半小时了解AOP知识及使用注解实现AOP -> 正文阅读

[Java知识库]Java-SpringBoot AOP-半小时了解AOP知识及使用注解实现AOP

Java-SpringBoot AOP-半小时了解AOP知识及使用注解实现AOP

环境

springboot 2.7.0
jdk 8

AOP是什么?

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

Spring AOP 与 Aspectj

Spring aop 和 AspectJ, 这是 Java 的两个最受欢迎的 aop 框架,springboot 使用AspectJ中的 @Aspect、@Pointcut、@Before、@After、@Around等常用的注解,可以方便实现切入和增强通知等功能。

常用注解

  • @Aspect标识一个切面
  • 切入点:@Pointcut
  • 通知:@Before、@After、@Around、@AfterReturning、@AfterThrowing

注:AOP = 切入点+通知

实现AOP的两种风格

使用切入点注解实现AOP

使用@Pointcut注解可以在一个切面类中,将多个通知的切入点表达式抽取出来。不需要在每个通知注解中重复定义切入点表达式,代码风格直观。

不使用切入点注解AOP

当然在切面类中可以不用定义一个切入点,直接在通知类注解中使用表达式完成切入+通知操作,在切面类中定义一个通知时,此方法还是比较简单的。

切入点表达式

切入点表单时需要结合AOP注解使用,例如 value 中的 execution:

  • @Before(value = “execution(* com.example…getTime(…))”)
    以下为注解支持的表达式类型:
  • execution: 用于匹配方法执行连接点。 这是使用Spring AOP时使用的主要切点标识符。 可以匹配到方法级别 ,细粒度
  • within: 只能匹配类这级,只能指定类, 类下面的某个具体的方法无法指定, 粗粒度
  • this: 匹配实现了某个接口:this(com.xyz.service.AccountService)
  • target: 限制匹配到连接点(使用Spring AOP时方法的执行),其中目标对象(正在代理的应用程序对象)是给定类型的实例。
  • args: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中变量是给定类型的实例。 AOP) where the arguments are instances of the given types.
  • @target: 限制与连接点的匹配(使用Spring AOP时方法的执行),其中执行对象的类具有给定类型的注解。
  • @args: 限制匹配连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注解。
  • @within: 限制与具有给定注解的类型中的连接点匹配(使用Spring AOP时在具有给定注解的类型中声明的方法的执行)。
  • @annotation:限制匹配连接点(在Spring AOP中执行的方法具有给定的注解)。

springboot中注解方式定义AOP

使用@Pointcut注解

package com.example.learnspringbootaop.aop;

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


@Component
@Aspect // 切面 = 切入点 + 通知
public class MyAspect {
    @Pointcut("@annotation(com.example.learnspringbootaop.annotation.TestMyAspect)")
    public void pointcut(){
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("pointcut\t开始执行\t类:"+this.getClass().getSimpleName()+" 方法:pointcut()");
    }

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("before\t开始执行\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }

    @After("pointcut()")
    public void after(JoinPoint joinPoint) {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("after\t查询结束\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        // 获取执行目标类和方法名等等
    }

    /**
     * 环绕通知:
     * 1.加入 try..cache 可以避免环绕通知的逻辑被调用方法异常打断;
     * 2.catch 中需要将抛出异常,避免将异常吞掉;
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object aroud(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("aroud\t环绕通知-方法执行前\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        // 必须方法目标方法
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();
            System.out.print(this.getClass().getSimpleName()+"\t");
        }catch (Throwable ex){
            System.out.print(this.getClass().getSimpleName()+"\t");
            System.out.println("aroud\t执行时产生异常,error="+ex.toString());
            throw ex;
        }finally {
            System.out.println("aroud\t环绕通知-方法执行后\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        }

        // 将目标方法的返回值进行返回,否则调用目标方法的方法无法获取到返回值
        return proceed;
    }

    @AfterReturning("pointcut()")
    public void doAfterSuccess(JoinPoint joinPoint){
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("AfterSuccess\t执行返回通知\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }

    @AfterThrowing("pointcut()")
    public void doAfterError(JoinPoint joinPoint){
        System.out.println("AfterError\t执行异常通知\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }
}

不使用@Pointcut注解定义

package com.example.learnspringbootaop.aop;

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

// 表示当前的类是一个配置类
@Configuration
//该注解只能用在类上,作用:代表当前类是一个切面类,等价于spring.xml中的<aop:config>标签
//所以现在有了<aop:config>切面,还需要  通知 + 切入点
// 切面 == 通知 + 切面
@Aspect
public class MyAdviceConfig {

    /**
     * @param joinPoint
     * @Before:前置通知
     * value:切入点表达式  二者加起来构建成为一个切面
     * JoinPoint:连接点:可以理解为两个圆形的切点,从这个切点就可以获取到当前执行的目标类及方法
     * 前置通知和后置通知的参数的都是 JoinPoint, 前置后置通知都没有返回值
     */
    // 表示com.example包下的所有类所有方法都执行该前置通知
    @Before(value = "execution(* com.example..getTime(..))")
    public void before(JoinPoint joinPoint) {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("before\t开始执行\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }
    /**
     * 后置通知,属性参数同上面的前置通知
     * @param joinPoint 前置通知和后置通知独有的参数
     */
    @After(value = "execution(* com.example..getTime(..))")
    public void after(JoinPoint joinPoint) {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("after\t查询结束\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        // 获取执行目标类和方法名等等
    }
    /**
     * 环绕通知
     * @param joinPoint 环绕通知的正在执行中的连接点(这是环绕通知独有的参数)
     * @return 目标方法执行的返回值
     * @Around: 环绕通知,有返回值,环绕通知必须进行放行方法(就相当于拦截器),否则目标方法无法执行
     */
    @Around(value = "execution(* com.example..getTime(..))")
    public Object aroud(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("aroud\t环绕通知-方法执行前\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        // 必须方法目标方法
        Object proceed = joinPoint.proceed();
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("aroud\t环绕通知-方法执行后\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
        // 将目标方法的返回值进行返回,否则调用目标方法的方法无法获取到返回值
        return proceed;
    }


    @AfterReturning("execution(* com.example..getTime(..))")
    public void doAfterSuccess(JoinPoint joinPoint){
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("AfterSuccess\t执行返回通知\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }

    @AfterThrowing("execution(* com.example..getTime(..))")
    public void doAfterError(JoinPoint joinPoint){
        System.out.print(this.getClass().getSimpleName()+"\t");
        System.out.println("AfterError\t执行异常通知\t类:"+joinPoint.getTarget()+" 方法:"+ joinPoint.getSignature().getName());
    }
}

定义一个service类

package com.example.learnspringbootaop.test1;

import com.example.learnspringbootaop.annotation.MyAnnotation;
import com.example.learnspringbootaop.annotation.TestMyAspect;
import org.springframework.stereotype.Service;
import utils.DatetimeUtil;

import java.util.Date;

@Service
public class TestService {
    public String getTime(){
        System.out.println("执行 getTime() 方法代码。。。");
        return DatetimeUtil.getNowTime();
    }

    @TestMyAspect
    public String getTime2(){
        System.out.println("执行 getTime2() 方法代码。。。");
        return DatetimeUtil.getNowTime();
    }

    @MyAnnotation
    public String getTime3(){
        System.out.println("执行 getTime3() 方法代码。。。");
        return DatetimeUtil.getNowTime();
    }

    @TestMyAspect
    public Integer getNumber(){
        System.out.println("执行 getNumber() 方法代码。。。");
        return Integer.parseInt("ABC");
    }
}

测试下注解的效果

package com.example.learnspringbootaop.test1;

import com.example.learnspringbootaop.basic.BasicTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import static org.junit.jupiter.api.Assertions.*;


class TestServiceTest  extends BasicTest {
    @Autowired
    private TestService testService;

    @Test
    void getTime() {
        testService.getTime();
    }

    @Test
    void getTime2() {
        testService.getTime2();
    }
    @Test
    void getTime3() {
        testService.getTime3();
    }
    @Test
    void getNumber() {
        testService.getNumber();
    }
}
package com.example.learnspringbootaop.basic;

import com.example.learnspringbootaop.LearnAopApplication;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = LearnAopApplication.class)
public class BasicTest {


    @Before
    public void before(){
        System.out.println("----------测试开始-------------");
    }

    @After
    public void after(){
        System.out.println("----------测试结束-------------");
    }

}

总结

相比原生Spring AOP需要定义拦截器、切入点等操作,而在Springboot中借助Aspectj的注解可以在一个类中实现aop功能。
虽然Aspectj使用任意一个“通知注解”的切入点表达式,可以将"通知+切入点"合二为一,但由于破坏了单一职责原则,所以个人还是习惯在一个切面中定义一个业务的代码。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 16:07:52  更:2022-07-17 16:08:17 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 15:21:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码