Z次元
文章 笔记 日志
专题
专栏分类 文章归档
友链
友情链接 朋友圈
留言
头像
系列文章
SpringBoot的AOP实战
系列文章
知识积累
最后更新:2024/12/15|创建时间:2021/12/23
文章摘要
文章介绍了AOP(面向切面编程)的基本概念。文章详细讲解了AOP在SpringBoot中的应用,通过实例展示了如何通过Spring AOP框架实现日志记录、性能监控等非业务功能。同时,介绍了切入点表达式的编写方法及多种通知类型的使用场景,并强调了AOP在提升代码模块化、降低耦合度方面的优势。

前言

简单回顾下AOP:

  • AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程
  • 切面(Aspect):,给目标类增加的功能即为切面,切面一般都是非业务方法,独立运行,常用的切面的增强/通知(Advice)。
  • 连接点(JoinPoint) :连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
  • 切入点(Pointcut): 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不 能被增强的。
  • 目标对象(Target): 目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。当某对象被增强,则该类称为目标类,该类对象称为目标对象。
  • 通知(Advice): 通知表示切面的执行时间,Advice 也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方 法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。 切入点定义切入的位置,通知定义切入的时间。

切入点表达式格式:execution(访问权限 方法返回值 方法声明(参数) 异常类型)

符号 意义
* 0至多个任意字符
- 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径
+ 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

示例:

excution(public * *(...参数...)) //任意声明为public权限的方法
excution(public * set*(...参数...)) //任意一个以set开始的方法
excution(* com.java.* *(...参数...)) //com.java包中的所有类
  
execution(* com.xyz.service..*.*(..))
//指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
execution(* *..service.*.*(..))
//指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.service.*.*(..))
//指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点
execution(* *.ISomeService.*(..))
//指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点
execution(* *..ISomeService.*(..))
//指定所有包下的 ISomeSerivce 接口中所有方法为切入点
execution(* com.xyz.service.IAccountService.*(..)) 
//指定切入点为:IAccountService 接口中的任意方法。
execution(* com.xyz.service.IAccountService+.*(..)) 
//指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
execution(* joke(String,int)))
//指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。
execution(* joke(String,*))) 
//指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。
execution(* joke(String,..))) 
//指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)都是。
execution(* joke(Object))
//指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。
execution(* joke(Object+))) 
//指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

实战使用

测试直接在开源框架,jeesite上进行
导入依赖:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

切面类:

package com.jeesite.modules.test.service;

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

import java.util.Arrays;

//表明当前类为切面类
@Aspect
//将当前类放到Spring容器中
@Component
public class AopTest {

    private static final Logger log = LoggerFactory.getLogger(AopTest.class);
    /*
    后置切入测试
    */
    //设置要切入的点
    //PointCut表达式
    @Pointcut("execution(public * com.jeesite.modules.sys.service.support.LogServiceSupport.insertLog(..))")
    private void newController1() {
        //PointCut签名方法,没有实际作用,只是用来标记一个Pointcut,作为切入点的标记
    }

    //@After表示在目标方法之后执行下面的方法(afterController())
    @After("newController1()")
    private void afterController(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        //打印被切方法形参
        log.info("insertLog:{}",Arrays.toString(args));
    }

    @Pointcut("execution(public * com.jeesite.modules.sys.web.user.UserController.info(..))")
    private void newController2() {
    }
  
    /*
    环绕切入测试
    */
    @Around(value = "newController2()", argNames = "pjp")
    private String aroundController(ProceedingJoinPoint pjp) {
//        try {
//            //proceed()方法表示执行目标方法,如果不使用proceed(),就不会执行目标方法,只会执行此方法(aroundController()),可以实现目标方法被替换的效果
//            pjp.proceed();
//        } catch (Throwable e) {
//            e.printStackTrace();
//        }

//        User user = new User();
//        user.setUserName("ahzoo");
//        try {
        //使用proceed(),可以用数组形式,传递参数给目标方法,参数个数需与目标方法形参个数一致
//            pjp.proceed(new Object[]{user,"",null});
//        } catch (Throwable e) {
//            e.printStackTrace();
//        }
        Object[] args = pjp.getArgs();
        //打印被切方法形参
        log.info("info:{}",Arrays.toString(args));
        //替换目标方法return语句
        return "modules/sys/user/userSelect";
    }


    /*
    批量(前置)切入测试
    */
    @Pointcut("execution(public String com.jeesite.modules.sys.web.LoginController.switchSkin(..))")
    public void newController12() {
    }
    //可以一次对多个切面进行切入
    @Before("newController12() || newController1()")
    public void beforeController1(JoinPoint joinPoint) {
        log.info("测试切入");
    }

}

图片

目标被切方法1(后置切入):

	@Transactional(readOnly=false)//, propagation=Propagation.NOT_SUPPORTED)
	public void insertLog(Log entity) {
		dao.insert(entity);
	}

测试图:
图片
图片
目标被切方法2(环绕切入):

	@RequiresPermissions("user")
	@RequestMapping(value = "info")
	public String info(User user, String op, Model model) {
		if (StringUtils.isBlank(op)){
			op = "base";
		}
		model.addAttribute("op", op);
		model.addAttribute("user", UserUtils.getUser());
        //为了显示环绕通知效果,加了这段打印代码
		logger.info("userInfo:{}","here");
		return "modules/sys/user/userInfo";
	}

测试图:
图片
图片

批量切入测试(前置切入):
图片

表达式优化

优化前

 @Before("execution(* aa.*.*(..))")
 public void begin() {
	 System.out.println("开始事务");
 }
 @After("execution(* aa.*.*(..))")
 public void close() {
	 System.out.println("关闭事务");
 }

优化后:
优化后就只需要写一次切面表达式就行了

@Pointcut("execution(* aa.*.*(..))")
public void pt() {
}

@Before("pt()")
public void begin() {
 System.out.println("开始事务");
}
@After("pt()")
public void close() {
 System.out.println("关闭事务");
}

后记

实测无法切入 子类重写父类的方法,及 通过this调用的方法

版权声明
本文依据 CC-BY-NC-SA 4.0 许可协议授权,请您在转载时注明文章来源为 Z次元 ,若本文涉及转载第三方内容,请您一同注明。
更多专栏文章推荐
知识积累
人非生而知之者,孰能无惑?惑而不从师,其为惑也,终不解矣。
Java私有成员是否会被继承
2021/12/12
“类的成员”与“类的主体”
2021/12/11
SpringBoot集成轻量级搜索引擎——Meilisearch
2025/8/7
JDK新版特性(18-21)
2025/2/7
评论区

删除确认

评论删除后无法恢复,请确认是否继续?
发表评论

这里还没有评论哦

快来发一条评论抢占前排吧

前言
实战使用
表达式优化
后记
目录
前言
实战使用
表达式优化
后记
博客
文章 笔记 日志
专题
专栏分类 文章归档
友链
友情链接 朋友圈
交流
留言 关于我
主页
菜单
置顶
主题
我的
十玖八柒
每天进步多一点
欢迎到访φ(゜▽゜*)♪
最新评论
个人占星:
DeepSeek没有想象中的好用
个人占星:
想给自己的网站弄个统计功能,但不会弄,头疼
永恒末匕:
好哇塞,这个厉害
Corwin: @十玖八柒
哎 主要是我的个人网站用的是静态的cos 实现评论框还是有点困难
我的
关于我
个人主页
站点地图
RSS订阅
导航
十年之约
虫洞穿梭
开源博客
前端开源仓库
后端开源仓库
©2020 - 2025 By 十玖八柒 版权所有
豫ICP备20021466号