开心一刻html
一只被二哈带偏了的柴犬,我只想弄死隔壁的二哈java
BeanFactoryPostProcessor接口很简单,只包含一个方法mysql
/** * 经过BeanFactoryPostProcessor,咱们自定义修改应用程序上下文中的bean定义 * * 应用上下文可以在全部的bean定义中自动检测出BeanFactoryPostProcessor bean, * 并在任何其余bean建立以前应用这些BeanFactoryPostProcessor bean * * BeanFactoryPostProcessor对自定义配置文件很是有用,能够覆盖应用上下文已经配置了的bean属性 * * PropertyResourceConfigurer就是BeanFactoryPostProcessor的典型应用 * 将xml文件中的占位符替换成properties文件中相应的key对应的value */ @FunctionalInterface public interface BeanFactoryPostProcessor { /** * 在应用上下文完成了标准的初始化以后,修改其内部的bean工厂 * 将加载全部bean定义,但还没有实例化任何bean. * 咱们能够覆盖或添加bean定义中的属性,甚至是提早初始化bean */ void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; }
推荐你们直接去读它的源码注释,说的更详细、更好理解git
简单来讲,BeanFactoryPostProcessor是spring对外提供的接口,用来拓展spring,可以在spring容器加载了全部bean的信息信息以后、bean实例化以前执行,修改bean的定义属性;有人可能会问,这有什么用?你们还记得spring配置文件中的占位符吗? 咱们会在spring配置中配置PropertyPlaceholderConfigurer(继承PropertyResourceConfigurer)bean来处理占位符, 举个例子你们就有印象了github
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:mysqldb.properties</value> </list> </property> </bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName"value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}"/> <property name="password"value="${jdbc.password}" /> </bean> </beans>
mysqldb.propertiesredis
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://192.168.1.100:3306/mybatis jdbc.username=root jdbc.password=root
PropertyPlaceholderConfigurer类的继承关系图算法
怎么用,这个问题比较简单,咱们实现BeanFactoryPostProcessor接口,而后将将其注册到spring容器便可,在spring启动过程当中,在常规bean实例化以前,会执行BeanFactoryPostProcessor的postProcessBeanFactory方法(里面有咱们想要的逻辑),完成咱们想要的操做;spring
重点应该是:用来干什么sql
上述占位符的例子是BeanFactoryPostProcessor的应用之一,但这是spring提供的BeanFactoryPostProcessor拓展,不是咱们自定义的;实际工做中,自定义BeanFactoryPostProcessor的状况确实少,反正至少我是用的很是少的,但我仍是有使用印象的,那就是对敏感信息的解密处理;上述数据库的链接配置中,用户名和密码都是明文配置的,这就存在泄漏风险,还有redis的链接配置、shiro的加密算法、rabbitmq的链接配置等等,凡是涉及到敏感信息的,都须要进行加密处理,信息安全很是重要数据库
配置的时候以密文配置,在真正用到以前在spring容器中进行解密,而后用解密后的信息进行真正的操做,下面我就举个简单的例子,用BeanFactoryPostProcessor来完整敏感信息的解密
加解密工具类:DecryptUtil.java
package com.lee.app.util; import org.apache.commons.lang3.StringUtils; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import java.security.Key; import java.security.SecureRandom; public class DecryptUtil { private static final String CHARSET = "utf-8"; private static final String ALGORITHM = "AES"; private static final String RANDOM_ALGORITHM = "SHA1PRNG"; public static String aesEncrypt(String content, String key) { if (content == null || key == null) { return null; } Key secretKey = getKey(key); try { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] p = content.getBytes(CHARSET); byte[] result = cipher.doFinal(p); BASE64Encoder encoder = new BASE64Encoder(); String encoded = encoder.encode(result); return encoded; } catch (Exception e) { throw new RuntimeException(e); } } public static String aesDecrypt(String content, String key) { Key secretKey = getKey(key); try { Cipher cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); BASE64Decoder decoder = new BASE64Decoder(); byte[] c = decoder.decodeBuffer(content); byte[] result = cipher.doFinal(c); String plainText = new String(result, CHARSET); return plainText; } catch (Exception e) { throw new RuntimeException(e); } } private static Key getKey(String key) { if (StringUtils.isEmpty(key)) { key = "hello!@#$world";// 默认key } try { SecureRandom secureRandom = SecureRandom.getInstance(RANDOM_ALGORITHM); secureRandom.setSeed(key.getBytes()); KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM); generator.init(secureRandom); return generator.generateKey(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { // key能够随意取,DecryptConfig中decryptKey与此相同便可 String newUserName= aesEncrypt("root", "hello!@#$world"); // QL34YffNntJi1OWG7zGqVw== System.out.println(newUserName); String originUserName = aesDecrypt(newUserName, "hello!@#$world"); System.out.println(originUserName); String newPassword = aesEncrypt("123456", "hello!@#$world"); // zfF/EU6k4YtzTnKVZ6xddw== System.out.println(newPassword); String orignPassword = aesDecrypt(newPassword, "hello!@#$world"); System.out.println(orignPassword); } }
配置文件:application.yml
server: servlet: context-path: /app port: 8888 spring: #链接池配置 datasource: type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8 #Enc[:解密标志前缀,]:解密后缀标志,中间内容是须要解密的内容 username: Enc[QL34YffNntJi1OWG7zGqVw==] password: Enc[zfF/EU6k4YtzTnKVZ6xddw==] initial-size: 1 #链接池初始大小 max-active: 20 #链接池中最大的活跃链接数 min-idle: 1 #链接池中最小的活跃链接数 max-wait: 60000 #配置获取链接等待超时的时间 pool-prepared-statements: true #打开PSCache,而且指定每一个链接上PSCache的大小 max-pool-prepared-statement-per-connection-size: 20 validation-query: SELECT 1 FROM DUAL validation-query-timeout: 30000 test-on-borrow: false #是否在得到链接后检测其可用性 test-on-return: false #是否在链接放回链接池后检测其可用性 test-while-idle: true #是否在链接空闲一段时间后检测其可用性 #mybatis配置 mybatis: type-aliases-package: com.lee.app.entity #config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mapper/*.xml # pagehelper配置 pagehelper: helperDialect: mysql #分页合理化,pageNum<=0则查询第一页的记录;pageNum大于总页数,则查询最后一页的记录 reasonable: true supportMethodsArguments: true params: count=countSql decrypt: prefix: "Enc[" suffix: "]" key: "hello!@#$world"
工程中解密:DecryptConfig.java
package com.lee.app.config; import com.lee.app.util.DecryptUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.context.EnvironmentAware; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.stream.Collectors; import java.util.stream.StreamSupport; /** * 敏感信息的解密 */ @Component public class DecryptConfig implements EnvironmentAware, BeanFactoryPostProcessor { private static final Logger LOGGER = LoggerFactory.getLogger(DecryptConfig.class); private ConfigurableEnvironment environment; private String decryptPrefix = "Enc["; // 解密前缀标志 默认值 private String decryptSuffix = "]"; // 解密后缀标志 默认值 private String decryptKey = "hello!@#$world"; // 解密能够 默认值 @Override public void setEnvironment(Environment environment) { this.environment = (ConfigurableEnvironment) environment; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { LOGGER.info("敏感信息解密开始....."); MutablePropertySources propSources = environment.getPropertySources(); StreamSupport.stream(propSources.spliterator(), false) .filter(ps -> ps instanceof OriginTrackedMapPropertySource) .collect(Collectors.toList()) .forEach(ps -> convertPropertySource((PropertySource<LinkedHashMap>) ps)); LOGGER.info("敏感信息解密完成....."); } /** * 解密相关属性 * @param ps */ private void convertPropertySource(PropertySource<LinkedHashMap> ps) { LinkedHashMap source = ps.getSource(); setDecryptProperties(source); source.forEach((k,v) -> { String value = String.valueOf(v); if (!value.startsWith(decryptPrefix) || !value.endsWith(decryptSuffix)) { return; } String cipherText = value.replace(decryptPrefix, "").replace(decryptSuffix, ""); String clearText = DecryptUtil.aesDecrypt(cipherText, decryptKey); source.put(k, clearText); }); } /** * 设置解密属性 * @param source */ private void setDecryptProperties(LinkedHashMap source) { decryptPrefix = source.get("decrypt.prefix") == null ? decryptPrefix : String.valueOf(source.get("decrypt.prefix")); decryptSuffix = source.get("decrypt.suffix") == null ? decryptSuffix : String.valueOf(source.get("decrypt.suffix")); decryptKey = source.get("decrypt.key") == null ? decryptKey : String.valueOf(source.get("decrypt.key")); } }
主要就是3个文件,DecryptUtil对明文进行加密处理后,获得的值配置到application.yml中,而后工程启动的时候,DecryptConfig会对密文进行解密,明文信息存到了spring容器,后续操做都是在spring容器的明文上进行的,因此与咱们平时的不加密的结果一致,可是却对敏感信息进行了保护;工程测试结果以下:
完整工程地址:spring-boot-BeanFactoryPostProcessor
有兴趣的能够去看下jasypt-spring-boot的源码,你会发现他的原理是同样的,也是基于BeanFactoryPostProcessor的拓展
为何DecryptConfig实现了BeanFactoryPostProcessor,将DecryptConfig注册到spring以后,DecryptConfig的postProcessBeanFactory方法就会执行?事出必有因,确定是spring启动过程当中会调用DecryptConfig实例的postProcessBeanFactory方法,具体咱们来看看源码,咱们从AbstractApplicationContext的refresh方法开始
不得不说,spring的命名、注释确实写得好,很明显咱们从refresh中的invokeBeanFactoryPostProcessors方法开始,你们能够仔细看下PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors方法,先按PriorityOrdered、Ordered、普通(没有实现PriorityOrdered和Ordered接口)的顺序调用BeanDefinitionRegistryPostProcessor,而后再按先按PriorityOrdered、Ordered、普通的顺序调用BeanFactoryPostProcessor,这个顺序仍是值得你们注意下的,若是咱们自定义的多个BeanFactoryPostProcessor有顺序之分,而咱们又没有指定其执行顺序,那么可能出现的不是咱们想要的结果
这里可能会有会有人有这样的的疑问:bean定义(BeanDefinition)是在何时加载到spring容器的,如何保证BeanFactoryPostProcessor实例起做用以前,全部的bean定义都已经加载到了spring容器
ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,在springboot的createApplicationContext阶段注册到spring容器的,也就是说在spring的refresh以前就有了ConfigurationClassPostProcessor实例;ConfigurationClassPostProcessor被应用的时候(调用其postProcessBeanDefinitionRegistry方法),会加载所有的bean定义(包括咱们自定义的BeanFactoryPostProcessor实例:DecryptConfig)到spring容器,bean的加载详情可查看:springboot2.0.3源码篇 - 自动配置的实现,是你想象中的那样吗,那么在应用BeanFactoryPostProcessor实例以前,全部的bean定义就已经加载到spring容器了,BeanFactoryPostProcessor实例也就能修改bean定义了
至此,BeanFactoryPostProcessor的机制咱们就清楚了,为何能这么用这个问题也就明了了
一、BeanFactoryPostProcessor是beanFactory的后置处理器接口,经过BeanFactoryPostProcessor,咱们能够自定义spring容器中的bean定义,BeanFactoryPostProcessor是在spring容器加载了bean的定义信息以后、bean实例化以前执行;
二、BeanFactoryPostProcessor类型的bean会被spring自动检测,在常规bean实例化以前被spring调用;
三、BeanFactoryPostProcessor的经常使用场景包括spring中占位符的处理、咱们自定义的敏感信息的解密处理,固然不局限与此;
其实只要咱们明白了BeanFactoryPostProcessor的生效时机,哪些场景适用BeanFactoryPostProcessor也就很清楚了