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


 
       最近看了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 {


    @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()

 

 

 

 
 
 
 
.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值