SpringBoot中动态注册Bean的方式

测试环境,本文源码

  • Java:8
  • SpringBoot:2.5.14
  • 示例场景:动态注册ProxyServlet,间接实现类似于Nginx的反向代理功能

先理解如何实现动态注册 Bean

由于在 SpringBoot 中,先进行 Bean 的定义,再根据定义进行 Bean 的实例化,所以实现动态 Bean ,我们只需要动态注册 Bean 定义即可。

这就用到了 BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry 这个方法。

源码注释

Modify the application context's internal bean definition registry after its standard initialization. All regular bean definitions will have been loaded, but no beans will have been instantiated yet. This allows for adding further bean definitions before the next post-processing phase kicks in.

所有常规的Bean都已经定义但尚未实例化时,这时候,你可以再新增 Bean 定义

一、通用方式

先说一个小插曲,建议搭配源码食用。假如三个类,他们分别实现且只实现了以下三个接口

  • BeanDefinitionRegistryPostProcessor
  • ApplicationContextAware
  • EnvironmentAware

这时候,这三个 Bean 的默认加载顺序如下。

并且,这三个 Bean 的方法执行顺序也是跟加载顺序相同。

但是,如果 Bean 实现了一个比 ApplicationContextAware 或者 EnvironmentAware 更先加载的 Bean ,那么就会出现先执行 setApplicationContext 或者 setEnvironment 的方法,为啥嘞?

是因为在 Bean 初始化前,先判定有没有实现 Aware 接口,如果实现过了,那么就直接优先调用 Aware 中的方法。如图。

既然明白了这个流程,那我们就可以实现功能了。

该通用方式是适用于 SpringBoot 框架中通用动态注册 Bean 的做法。主要是通过 BeanDefinitionRegistryPostProcessor 动态注册 Bean 定义。

java 复制代码
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import java.util.HashMap;
import java.util.Map;

/**
 * 动态配置bean
 *
 * @author chenchuancheng
 * @since 2024/07/11 22:50
 */
@Configuration
public class DynamicBeanConfig implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

    private Environment environment;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ServletRegistrationBean.class);
        builder.addConstructorArgValue(new ProxyServlet());
        builder.addConstructorArgValue(environment.getProperty("proxy.servletUrl"));
        Map<String, String> initParams = new HashMap<>();
        initParams.put("targetUri", environment.getProperty("proxy.targetUrl"));
        initParams.put("log", "true");
        builder.addPropertyValue("initParameters", initParams);
        builder.addPropertyValue("name", environment.getProperty("proxy.name"));
        registry.registerBeanDefinition("proxyServlet", builder.getBeanDefinition());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

除了实现 Aware 接口,也可以通过构造函数注入,也可以保证调用方法时有值了。

二、特定方式

这个是使用 ServletContext 动态注册 Servlet 的方式。

java 复制代码
import org.mitre.dsmiley.httpproxy.ProxyServlet;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ProxyServletInitializer implements ServletContextInitializer {

    private final Environment environment;

    public ProxyServletInitializer(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        registerProxyServlet(servletContext,environment.getProperty("proxy.name"),environment.getProperty("proxy.targetUrl"));
    }

    private void registerProxyServlet(ServletContext servletContext, String name, String targetUri) {
        ProxyServlet proxyServlet = new ProxyServlet();
        ServletRegistration.Dynamic registration = servletContext.addServlet(name + "ProxyServlet", proxyServlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/" + name + "/*");
        
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put("targetUri", targetUri);
        initParameters.put("log", "true");
        registration.setInitParameters(initParameters);
    }
}
相关推荐
從南走到北11 分钟前
JAVA同城服务场馆预约门店预约健身房瑜伽馆预约系统支持H5小程序APP源码
java·开发语言·小程序
韩立学长15 分钟前
【开题答辩实录分享】以《智慧农业信息化服务平台小程序》为例进行答辩实录分享
数据库·spring boot·小程序
爱学的小码19 分钟前
JavaEE初阶——多线程3(案例)
java·开发语言·单例模式·java-ee
جيون داد ناالام ميづ19 分钟前
Spring Boot 核心原理(五):配置管理怎么玩?从基础到多环境再到配置中心
java·spring boot·后端
_小九24 分钟前
【开源】耗时数月、我开发了一款功能全面【30W行代码】的AI图床
前端·后端·开源
疯狂的程序猴29 分钟前
完整指南:iPhone崩溃日志查看与分析方法及低内存崩溃处理
后端
秧歌star51939 分钟前
PageHelper 分页失效原因分析与正确实践
后端
疯狂的程序猴44 分钟前
苹果iOS应用签名与上架App Store完整指南包括注意事项
后端
回家路上绕了弯44 分钟前
生产环境服务器变慢?从应急到根因的全流程诊断处理指南
分布式·后端
小胖霞44 分钟前
Node+Express+MySQL 后端生产环境部署,实现注册功能(三)
前端·后端