SpringBoot的扩展点及其使用场景
FactoryBean
当创建对象的过程复杂时可使用 BeanFactory,调用方只需要注入就可使用
比如说这个对象包含了其他一些注入的类的方法,一些规则,需要调用其他的复杂逻辑的场景创建这个对象的时候。
EnvironmentPostProcessor
用来自定义加解密规则
/**
* 环境变量解密扩展点
* @author hubz
* @date 2022/10/10 11:09
*/
@Order(Integer.MIN_VALUE + 15)
public class EnvironmentDecryptExtensionPoint implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
//处理加密内容(获取到原有配置,然后解密放到新的map 里面(key是原有key))
MutablePropertySources propertySources = environment.getPropertySources();
//这里是获取我们指定的当前激活的配置文件
String[] profiles = environment.getActiveProfiles();
Properties prop = getConfig(profiles);
propertySources.addLast(new PropertiesPropertySource("allProp", prop));
Map<String, Object> decryptMap = new HashMap<>();
for (PropertySource<?> propertySource : propertySources) {
//是键值对的properties 才放入propertiesUtil工具类中
if (propertySource.getSource() instanceof Map) {
Map map = (Map) propertySource.getSource();
for (Object key : map.keySet()) {
String keyStr = (String) key;
String valueStr = MapUtil.getStr(map, key);
if (checkNeedDecrypt(valueStr)) {
decryptMap.put(keyStr, decryptValue(valueStr));
}
}
}
}
// 将解密的数据放入环境变量,并处于第一优先级上 (这里一定要注意,覆盖其他配置)
if (!decryptMap.isEmpty()) {
environment.getPropertySources().addFirst(new MapPropertySource("custom-encrypt", decryptMap));
}
}
/**
*
* @author hubz
* @date 2022/10/11 10:50
*
* @param profiles
* @return java.util.Properties
**/
private Properties getConfig(String[] profiles) {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
List<Resource> resourceList = new ArrayList<>();
getResource(resolver, resourceList, "classpath*:/*.properties");
getResource(resolver, resourceList, "classpath*:/log.config");
if (profiles != null) {
for (int i = 0; i < profiles.length; i++) {
String p = "classpath*:/config/" + profiles[i] + "/*.properties";
getResource(resolver, resourceList, p);
}
}
try {
//spring properties管理对象 将properties加载处理进ioc
PropertiesFactoryBean config = new PropertiesFactoryBean();
config.setLocations(resourceList.toArray(new Resource[]{}));
config.afterPropertiesSet();
return config.getObject();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void getResource(PathMatchingResourcePatternResolver resolver, List<Resource> resourceList, String path) {
try {
Resource[] resources = resolver.getResources(path);
resourceList.addAll(Arrays.asList(resources));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断配置项的值是否需要解密:以 ENC0 开头的字符串
* @author hubz
* @date 2022/10/11 10:16
*
* @param value 配置项的值
* @return java.lang.Boolean true 需要解密 false 不需要
**/
private Boolean checkNeedDecrypt(String value) {
return value.startsWith("ENC0:");
}
/**
* 对配置项的值进行解密
* @author hubz
* @date 2022/10/11 10:17
*
* @param value 配置项的值
* @return java.lang.String 解密后的结果
**/
private String decryptValue(String value) {
value = value.substring(4);
return DesConfig.DES.decryptStr(value);
}
}
BeanFactoryProcessor
容器扩展接口,调用时机在 Spring 读取 BeanDefinition 信息之后,实例化 bean 之前。
可用作修改已经注册的 BeanDefinition,比如修改类的属性、是否懒加载、类名等。
BeanDefinitionRegistryPostProcessor
这个接口也只有一个实现方法,这个方法参数是 BeanDefinitionRegistry,我们的容器是实现了这个接口,所以可以对容器中的 beandefinition 进行增删查改。
BeanPostProcessor
Bean 的后置处理器,在 bean 的生命周期各个阶段起到扩展和应用,常见的应用有初始化前@postConstruct 解析执行,初始化后 aop 的动态代理。
InstantiationAwareBeanPostProcessor
Bean 实例化处理器,相当于在创建这个 bean 的前后做扩展,在 bean 通过反射创建前后的扩展。
- postProcessAfterInstantiation:注意它的返回值是 boolean 而不是 obj,因为它的返回值是决定要不要调用 postProcessPropertyValues 方法。
- postProcessProperties: 顾名思义处理属性值作用:对属性值进行修改,如果 postProcessAfterInstantiation 方法返回 false,该方法可能不会被调用。可以在该方法内对属性值进行修改。
SmartInstantiationAwareBeanPostProcessor
InstantiationAwareBeanPostProcessor 的子接口,比父类多了三个方法,我们常用的 AbstractAdvisorAutoProxyCreator 用于 aop 和提前暴露解决循环依赖的就是该接口的实现。
-
predictBeanType:该触发点发生在 postProcessBeforeInstantiation 之前,这个方法用于预测 Bean 的类型,返回第一个预测成功的 Class 类型,如果不能预测返回 null;当你调用 BeanFactory.GetType (name)时当通过 bean 的名字无法得到 bean 类型信息时就调用该回调方法来决定类型信息。
-
determineCandidateConstructors:该触发点发生在 postProcessBeforeInstantiation 之后,用于确定该 bean 的构造函数之用,返回的是该 bean 的所有构造函数列表。用户可以扩展这个点,来自定义选择相应的构造器来实例化这个 bean。
-
getEarlyBeanReference:该触发点发生在 postProcessAfterInstantiation 之后,当有循环依赖的场景,当 bean 实例化好之后,为了防止有循环依赖,会提前暴露回调方法,用于 bean 实例化的后置处理。这个方法就是在提前暴露的回调方法中触发。
MergedBeanDefinitionPostProcessor
这个用于收集 bean 上的注解,比如我们常见的@Value、@NacosValue、@Mapper 等,收集了,收集好的数据缓存在 injectionMetadataCache 中,以便后续比如属性注入的时候使用。
ApplicationListener
ApplicationListener 可以监听某个事件的 event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件。但是 spring 内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。最常见的 Spring Cloud 中的注册就是通过监听 WebServerInitializedEvent 事件来进行服务注册。
当然还有很多不同的事件,在 Spring 生命周期过程中。会进行触发。
| 事件 | 触发时期 |
|---|---|
| ContextRefreshedEvent | ApplicationContext 被初始化或刷新时 |
| ContextStartedEvent | 当使用 ConfigurableApplicationContext (ApplicationContext 子接口)接口中的 start () 方法启动 ApplicationContext 时,该事件被发布。 |
| ContextStoppedEvent | 当使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件 |
| ContextClosedEvent | 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被发布。 |
| RequestHandledEvent | 当 Spring 处理用户请求结束后,系统会自动触发该事件。 |
XxxxAware
资源注入,一般我们常用的有 BeannameAware 用于设置 bean 名字、BeanFactoryAware 用于设置 beanFactory 等,还有很多 aware 接口,我们根据 bean 对应所需的属性设置即可。
InitializingBean/DisposableBean
InitializingBean 初始化 Bean 时候判断 bean 是否实现该接口,如果实现则会调用 afterPropertiesSet 方法,它的调用时机在 BeanPostProcessor 的 before、after 方法中间。用于 bean 的初始化
FactoryBean
顾名思义工厂 bean,当我们调用 getBean 获取 bean 的时候,如果判断该 bean 是工厂 bean 那么就会调用工厂的 getObject 方法来获取。本质就是避免了我们创建 bean 所需要提供的大量配置,用工厂 bean 简洁创建。避免底层繁琐实现。
SmartInitializingSingleton
这个类的作用是在 spring 容器管理的所有**单例对象(非懒加载对象)**初始化完成之后调用的回调接口。它只有一个方法 afterSingletonsInstantiated
@PostConstruct
这个注解是用于实例化后的初始化操作的,和 InitializingBean 作用很像,但两者的实现位置不一样,postConstruct 是在 BeanPostProcessor 的前置方法中判断和回调的。而 InitializingBean 则是前置方法执行完进行初始化操作中回调的。
CommandLineRunner
这个接口也只有一个方法:run (String… args),触发时机为整个项目启动完毕后,自动执行。如果有多个 CommandLineRunner,可以利用@Order 来进行排序。
使用场景:用户扩展此接口,进行启动项目之后一些业务的预处理。
SpringApplicationRunListener&ApplicationContextInitializer
这两个接口无法通过@Component 组件引入,但可以配置在 Spring. Factories 方法引入,因为他是用 SPI 机制去扫描获取对应的监听器和初始化。
SecretRequestAdvice 请求解密
SecretResponseAdvice 响应加密
参考/原文: