Java开发从工作到原理--非SpringBean主动获取SpingBean的处理方式
开发过程中,我们经常会开发一些工具类,通常是以public static方法的形式,向外提供功能。比如日期格式化和转换功能,一般都会开发类似的DateUtils工具类。还有一些工具类比较特殊,比如Redis操作工具类,或者MQ操作工具类,这些工具类我们在实现的过程中,需要使用到SpringBean,比如RedisUtils中我们一般会用到RedisTemplate对象,而RedisTemplate一般是默认在RedisAutoConfiguration中被加载到Spring的BeanDefinitionRegistry中,然后由BeanFactory进行实例化并存储在BeanFactory中。
因为我们期望使用RedisUtils的static方法进行redis操作,所以在RedisUtils中redisTemplate的field也需要使用static进行修饰,将redisTemplate传入到RedisUtils的方法也需要使用static进行修饰,因此得到初步的代码如下:
那么接下来就是如何从SpringIOC容器中拿到BeanFactory对象,并且从BeanFactory中获取到redisTemplate对象了。
Spring中提供了BeanFactoryAware接口。实现这个接口,并且是托管给Spring类,在实例化过程中,SpringIOC容器会调用BeanFactoryAware接口方法setBeanFactory进行beanFaoctry对象的注入,这样看来我们需要一个中间桥接类来做这个事情
很显然这里我们调用获取bean的方法应该也是static的,否则就需要获取SpringUtils的bean,这个跟SpringUtils的设计初衷不符。因此beanFactory需要添加static修饰。既然SpringUtils是以静态方法向往提供功能,那我们的RedisUtils则不需要setRedisTemplate方法进行RedisTemplate注入了,直接借鉴单例模式的静态内部类实现模式进行处理,得到如下代码
这样,一般情况下的Redis操作,通过RedisUtils的静态方法都可以做了,但是特殊状态下的Redis操作还是存在问题!!!
特殊状态: 项目启动完成前的Redis操作,具体的问题:当进行Redis操作时,BeanFactory还没有创建SpringUtils的bean,也就是说SpringUtils还没有被注入BeanFactory对象,此时进行Redis操作会报BeanFactory的空指针异常,简单点的例子就是1、@PostConstruct方法中调用,2、@Autowired放在set方法或者构造方法中调用,3、Aware容器回调方法中调用等等;
当然静态field的初始化和静态代码块中的调用就不用说了。很显然静态field的初始化和静态代码块中的调用进行redis操作目前看来无法处理,除非将连接redis的操作也完全重新做一遍。
那么有没有什么办法能够处理前面提到的三个调用点时可能出现的问题呢,答案是有的。存在问题的原因是SpringUtils bean的创建和初始化排在了调用Redis操作的bean的创建和初始化之后,这个顺序Spring并没有提供相应的机制去控制。
对于特殊的bean如 BeanPostProcessor,spring提供了@Order注解,Ordered 接口,PriorityOrdered接口进行顺序控制。
因为没办法控制普通bean实例化顺序,那么只能通过提升SpringUtils的角色来提前初始化SpringUtils了
1、提升SpringUtils为Configuration,添加@Configuration注解去除@Component注解
结果:无效,原因:@Configuration注解标识继承@Componenet注解会和普通bean一样被扫描注册到BeanDefinitionRegistry,只不过会有ConfigurationClassPostProcessor对@Configuration注解的class进行解析,解析出内含的跟多需要注册的BeanDefinition。而不是会提前实例化对应的bean。
2、提升SpringUtils实现ImportSelector(包括子接口DefferredImportSelector)接口或者ImportBeanDefinitionRegistrar,然后通过Import注解引入
结果:有效,原因:ImportSelector或者ImportBeanDefinitionRegistrar接口实现类用于选择需要注册到BeanDefinitionRegistry的bean,所以会提前实例化,并注入BeanFactory对象,所以无论后续其他bean的生命周期方法中怎么调用都不会有问题
其他更底层能更早拿到BeanFactory的方法肯定还有很多,但跟日常工作差的有点远就不说了。
当然获取bean的方式处理BeanFactory还有ApplicationContext,但其实ApplicationContext也是继承了BeanFactory。还有通过
@Autowired BeanFactory beanFactory也可以直接获取BeanFactory,只是实现BeanFactoryAware接口能更早的拿到beanFactory对象,但其实这并不会影响到具体的使用。
向BeanFactory获取RedisTemplate时并不需要确定BeanFactory已经创建好了RedisTemplate对象,获取时如果没有,BeanFactory会即时创建。