关于建设 医院网站的请示,网站设计工资,wordpress如何更改登录地址,学生ppt模板免费下载 素材#x1f9d1; 博主简介#xff1a;CSDN博客专家#xff0c;「历代文学网」#xff08;PC端可以访问#xff1a;https://lidaiwenxue.com/#/?__c1000#xff0c;移动端可关注公众号 “ 心海云图 ” 微信小程序搜索“历代文学”#xff09;总架构师#xff0c;首席架构师…博主简介CSDN博客专家「历代文学网」PC端可以访问https://lidaiwenxue.com/#/?__c1000移动端可关注公众号 “心海云图” 微信小程序搜索“历代文学”总架构师首席架构师也是联合创始人16年工作经验精通Java编程高并发设计分布式系统架构设计Springboot和微服务熟悉LinuxESXI虚拟化以及云原生Docker和K8s热衷于探索科技的边界并将理论知识转化为实际应用。保持对新技术的好奇心乐于分享所学希望通过我的实践经历和见解启发他人的创新思维。在这里我希望能与志同道合的朋友交流探讨共同进步一起在技术的世界里不断学习成长。商务合作请搜索或扫码关注微信公众号 “心海云图”优雅终结启动顺序噩梦ObjectProvider —— Spring 4.3 开始引入从“饥渴式依赖”到“按需获取”一次依赖注入的思想跃迁缘起一个再普通不过的配置类为何启动就报错就在上周我在维护一个 Spring Boot 4.0 项目时遇到了一个棘手的问题ConfigurationpublicclassWebmvcEncryptionConfiguration{AutowiredprivateHttpMessageConverterObjecthttpMessageConverter;// 注入总为 nullBeanFilterRegistrationBeanEncryptedFilterencryptedFilterRegistrationBean(){// 启动时抛出 NullPointerExceptionhttpMessageConverter 未初始化returnnewFilterRegistrationBean(newEncryptedFilter(httpMessageConverter));}}无论我怎么调整注入方式——字段注入、构造器注入、方法参数注入——HttpMessageConverter始终是null。更诡异的是只要注释掉这个配置类整个应用就能正常启动HttpMessageConverter 也能被完美初始化。直觉告诉我这不是注入“写错了”而是FilterRegistrationBean 的初始化时机早于 HttpMessageConverter 的自动配置。Spring 容器尚未完成 WebMvc 基础设施的装配我的过滤器却已经急不可耐地索要依赖——这就像在清晨 5 点去咖啡馆要一杯现磨手冲咖啡师还在通勤路上。如何让依赖“等等再给”答案就藏在 Spring 4.3 引入的一个低调接口中ObjectProvider。一、版本之问ObjectProvider 究竟出生在哪一年关于ObjectProvider的引入版本网上存在不少混淆信息。我需要在这里正本清源✅Spring Framework 4.3 首次引入 ObjectProvider❌Spring 5.0 说、Spring Boot 2.0 说均为误传权威证据链Spring 官方博客2016年3月明确写道“Spring Framework 4.3 引入了ObjectProvider它是现有ObjectFactory接口的扩展提供getIfAvailable和getIfUnique等便捷签名”版本时间线Spring 4.3.0.RC1 发布于 2016年3月4.3.0.GA 发布于 2016年5月而 Spring 5.0 在 2017年9月才正式发布历史实证有开发者反馈在 Spring 4.2.4 中遇到NoClassDefFoundError: org/springframework/beans/factory/ObjectProvider升级到 4.3 后解决为什么会有 5.0 的说法因为 Spring 5.0 和 Spring Boot 2.0增强了ObjectProvider如添加orderedStream()方法但它真正的诞生时刻是2016 年 5 月Spring Framework 4.3 GA。二、原理深潜ObjectProvider 为什么能解决顺序问题2.1 两种依赖获取哲学的较量要理解ObjectProvider的优雅首先要看清Autowired的本质维度AutowiredObjectProviderT获取时机Bean 实例化立即解析调用getObject()时延迟解析依赖强度默认强依赖requiredtrue可选依赖允许不存在多实例处理必须配合Qualifier/Primary支持运行时动态筛选、流式处理原型 Bean注入固定实例违背原型语义每次调用getObject()获取新实例异常处理启动即失败将异常推迟到业务运行时形象的比喻Autowired“咖啡必须在我进办公室前就放在桌上”ObjectProvider“给我一张咖啡券我想喝的时候自己去打”2.2 Spring 源码级的特殊对待为什么ObjectProvider能“躲过”启动时的依赖解析秘密藏在DefaultListableBeanFactory.resolveDependency()中 OverridepublicObjectresolveDependency(DependencyDescriptordescriptor,...){// 1. OptionalTif(Optional.classdescriptor.getDependencyType()){returncreateOptionalDependency(descriptor,requestingBeanName);}// 2. ObjectFactoryT、ObjectProviderT —— 重点在这里elseif(ObjectFactory.classdescriptor.getDependencyType()||ObjectProvider.classdescriptor.getDependencyType()){// 不触发真正的依赖解析直接返回一个“懒加载代理”returnnewDependencyObjectProvider(descriptor,requestingBeanName);}// ... 其他情况else{returndoResolveDependency(descriptor,...);// 立即解析}}关键结论当 Spring 发现你注入的是ObjectProviderT时它不会去容器中查找 T 类型的 Bean而是直接给你一个DependencyObjectProvider对象。真正的 Bean 查找被推迟到你第一次调用getObject()或getIfAvailable()的时刻。这正是解决FilterRegistrationBean初始化顺序问题的终极武器——我的 Filter 可以提前注册但 HttpMessageConverter 可以“按需再取”。三、实战改造从“饥渴注入”到“按需获取”3.1 问题代码的完整解决方案publicclassEncryptedFilterimplementsFilter{privatefinalWebClientConfigPropertiesproperties;privatefinalRequestMappingHandlerMappinghandlerMapping;privatefinalObjectProviderHttpMessageConverter?converterProvider;// 注入提供者publicEncryptedFilter(WebClientConfigPropertiesproperties,RequestMappingHandlerMappinghandlerMapping,ObjectProviderHttpMessageConverter?converterProvider){this.propertiesproperties;this.handlerMappinghandlerMapping;this.converterProviderconverterProvider;}OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{// 真正需要转换器时才去获取——此时容器已完成自动配置HttpMessageConverter?converterconverterProvider.getIfAvailable();if(converter!null){// 使用转换器处理加解密逻辑}chain.doFilter(request,response);}}配置类彻底抛弃字段注入ConfigurationpublicclassWebmvcEncryptionConfiguration{BeanFilterRegistrationBeanEncryptedFilterencryptedFilterRegistrationBean(WebClientConfigPropertieswebClientConfigProperties,RequestMappingHandlerMappingrequestMappingHandlerMapping,ObjectProviderHttpMessageConverter?httpMessageConverterProvider){// 参数注入EncryptedFilterfilternewEncryptedFilter(webClientConfigProperties,requestMappingHandlerMapping,httpMessageConverterProvider);// 传递的是 Provider不是具体的 ConverterFilterRegistrationBeanEncryptedFilterregistrationBeannewFilterRegistrationBean();registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);registrationBean.setFilter(filter);registrationBean.setName(encryptedFilter);registrationBean.addUrlPatterns(/*);returnregistrationBean;}}✅改动极小但彻底解决了启动顺序问题。四、ObjectProvider 的四大黄金场景如果说解决启动顺序只是ObjectProvider的“意外之喜”那么下面四个场景才是它真正的设计目标也是每一位 Spring 开发者都应该掌握的进阶技巧。场景一优雅处理可选依赖替代Autowired(requiredfalse)传统写法——丑陋的空值检查ServicepublicclassNotificationService{Autowired(requiredfalse)privateSmsServicesmsService;// 可能为 nullpublicvoidsendAlert(Stringmessage){if(smsService!null){// 每次都要判空smsService.send(message);}}}ObjectProvider 写法——函数式、零判空ServicepublicclassNotificationService{AutowiredprivateObjectProviderSmsServicesmsServiceProvider;publicvoidsendAlert(Stringmessage){// 存在才执行不存在什么都不发生smsServiceProvider.ifAvailable(sms-sms.send(message));// 或者提供默认实现SmsServicesmssmsServiceProvider.getIfAvailable(()-newMockSmsService());}}场景二在单例 Bean 中正确获取原型 Bean最核心用途错误示范——90% 开发者踩过的坑 ComponentScope(prototype)publicclassTaskProcessor{/* ... */}Service// 默认单例publicclassTaskService{AutowiredprivateTaskProcessortaskProcessor;// ❌ 仅在启动时注入一次之后永远是同一个实例publicvoidexecute(){taskProcessor.process();// 每次用的都是同一个对象}}ObjectProvider 拯救ServicepublicclassTaskService{AutowiredprivateObjectProviderTaskProcessortaskProcessorProvider;publicvoidexecute(){// 每次调用都从容器获取全新的原型实例TaskProcessorprocessortaskProcessorProvider.getObject();processor.process();}}与 Lookup 对比方式优点缺点Lookup注解简洁无需显式注入仅能用于方法调试困难ObjectProvider灵活、可编程、支持条件判断需注入 Provider 对象ApplicationContext功能最全耦合容器性能稍差结论原型 Bean 获取首选ObjectProvider。场景三动态筛选多个同类型 Bean当一个接口有多个实现时传统方式必须指定Qualifier或Primary编译时就决定了用哪一个。ObjectProvider 实现运行时动态选择publicinterfacePaymentService{booleansupports(Stringtype);voidpay(Orderorder);}ServicepublicclassPaymentProcessor{AutowiredprivateObjectProviderPaymentServicepaymentServiceProvider;publicvoidprocessPayment(Orderorder,StringpaymentType){PaymentServiceservicepaymentServiceProvider.stream().filter(ps-ps.supports(paymentType)).findFirst().orElseThrow(()-newUnsupportedOperationException(不支持的支付方式));service.pay(order);}}加上排序支持Spring 5.1// 按照 Order 或 Ordered 接口的顺序获取所有实现paymentServiceProvider.orderedStream().forEach(PaymentService::someCommonOperation);场景四解决循环依赖虽然 Spring 三级缓存能解决大部分 setter 注入的循环依赖但对于构造器注入的循环依赖依然束手无策。ObjectProvider可以轻松破局ComponentpublicclassServiceA{privatefinalObjectProviderServiceBserviceBProvider;publicServiceA(ObjectProviderServiceBserviceBProvider){this.serviceBProviderserviceBProvider;}publicvoiddoSomething(){// 需要 B 的时候再去拿此时 B 一定已初始化完毕ServiceBbserviceBProvider.getObject();}}ComponentpublicclassServiceB{privatefinalServiceAserviceA;// 正常构造器注入publicServiceB(ServiceAserviceA){this.serviceAserviceA;}}五、与其他方案的对比我该如何选择场景推荐方案理由普通依赖注入Autowired构造器注入最简洁、最符合 DI 原则依赖可能不存在ObjectProvider.getIfAvailable()无需判空、函数式风格单例中获取原型 BeanObjectProvider最均衡灵活、低耦合、性能优运行时多实现动态选择ObjectProvider.stream()原生支持流式处理仅需简单原型获取Lookup代码最少无额外注入需要完全控制容器ApplicationContext功能最全但耦合度高启动顺序问题ObjectProvider唯一的“延迟查找”解核心决策标准只要存在“依赖的创建/解析时机晚于当前 Bean 的初始化时机”就应该考虑ObjectProvider。六、结语为什么说 ObjectProvider 是一颗“时间胶囊”回顾开篇的问题FilterRegistrationBean之所以无法直接注入HttpMessageConverter本质上是基础设施组件与业务组件初始化阶段的不匹配。ObjectProvider的伟大之处不是它提供了什么复杂的功能而是它改变了我们对依赖注入的思考方式从“饥渴式”到“按需式”依赖不一定要在注入点就绪可以等到真正使用时才获取从“静态绑定”到“动态解析”依赖的选择可以从编译期推迟到运行期从“强依赖”到“可选依赖”允许依赖的不确定性并在语言层面优雅处理这让我想起计算机科学中的一句名言“计算机科学中的所有问题都可以通过增加一个间接层来解决”。ObjectProviderT正是这样一层优雅的“间接”——它将“依赖”封装成“获取依赖的能力”将“对象”升级为“对象提供者”。当你下次在 Spring Boot 启动过程中遇到类似的顺序问题时不妨问问自己“我是否可以在当前 Bean 中不为具体的依赖而为它的‘提供者’预留一个位置”这个答案早在 2016 年春天Spring 4.3 就已经为你准备好了。附录ObjectProvider 核心 API 速查表方法行为典型场景T getObject()获取 Bean不存在/不唯一则抛异常必需依赖、原型 BeanT getIfAvailable()存在则返回否则返回 null可选依赖T getIfAvailable(SupplierT)不存在则返回默认值提供降级方案void ifAvailable(ConsumerT)存在时执行操作函数式风格、零判空T getIfUnique()唯一则返回否则返回 null期望单一候选StreamT stream()返回所有匹配 Bean 的流多实现处理StreamT orderedStream()按 Order 排序的流有序多实现本文由真实生产问题驱动结合 Spring 源码与官方文档撰写。