最近看了Springboot 相关的源码,正好项目上有需求,需要对自定义的 spring-boot-starter 封装的方法,暴露出钩子。对封装的方法,做一些前置或后置的扩展,所以简单写个demo 记录一下。
这里用两种方法实现上面的需求,一种是使用 ApplicationContext 的事件发布机制实现。另一种是自己用 观察者模式 + ApplicationListener 实现。话不多说,直接上代码。
文章来源:https://www.toymoban.com/news/detail-709116.html
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 {
@Autowired
private ApplicationContext applicationContext;
@Override
public 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 {
@Autowired
private 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);
}
@Override
public 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> {
@Override
public 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.MyStarterConfig
org.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
@Override
public void methodTwoBefore(String name) {
log.info("执行 -> MethodTwoListener.methodTwoBefore name = {}", name);
}
@Override
public 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 {
@Autowired
private 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()
.文章来源地址https://www.toymoban.com/news/detail-709116.html
到了这里,关于自定义 spring-boot-starter 暴露钩子的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!