前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot的AOP实战

SpringBoot的AOP实战

作者头像
十玖八柒
发布2022-08-01 09:34:12
3950
发布2022-08-01 09:34:12
举报
文章被收录于专栏:ahzoo.cn的博客分享

前言

简单回顾下AOP:

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

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

符号

意义

*

0至多个任意字符

-

用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径

+

用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

示例:

代码语言:javascript
复制
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上进行 导入依赖:

代码语言:javascript
复制
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

切面类:

代码语言:javascript
复制
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(后置切入):

代码语言:javascript
复制
@Transactional(readOnly=false)//, propagation=Propagation.NOT_SUPPORTED)
public void insertLog(Log entity) {
	dao.insert(entity);
}

测试图:

图片
图片
图片
图片

目标被切方法2(环绕切入):

代码语言:javascript
复制
@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";
}

测试图:

图片
图片
图片
图片

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

图片
图片

表达式优化

优化前

代码语言:javascript
复制
@Before("execution(* aa.*.*(..))")
public void begin() {
 System.out.println("开始事务");
}
@After("execution(* aa.*.*(..))")
public void close() {
 System.out.println("关闭事务");
}

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

代码语言:javascript
复制
@Pointcut("execution(* aa.*.*(..))")
public void pt() {
}

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

后记

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实战使用
  • 表达式优化
  • 后记
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档