自定义 spring-boot-starter 暴露钩子

自定义 spring-boot-starter 暴露钩子

  • 1、前置工作:自定义一个 spring-boot-starter
    • 1.1、pom文件
    • 1.2、starter 封装的接口
    • 1.3、starter 的配置类
    • 1.4、starter 的 spring.factories
  • 2、方法一:ApplicationContext 实现
    • 2.1、MyService的实现类
    • 2.2、事件类及泛型实体
    • 2.3、使用钩子
  • 3、方法二:观察者模式 + ApplicationListener 实现
    • 3.1、定义监听者接口类
    • 3.2、MyService 的实现类
    • 3.3、定义 ApplicationListener
    • 3.4、MyListener 加入 spring.factories 文件
    • 3.5、使用钩子

 
       最近看了Springboot 相关的源码,正好项目上有需求,需要对自定义的 spring-boot-starter 封装的方法,暴露出钩子。对封装的方法,做一些前置或后置的扩展,所以简单写个demo 记录一下。

       这里用两种方法实现上面的需求,一种是使用 ApplicationContext 的事件发布机制实现。另一种是自己用 观察者模式 + ApplicationListener 实现。话不多说,直接上代码。

 

1、前置工作:自定义一个 spring-boot-starter

 

1.1、pom文件

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.demo</groupId><artifactId>my-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.3.5.RELEASE</version></dependency><!--包含自动配置的代码--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>2.1.5.RELEASE</version></dependency><!--非必须:编写配置文件时会有提示--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><version>2.6.8</version><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.3.5.RELEASE</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.16</version><optional>true</optional></dependency></dependencies></project>

 

1.2、starter 封装的接口

 

