基于注解的Spring-AOP应用实例

1、应用场景

需求是:在a系统每次字典数据变更时,都需要给b系统同步一次数据,以保持两个系统字典数据相同。
字典的增、删、改、合并接口,都需要执行数据推送操作,如果不用AOP、这些接口都需要增加推送操作的代码,这样会大大增加主业务与推送的耦合度,而且改起来很麻烦、代码也很不优雅。
下面的应用实例

  • 我们使用AOP来降低耦合度;
  • 同时为了不影响主业务的执行效率,采用多线程异步执行推送;
  • 为了优雅,采用自定义注解标记连接点。

2、应用实例

1)约定

2)配置

3)自定义注解

@Target(ElementType.METHOD) /*注解作用范围*/
@Retention(RetentionPolicy.RUNTIME) /*注解生命周期*/
public @interface PushDataToB {
	/*加两个变量,方便业务功能拓展*/

    //数据类型:1=机构,2=字典
    String dataType() default "1";

    //方法类型:mm=合并
    //合并时给b系统两次,一次传删除的、一次传留下的
    String methodType() default "nmm";
}

4)写切面类

@Component
@Aspect
public class PushDataToBAspect {

    @Resource(name = "threadPool")
    private ThreadPoolTaskExecutor taskExecutor;

    /*@Pointcut("@annotation(com.hhh.bbb.annotation.PushDataToB)")
    public void annotationPointCut() {
    }*/

    @AfterReturning("@annotation(pushDataToB)")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        //获取参数
        String dataType = pushDataToB.dataType();
        String methodType = pushDataToB.methodType();
        Object argObj = getParams(joinPoint);
        //执行任务
        PushDataToBExecute pushDataToBExecute = new PushDataToBExecute(argObj, dataType, methodType);
        /*Thread t = new Thread(pushDataToBExecute);
        t.start();*/
        taskExecutor.execute(pushDataToBExecute);
    }

    //获取连接点参数
    private Object getParams(JoinPoint joinPoint) {
        /*Map<String, Object> param = new HashMap<>();
        //获取参数值数组
        Object[] paramValues = joinPoint.getArgs();
        //获取参数名数组
        String[] paramNames = ((CodeSignature)joinPoint.getSignature()).getParameterNames();
        //组装,返回jsonobject
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return JSONObject.parseObject(JSON.toJSONString(param));*/
        //获取参数值数组
        Object[] paramValues = joinPoint.getArgs();
        return paramValues[0];
    }
}

代码基本可以分为以下几个部分:

@Pointcut

第一部分,是@Pointcut注解及其方法,用来定义切入点、即我们的通知要在什么地方执行;我这里用的注解标记,表示在用了@PushDataToB这个注解的地方,按照通知类型织入我们的通知。
其实,这个方法并不是必须的,因为通知注解也可以指明通知在什么地方执行。
那么@Pointcut在什么时候用呢?
当类中有多个方法定义的切面相同时,可以单独定义一个方法,不必写方法体,统一设置@Pointcut;当单独指定了一个方法设置好@Pointcut后,类中的其他方法如果要跟单独指定的方法设置相同的切面时,只需要引用设置@Pointcut方法的名字即可。所以上面的示例代码还可以是以下形式的

@Component
@Aspect
public class PushDataToBAspect {

	...

    @Pointcut("@annotation(com.hhh.bbb.annotation.PushDataToB)")
    public void annotationPointCut() {
    }

	@Before("annotationPointCut()")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        xxx方法代码
    }

    @AfterReturning("annotationPointCut()")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        yyy方法代码
    }

	...
    
}

可以看到代码中我给注掉了,因为我的需求只要一个通知方法就能实现,不需要公用切入点(而且即使公用也不是必须的)。

通知方法

第二个部分是通知方法

	@AfterReturning("@annotation(pushDataToB)")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        //获取参数
        String dataType = pushDataToB.dataType();
        String methodType = pushDataToB.methodType();
        Object argObj = getParams(joinPoint);
        //执行任务
        PushDataToBExecute pushDataToBExecute = new PushDataToBExecute(argObj, dataType, methodType);
        /*Thread t = new Thread(pushDataToBExecute);
        t.start();*/
        taskExecutor.execute(pushDataToBExecute);
    }

五类通知对应五类通知注解

前置通知(@Before):在目标方法运行之前运行
后置通知(@After):在目标方法运行结束之后运行(无论方法正常结束还是异常结束)
返回通知(@AfterReturning):在目标方法正常返回之后运行
异常通知(@AfterThrowing):在目标方法出现异常以后运行
环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced()

