在这之前,先记录一个 SpringBoot 中通过 Environment 加载配置文件转换成对应实体的方式。
首先我们的配置文件如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 proxy: servlets: - name: proxy servletUrl: /* targetUrl: http://10.0.0.1:4321 log: enable: true logFormat: "{remoteAddr} {method} uri: {source} --> {target}" corsControl: enable: false allowCORS: true - name: proxy servletUrl: /* targetUrl: http://10.0.0.1:4321 log: enable: true logFormat: "{remoteAddr} {method} uri: {source} --> {target}" corsControl: enable: false allowCORS: true
其次我们的实体类如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @Data public class ServletInfo { private String name; private String servletUrl; private String targetUrl; private LOG log; private CORSControl corsControl; public Map<String, String> toMap () { Map<String, String> map = new LinkedHashMap<>(); map.put("proxy.name" , getName()); map.put("proxy.servletUrl" , getServletUrl()); map.put("proxy.targetUrl" , getTargetUrl()); map.put("proxy.log.enable" , String.valueOf(getLog().isEnable())); map.put("proxy.log.logFormat" , String.valueOf(getLog().getLogFormat())); map.put("proxy.corsControl.enable" , String.valueOf(getCorsControl().isEnable())); map.put("proxy.corsControl.allowCORS" , String.valueOf(getCorsControl().isAllowCORS())); return map; } @Data public static class CORSControl { private boolean enable; private boolean allowCORS; } @Data public static class LOG { private boolean enable; private String logFormat; } }
最后,封装一个通过 Environment 加载配置文件转换成对应实体的通用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static List<ServletInfo> yaml2EntityByEnvironment (Environment environment) { List<ServletInfo> servletInfos=new ArrayList<>(); int index = 0 ; while (true ) { String prefix = "proxy.servlets[" + index + "]" ; String name = environment.getProperty(prefix + ".name" ); if (name == null ) { break ; } ServletInfo servletInfo = new ServletInfo(); servletInfo.setName(name); servletInfo.setServletUrl(environment.getProperty(prefix + ".servletUrl" )); servletInfo.setTargetUrl(environment.getProperty(prefix + ".targetUrl" )); ServletInfo.LOG log = new ServletInfo.LOG(); log.setEnable(Boolean.parseBoolean(environment.getProperty(prefix + ".log.enable" ))); log.setLogFormat(environment.getProperty(prefix + ".log.logFormat" )); servletInfo.setLog(log); ServletInfo.CORSControl corsControl = new ServletInfo.CORSControl(); corsControl.setEnable(Boolean.parseBoolean(environment.getProperty(prefix + ".corsControl.enable" ))); corsControl.setAllowCORS(Boolean.parseBoolean(environment.getProperty(prefix + ".corsControl.allowCORS" ))); servletInfo.setCorsControl(corsControl); servletInfos.add(servletInfo); index++; } return servletInfos; }
下面是正文。
测试环境,本文源码
Java:8 SpringBoot:2.5.14 示例场景:动态注册ProxyServlet
,间接实现类似于Nginx的反向代理功能 先理解如何实现动态注册 Bean
。
由于在 SpringBoot
中,先进行 Bean
的定义,再根据定义进行 Bean
的实例化,所以实现动态 Bean
,我们只需要动态注册 Bean
定义即可。
这就用到了 BeanDefinitionRegistryPostProcessor
中 postProcessBeanDefinitionRegistry
这个方法。
源码注释
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
定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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;@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
的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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); } }