package com.demo.server;public interface MyService {// 该方法采用ApplicationContext 实现钩子暴露public void methodOne();public void methodTwo();}

 

1.3、starter 的配置类

 

package com.demo;import com.demo.server.impl.MyServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyStarterConfig {@Bean@ConditionalOnMissingBean(MyServiceImpl.class)public MyServiceImpl myServiceImpl() {return new MyServiceImpl();}}

 

1.4、starter 的 spring.factories

 
spring.factories 文件在 resources\META-INF 目录下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.MyStarterConfig

 

2、方法一:ApplicationContext 实现

 

2.1、MyService的实现类

 

package com.demo.server.impl;import com.demo.entity.MethodOneAfter;
import com.demo.entity.MethodOneBefore;
import com.demo.event.PostHandleEvent;
import com.demo.server.MyService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;@Slf4j
public class MyServiceImpl implements MyService {@Autowiredprivate ApplicationContext applicationContext;@Overridepublic void methodOne() {MethodOneBefore before = new MethodOneBefore();applicationContext.publishEvent(new PostHandleEvent<MethodOneBefore>(before));log.info("执行  -> MyServiceImpl.methodOne()");MethodOneAfter after = new MethodOneAfter();applicationContext.publishEvent(new PostHandleEvent<MethodOneAfter>(after));}}

 

2.2、事件类及泛型实体

 

package com.demo.event;import org.springframework.context.ApplicationEvent;// 订阅一个事件类
public class PostHandleEvent<TEntity> extends ApplicationEvent {private TEntity event;public PostHandleEvent(Object source) {super(source);this.event = (TEntity) source;}public TEntity getEvent() {return this.event;}}

 


package com.demo.entity;import lombok.Data;
// 前置钩子泛型实体
@Data
public class MethodOneAfter {private String name = "my name is MethodOneAfter";
}package com.demo.entity;import lombok.Data;
// 后置钩子泛型实体
@Data
public class MethodOneBefore {private String name = "my name is MethodOneBefore";
}

 

2.3、使用钩子

 
先在项目的pom文件中,引入自定义 starter 包的 依赖

        <dependency><groupId>com.demo</groupId><artifactId>my-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version></dependency>

 
然后监听 ApplicationContext 发布的事件即可

package com.demo.handle;import com.demo.entity.MethodOneAfter;
import com.demo.entity.MethodOneBefore;
import com.demo.event.PostHandleEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class MyServiceHandle {@EventListener// @Async  不加这个注解就是同步的,默认同步。加上@Async代表异步执行public void methodOneBefore(PostHandleEvent<Object> postHandleEvent) {if (postHandleEvent.getEvent() instanceof MethodOneBefore) {MethodOneBefore methodOneBefore = (MethodOneBefore) postHandleEvent.getEvent();log.info("执行 -> PostHandleEvent.methodOneBefore, name = {}",methodOneBefore.getName());} else if (postHandleEvent.getEvent() instanceof MethodOneAfter) {MethodOneAfter methodOneAfter = (MethodOneAfter) postHandleEvent.getEvent();log.info("执行 -> PostHandleEvent.methodOneAfter, name = {}",methodOneAfter.getName());}}}

 
调用 自定义 starter 中的 methodOne() 方法

package com.demo.controller;import com.demo.server.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@Autowiredprivate MyService myService;@GetMapping("/methodOne")public void methodOne() {myService.methodOne();}

 
执行结果:

2023-09-10 19:01:35.455 MyServiceHandle - 执行 -> PostHandleEvent.methodOneBefore, name = my name is MethodOneBefore
2023-09-10 19:01:35.456 impl.MyServiceImpl - 执行  -> MyServiceImpl.methodOne()
2023-09-10 19:01:35.458 MyServiceHandle - 执行 -> PostHandleEvent.methodOneAfter, name = my name is MethodOneAfter

 

3、方法二:观察者模式 + ApplicationListener 实现

 
       这种方法,只需要在项目中,实现 MyServiceListener 接口,即可达到 调用钩子的效果。并且可以选择性的实现 钩子方法。需要注意的是,实现 MyServiceListener 接口的实现类需要添加 @Component 注解。
 

3.1、定义监听者接口类

 

package com.demo.listener;// MyService类的监听类,用来实现监听者模式
public interface MyServiceListener {public default void methodTwoBefore(String name) {}public default void methodTwoAfter(String name) {}}

 

3.2、MyService 的实现类

 

package com.demo.server.impl;import com.demo.listener.MyServiceListener;
import com.demo.server.MyService;
import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;
import java.util.List;@Slf4j
public class MyServiceImpl implements MyService {// 监听者集合private List<MyServiceListener> listeners = new ArrayList<>();// 添加监听者public void addListener(MyServiceListener myServiceListener) {this.listeners.add(myServiceListener);}@Overridepublic void methodTwo() {String name = "my name is methodTwo()";methodTwoBefore(name);log.info("执行  -> MyServiceImpl.methodTwo()");methodTwoAfter(name);}/*** 通知所有观察者* @param name*/public void methodTwoBefore(String name) {for (MyServiceListener myServiceListener : this.listeners) {myServiceListener.methodTwoBefore(name);}}/*** 通知所有观察者* @param name*/public void methodTwoAfter(String name) {for (MyServiceListener myServiceListener : this.listeners) {myServiceListener.methodTwoAfter(name);}}}

 

3.3、定义 ApplicationListener

 
       这里监听 ContextRefreshedEvent 节点,在服务启动的 ContextRefreshedEvent 节点,将所有 实现 MyServiceListener 接口的实现类,加到 MyServiceImpl 业务实现类。
 

package com.demo.listener;import com.demo.server.impl.MyServiceImpl;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;import java.util.Map;public class MyListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();// 获取 BeanFactory 实例ListableBeanFactory beanFactory = (ListableBeanFactory ) applicationContext.getAutowireCapableBeanFactory();// 获取接口 A 的所有实现类Map<String, MyServiceListener> beansOfType = beanFactory.getBeansOfType(MyServiceListener.class);MyServiceImpl myService = beanFactory.getBean(MyServiceImpl.class);// 遍历监听接口的实现类,将监听者放到MyService业务实现类中for (Map.Entry<String, MyServiceListener> entry : beansOfType.entrySet()) {myService.addListener(entry.getValue());}}}

 

3.4、MyListener 加入 spring.factories 文件

 

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.MyStarterConfigorg.springframework.context.ApplicationListener=\
com.demo.listener.MyListener

 

3.5、使用钩子

 
先在项目的pom文件中,引入自定义 starter 包的 依赖

        <dependency><groupId>com.demo</groupId><artifactId>my-spring-boot-starter</artifactId><version>1.0-SNAPSHOT</version></dependency>

 
然后实现 MyServiceListener 接口

package com.demo.listener;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class MethodTwoListener implements MyServiceListener{// 这里可以选择性实现,因为接口方法是 default @Overridepublic void methodTwoBefore(String name) {log.info("执行 -> MethodTwoListener.methodTwoBefore  name = {}", name);}@Overridepublic void methodTwoAfter(String name) {log.info("执行 -> MethodTwoListener.methodTwoAfter  name = {}", name);}
}

 
调用 自定义 starter 中的 methodTwo() 方法
 

package com.demo.controller;import com.demo.server.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@Autowiredprivate MyService myService;@GetMapping("/methodTwo")public void methodTwo() {myService.methodTwo();}

 
执行结果:

2023-09-10 19:18:27.961 MethodTwoListener - 执行 -> MethodTwoListener.methodTwoBefore  name = my name is methodTwo()
2023-09-10 19:18:27.962 MyServiceImpl - 执行  -> MyServiceImpl.methodTwo()
2023-09-10 19:18:27.962 MethodTwoListener - 执行 -> MethodTwoListener.methodTwoAfter  name = my name is methodTwo()

 

 

 

 
 
 
 
.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

@FunctionalInterface注解的作用及其运用

定义 FunctionalInterface 是Java 8引入的一个注解&#xff0c;用于标识一个接口是函数式接口。 函数式接口是Java中的一个概念&#xff0c;指的是只包含一个抽象方法的接口。在Java 8之前&#xff0c;接口中可以包含多个抽象方法&#xff0c;但是在Java 8中引入了Lambda表达式…

Golang 中的匿名变量详解

在 Golang 中&#xff0c;可以使用匿名变量来忽略不需要的返回值或占位符。匿名变量是一种特殊类型的变量&#xff0c;可以简化代码并提高可读性。本文将详细介绍匿名变量的定义、特性和使用方法。 什么是匿名变量&#xff1f; 在 Golang 中&#xff0c;匿名变量是一种没有显…

【基于Cocos Creator 3.5的赛车游戏】8.引入触摸屏幕事件并简单的控制小车

转载知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具 项目地址&#xff1a;赛车小游戏-基于Cocos Creator 3.5版本实现: 课程的源码&#xff0c;基于Cocos Creator 3.5版本实现 上一张您已经对Cocos的坐标系有了了解。这一章我们将让小车能…

【经典小练习】JavaSE—拷贝文件夹

&#x1f38a;专栏【Java小练习】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f384;效果&#x1f33a;代码&#x1f6f8;讲解&#x…

【SA8295P 源码分析】96 - QNX AIS Camera 目录介绍 及 AIS Camera 框架介绍

【SA8295P 源码分析】96 - QNX AIS Camera 目录介绍 一、QNX AIS Camera 源码目录结构介绍1.1 AMSS/multimedia/camera/ais 目录介绍1.2 AMSS/multimedia/camera/build 目录介绍1.3 ais_server 可执行程序 包含哪些库?1.4 ais_be_server 可执行程序 包含哪些库?1.5 qcarcam_t…

【深入浅出C#】章节10: 最佳实践和性能优化:内存管理和资源释放

一、 内存管理基础 1.1 垃圾回收机制 垃圾回收概述 垃圾回收&#xff08;Garbage Collection&#xff09;是一种计算机科学和编程领域的重要概念&#xff0c;它主要用于自动管理计算机程序中的内存分配和释放。垃圾回收的目标是识别和回收不再被程序使用的内存&#xff0c;以…

高通胀加大英国债务负担

9月5日&#xff0c;英国伯明翰地方政府由于无力偿债宣布破产。一周后&#xff0c;该消息仍在市场持续发酵。越来越多的经济学家担忧&#xff0c;在伯明翰“倒下”之后&#xff0c;下一个宣布破产的英国城市会是哪个&#xff1f;作为仅次于伦敦的英国第二大城市&#xff0c;伯明…

微信管理系统在教育行业中的应用

随着教育行业越来越注重科技创新&#xff0c;对微信scrm工具的需求也会越来越大&#xff0c;微信scrm工具在教育行业的市场前景非常广阔&#xff0c;也为教育行业带来更多的发展机遇。 微信SCRM系统由监管、运营两大核心应用组成。能帮助培训机构实现从招生引流、销售管理、再…

【Android知识笔记】UI体系(四)

事件分发原理 屏幕事件会由Linux通过JNI传给WMS(WindowManagerService),然后由WMS传给Activity,最终经过PhoneWindow->DecorView开始往下分发。 View的事件分发 View的事件分发核心源码为 dispatchTouchEvent() 方法: public boolean dispatchTouchEvent(MotionEvent …

Spring Boot 集成 Redis

Spring-data-redis 在 Spring 中整合 Redis jedis : 采用的直连&#xff0c;多个线程操作的话&#xff0c;是不安全的&#xff0c;如果想要避免不安全的&#xff0c;使用 jedis pool 连接池 lettuce : 采用netty&#xff0c;实例可以再多个线程中进行共享&#xff0c;不存在…

导数的应用、单调性、极值、最大最小值

函数的单调性 函数的单调性是一个重要的性质&#xff0c;它描述了函数在某个区间上的变化趋势。如果函数在某个区间上单调递增&#xff0c;那么在这个区间上&#xff0c;随着自变量的增大&#xff0c;函数值也会增大&#xff1b;反之&#xff0c;如果函数在某个区间上单调递减&…

【halcon】halcon字符识别——OCR

前言 OCR&#xff08;Optical Character Recongnition&#xff09;光学字符识别。 halcon 的OCR&#xff0c;提供了几种方式&#xff0c;我们应该如何选择&#xff1f; 自动文本阅读器&#xff08;find_text&#xff09;手动文本阅读器&#xff08;find_text&#xff09;自己…

数据结构基础7:二叉树【链式结构】实现和递归思想。

二叉树的链式结构实现 一.二叉树链式结构的实现&#xff1a;1.前置说明&#xff1a;1.创建二叉树&#xff1a;2.二叉树的结构&#xff1a; 2.二叉树的遍历&#xff1a;1.二叉树的前中后序遍历&#xff1a;2.内容拓展&#xff1a; 二.二叉树链式(题目)题目一&#xff1a;计算节点…

【Axure高保真原型】日历日期原型模板

今天和大家分享日历日期的原型模板&#xff0c;包括月计划、周计划、日计划的原型案例&#xff0c;以及日期、时间、月份、区间选择器……具体效果可以点击下方视频观看 【原型预览及下载地址】 Axure 原型 备用地址&#xff1a;Untitled Document 【原型效果】 【原型效果…

2.k8s账号密码登录设置

文章目录 前言一、启动脚本二、配置账号密码登录2.1.在hadoop1&#xff0c;也就是集群主节点2.2.在master的apiserver启动文件添加一行配置2.3 绑定admin2.4 修改recommended.yaml2.5 重启dashboard2.6 登录dashboard 总结 前言 前面已经搭建好了k8s集群&#xff0c;现在设置下…

保姆级教程 --redis启动命令

1、在redis目录 打开命令 windowr 输入cmd 2、输入 redis-server.exe redis.windows.conf 启动redis命令&#xff0c;看是否成功 3、可能会启动失败&#xff0c;报28 Nov 09:30:50.919 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error 4、报错后&am…

【AI】《动手学-深度学习-PyTorch版》笔记(二十二):单发多框检测(SSD)

AI学习目录汇总 1、介绍 SSD(Single Shot MultiBox Detector)单发多框检测。“Single shot”说明SSD算法属于one-stage(一段式)方法,“MultiBox”说明SSD是多框预测(多尺度锚框/特征图)。 SSD和YOLO一样都是采用CNN网络执行one-stage(一段式)检测,区别是: YOLO速…

3D异常检测论文笔记 | Shape-Guided Dual-Memory Learning for 3D Anomaly Detection

参考&#xff1a;https://paperswithcode.com/sota/3d-anomaly-detection-and-segmentation-on 论文&#xff1a;https://openreview.net/pdf?idIkSGn9fcPz code&#xff1a;https://github.com/jayliu0313/Shape-Guided 文章目录 摘要一、介绍三、方法3.1. 形状引导专家学习3…

Linux下systemd深入指南:如何优化Java服务管理与开机自启配置

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER(二)

文章目录 TASK系列解析文章OptimizeByNLP1.get_nlp_info()定义问题规模2.get_bounds_info()定义约束边界约束3.get_starting_point()定义初值4.eval_f()求解目标函数5.eval_grad_f()求解梯度6.eval_g()求解约束函数7.eval_jac_g()求解约束雅可比矩阵8.eval_h()求解黑塞矩阵9. f…