可以在通知实现方法中增加JoinPoint类型的参数,用以接收实际业务(连接点方法)的方法名称和参数列表等信息,举例说明一下:
下面代码一个字典新增方法的实现方法,按照需求、字典新增我们要把数据推送到B系统,于是我们在方法上加自定义注解

	@PushDataToB(dataType = "2")
	@Override
    public void insertSelective(Dictionary dictionary) {
        xxx新增字典的实际代码,校验、新增等
        dictionaryDAO.insertSelective(dictionary);
    }

在切面类通知方法中,我们需要获取入参dictionary,所以通知方法如下:
我们使用最终通知@AfterReturning,因为这是一个新增,在新增执行成功前,没有字典id等数据,所以选择最终通知、在字典插入方法执行成功后,再获取参数、执行推送任务。

	@AfterReturning("@annotation(pushDataToB)")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        //获取参数
        String dataType = pushDataToB.dataType();
        String methodType = pushDataToB.methodType();
        Object argObj = getParams(joinPoint);
        //执行任务
        PushDataToBExecute pushDataToBExecute = new PushDataToBExecute(argObj, dataType, methodType);
        taskExecutor.execute(pushDataToBExecute);
    }

dataType、methodType是注解自定义的参数,如果需要正常获取就行;

JoinPoint

argObj参数是通过JoinPoint参数、以及动态代理实现的,具体代码如下:

	//获取连接点参数
    private Object getParams(JoinPoint joinPoint) {   
        //获取参数值数组
        Object[] paramValues = joinPoint.getArgs();
        return paramValues[0];
    }

因为示例中的主业务方法只有一个参数,所以我们直接获取第一个参数值就满足需求了;如果主业务方法有多个参数时,也可以获取,可以这样:

	//获取连接点参数
    private JSONObject getParams(JoinPoint joinPoint) {
        Map<String, Object> param = new HashMap<>();
        //获取参数值数组
        Object[] paramValues = joinPoint.getArgs();
        //获取参数名数组
        String[] paramNames = ((CodeSignature)joinPoint.getSignature()).getParameterNames();
        //组装,返回jsonobject
        for (int i = 0; i < paramNames.length; i++) {
            param.put(paramNames[i], paramValues[i]);
        }
        return JSONObject.parseObject(JSON.toJSONString(param));
    }

上面是得到JoinPoint以后,通过它获取主业务参数的代码,有一点需要注意
无论通知实现方法有多少个参数,JoinPoint类型的参数必须是第一个参数,否则Spring将无法识别。

在这里插入图片描述

ProceedingJoinPoint

还有一个ProceedingJoinPoint类,它继承了JoinPoint,而且添加了proceed()等方法,proceed()方法在环绕通知时会用到,通过它可以控制连接点方法的执行时机,即可以主动控制环绕通知方法的代码,哪些在连接点方法执行前要做,哪些在连接点方法执行后要做,应用示例如下:

@Around("@annotation(pushDataToB)")
public Object around(ProceedingJoinPoint joinPoint, PushDataToB pushDataToB) {
   //获取参数
   String event = pushDataToB.event();
   String dataPlace = pushDataToB.value();
   JSONObject argObj = getNameAndValue(joinPoint);
   
   Object returnValue = joinPoint.proceed();
   
   //
   JSONObject returnJsonOb = (JSONObject) JSONObject.toJSON(returnValue);
   excutePush(returnJsonOb,argObj,dataPlace,event);
   return returnValue;
}

上面代码是一个环绕通知方法,前三行代码是在主业务方法(也就是连接点方法)执行前执行的,最后三行代码是在主业务方法执行后执行的,这个功能在有些应用场景下非常好用。

5)多线程异步调用

在applicationContext.xml中配置一个线程池

	<!-- 线程池配置 -->
    <bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 核心线程数  -->
        <property name="corePoolSize" value="5" />
        <!-- 最大线程数 -->
        <property name="maxPoolSize" value="30" />
        <!-- 队列最大长度 >=mainExecutor.maxSize -->
        <property name="queueCapacity" value="500" />
        <!-- 线程池维护线程所允许的空闲时间 -->
        <property name="keepAliveSeconds" value="200" />
        <!-- 线程池对拒绝任务(无线程可用)的处理策略 -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
        </property>
    </bean>

推动任务放在run()方法中

写一个通知执行类,实现Runnable,将通知任务(也就是推送业务)放在run()方法中

public class PushDataToBExecute implements Runnable{

    private static Logger logger = LogManager.getLogger(PushDataToBExecute.class.getName());

    //服务器地址
    private  String bServerUrl = PropertiesSource.getProperty("B_SERVER");

    private  String headPicHost = PropertiesSource.getProperty("HEAD_PIC_HOST");

    private Object data = null;

    private String dataType;

    private String methodType;

