自定义 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()
.