    @Override
    public void run() {
        try {
            //判断是否是合并操作,合并推送两次:一次传删除的、一次传留下的
            if ("mm".equals(methodType)){
				xxx代码
            }else {
                String pwd = Md5Utils.hash("xxx");
                Map<String, String> params = new HashMap<String, String>();//请求参数集合
                params.put("type", dataType);
                params.put("pwd", pwd);
                params.put("json", JSONObject.toJSONString(data));
                logger.info("Thread:{},推送数据到B系统Param :{}", Thread.currentThread(), JSONObject.toJSONString(params));
                String res = HttpUtils.doPostWithJson("http://xxxx", params);
                System.out.print(res);
            }
        }catch (Exception e){
            logger.error("Thread:{},推送数据到B系统出现错误:{},", Thread.currentThread(), e);
        }
    }

    public PushDataToBExecute(Object data, String dataType, String methodType){
        this.data = data;
        this.dataType = dataType;
        this.methodType = methodType;
    }
}

在切面类注入线程池

	@Resource(name = "threadPool")
    private ThreadPoolTaskExecutor taskExecutor;

异步执行推送任务

 	@AfterReturning("@annotation(pushDataToB)")
    public void AfterReturning(JoinPoint joinPoint, PushDataToB pushDataToB) {
        
        xxx代码
        
        //异步执行推送任务
        PushDataToBExecute pushDataToBExecute = new PushDataToBExecute(argObj, dataType, methodType);
        taskExecutor.execute(pushDataToBExecute);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/2604.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker常规安装简介

总体步骤 搜索镜像拉取镜像查看镜像启动镜像,服务端口映射停止容器移除容器 案例 安装tomcat docker hub上面查找tomcat镜像&#xff0c;docker search tomcat从docker hub上拉取tomcat镜像到本地 docker pull tomcatdocker images查看是否有拉取到的tomcat 使用tomcat镜像创…

【带有平移和倾斜头的DIY相机滑块–基于Arduino的项目】

【带有平移和倾斜头的DIY相机滑块–基于Arduino的项目】 1. 前言2. 总体构思3. 构建相机滑块4. 电路图5. 印刷电路板设计6. 组装电子设备7. DIY 相机滑块 Arduino 代码1. 前言 在本教程中,我们将学习如何制作带有平移和倾斜头的电动相机滑块。这个基于 Arduino 的项目是 100%…

【Linux】进程概念二

文章目录进程概念二1. 进程状态2. 进程状态查看3. 僵尸进程3.1 僵尸进程的危害4. 孤儿进程5. 环境变量5.1 常见环境变量5.2 查看环境变量的方法5.3 测试PATH5.4 环境变量相关的命令5.5 环境变量的组织方式5.6 通过代码获取环境变量6. 程序地址空间7. 进程地址空间8. 扩展8.1 为…

如何安装nvm(nvm 安装教程)

如何安装nvm(nvm 安装教程) 一、nvm是什么? nvm是一个node的版本管理工具,可以简单操作node版本的切换、安装、查看等等,与npm不同的是,npm是依赖包的管理工具。 二、安装nvm 1.nvm下载地址 https://github.com/coreybutler/nvm-windows/releases提示:1.nvm-setup.z…

功能测试转型测试开发年薪27W,又一名功能测试摆脱点点点,进了大厂

咱们直接开门见山&#xff0c;没错我的粉丝向我投来了喜报&#xff0c;从功能测试转型测试开发&#xff0c;进入大厂&#xff0c;摆脱最初级的点点点功能测试&#xff0c;拿到高薪&#xff0c;遗憾的是&#xff0c;这名粉丝因为个人原因没有经过指导就去面试了&#xff0c;否则…

CCM调试的理论依据

前言 很久之前在网上看到一些CCM的调试总结&#xff0c;但是没有理论依据&#xff0c;经过我本人的推理&#xff0c;以及和结果比对&#xff0c;这里总结一个我称之为色相环补色原理的调试理论。 CCM理论&#xff1a; CMOS sensor 使用颜色滤波阵列&#xff08;Color Filter…

YOLOV8改进:如何增加注意力模块?(以CBAM模块为例)

YOLOV8改进&#xff1a;如何增加注意力模块&#xff1f;&#xff08;以CBAM模块为例&#xff09;前言YOLOV8nn文件夹modules.pytask.pymodels文件夹总结前言 因为毕设用到了YOLO&#xff0c;鉴于最近V8刚出&#xff0c;因此考虑将注意力机制加入到v8中。 YOLOV8 代码地址&am…

蓝桥杯每日一真题——[蓝桥杯 2021 省 B] 杨辉三角形(二分+规律)

文章目录[蓝桥杯 2021 省 B] 杨辉三角形题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路&#xff1a;全部代码&#xff1a;[蓝桥杯 2021 省 B] 杨辉三角形 题目描述 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列&…

配置Maven环境变量

我们现在进行项目开发时&#xff0c;项目中一般都会有依赖包的存在&#xff0c;而这些依赖包一般都是利用Maven进行下载管理的。 一. 下载&安装 下载地址 maven下载地址如下&#xff0c;各位请选择对应系统的maven版本进行下载。 https://maven.apache.org/download.cgi…

做一个前端网页送给女朋友~轮播图+纪念日

文章目录1. 轮播图框架2. 轮播图大盒子实现1. 盒子及图片的可视化2. 将图片重叠起来并放入轮播图盒子中...相对定位与绝对定位3. 添加左右按钮4. 点击按钮跳转图片5. 鼠标离开图片轮播图按钮隐藏6. 添加小圆点按钮7. 点击小圆点跳转图片并且该小圆点变色8. 自动轮播9. 最后一步…

SoC设计流程

此为一个学习记录文&#xff0c;内容可能从书上《SoC设计方法与实现&#xff0c;郭炜等电子工业出版社》来&#xff0c;也可能从网络来。 目录 软、硬件协同设计&#xff1a; 基于标准单元的SoC设计流程&#xff1a; 软、硬件协同设计&#xff1a; SoC 通常被称作系统级芯片…

EasyExcel导入Excel文件,并对文件内容作

首页是pom文件导入EasyExcel的依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.6</version> </dependency> mysql中添加三个字段做测试 自定义异常类 package com.exampl…

ABC278 F - Shiritori

不懂博弈和状压DP&#xff0c;今晚加训状压DP&#xff01;博弈太难了&#xff0c;东西太多了&#xff0c;等蓝桥杯打完再说QwQF - Shiritori (atcoder.jp)题意&#xff1a;思路&#xff1a;注意到数据范围是到16&#xff0c;因此可以考虑状压DP状态设计&#xff1a;&#xff08…

Java-Collections and Lambda

Java SE API know how 集合API 根据算法访选择合适集合 linkedlist不适合搜索 随机访问数据用hashmap 数据保持有序使用treemap 通过索引访问使用数组集合 同步和非同步 访问性能统计 与简单的非同步访问相比&#xff0c;使用任何数据保护技术都会有较小的损失 设置集合…

AI绘画关键词网站推荐 :轻松获取百万个提示词!完全免费

一、lexica.art 该网站拥有数百万Stable Diffusion案例的文字描述和图片&#xff0c;可以为大家提供足够的创作灵感。 使用上也很简单&#xff0c;只要在搜索框输入简单的关键词或上传图片&#xff0c;就能为你提供大量风格不同的照片。点击照片就能看到完整的AI关键词&#…

利用客户支持建立忠诚度和竞争优势

客户支持可以极大地改变您的业务;最细微、最微妙的差异都会使拥有一次性客户和拥有终身客户之间产生差异。在这篇博文中&#xff0c;我们将揭示客户对企业的忠诚度的三种核心类型&#xff0c;以及如何利用强大的客户支持工具和原则来提高理想的忠诚度并获得决定性的竞争优势。一…

11.12安全进阶:SSH实验配置指导

实验拓扑 实验需求 完成PC及交换机的配置,使得PC能够通过SSH的方式登录到交换机。 实验步骤及配置 交换机完成基础配置 [SW] interface Vlanif 1 [SW-Vlanif1] ip address 192.168.1.100 24简单起见,我们就直接使用VLAN1与PC对接,因此将交换机的IP地址配置在Vlanif1 交换机…

第16天-性能压测:压力测试,性能监控,优化QPS,Nginx动静分离

1.性能监控 1.1.JVM架构 运行时数据区&#xff1a; 方法区&#xff1a;最重要的内存区域&#xff0c;多线程共享&#xff0c;保存了类的信息&#xff08;名称、成员、接口、父类&#xff09;&#xff0c;反射机制是重要的组成部分&#xff0c;动态进行类操作的实现&#xff1b;…

[排序算法]堆排序

参考&#xff1a;《漫画算法-小灰的算法之旅》 目录 一、堆排序过程 二、堆排序的代码实现 三、时间复杂度和空间复杂度 四、从宏观上看&#xff0c;堆排序和快速排序相比&#xff0c;有什么区别和联系呢 回顾二叉堆&#xff1a; 1.最大堆的堆顶是整个堆中的最大元素。 2…

基于SpringBoot的外卖项目(详细开发过程)

基于SpringBootMyBatisPlus的外卖项目1、软件开发整体介绍软件开发流程角色分工2、外卖项目介绍项目介绍产品展示后台系统管理移动端技术选型功能结构角色3、开发环境的搭建开发环境说明建库建表Maven项目搭建项目的目录结构pom.xmlapplication.ymlReggieApplication启动类配置…