尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot获取Bean的多种方式:从依赖注入到容器底层原理

SpringBoot获取Bean的多种方式:从依赖注入到容器底层原理 1. 写在前面为什么“怎么拿一个Bean”也能成为高频面试题先抛出个场景。你在一个老项目里维护代码某个普通工具类、某个监听器、甚至某个静态方法里突然需要调用Spring容器里管理的一个Service。你第一反应是不是直接new一个如果这个Service里又注入了Mapper、RedisTemplate、MQ生产者那new出来的对象里这些依赖全是null一调用就空指针。这时候你就明白了Spring容器里的Bean必须通过容器来获取不能手动new。“SpringBoot获取bean的几种方式”这个标题看起来基础但它几乎是SpringBoot面试必问题也是日常开发里实打实会踩坑的考点。网上搜一圈相关热词能看到大家真正关心的是构造函数注入和属性注入到底怎么选、为什么Bean名字首字母大写序列化后会变成小写、ApplicationContextAware是干嘛的、循环依赖报错怎么解决、Bean的作用域到底影响什么。这些问题本质上都绕不开“Bean是怎么被创建、怎么被拿出来的”这一条主线。这篇博文不打算讲教科书式的概念罗列而是把我在实际项目里用过的、见过的、踩过坑的获取方式全部梳理一遍。从最简单的Autowired到工具类里的静态获取再到BeanFactory和ObjectProvider这些偏冷门的方式每种方式我都会说清楚适用场景、代码写法、底层原理以及最容易翻车的地方。适合刚接触SpringBoot的初学者建立整体认知也适合有两年左右经验的开发者在面试前快速回顾一遍。2. 先搞懂Bean在Spring容器里到底是怎么存的2.1 容器的本质就是一个大Map很多人学SpringBoot上来就背“IOC容器”、“控制反转”但真正理解容器的存储结构才能理解后面所有获取Bean的方式。Spring容器最核心的存储结构说白了就是一个MapString, Objectkey是beanNamevalue是Bean实例。你写的Service、Component、Repository这些注解在容器启动的时候会被扫描到解析成BeanDefinitionBean的定义信息然后经过实例化、属性填充、初始化等步骤最终放进singletonObjects这个单例池里。单例池就是上面说的那个大Map它在DefaultSingletonBeanRegistry这个类里源码定义是private final MapString, Object singletonObjects new ConcurrentHashMap(256);为什么用ConcurrentHashMap因为Spring容器在启动和运行过程中可能有多线程并发访问Bean。比如接口同时来了多个请求每个请求线程都要从容器里拿同一个Service如果这个Map不是线程安全的拿到的实例可能是不完整或者不一致的。理解了这一点你就能明白获取Bean的本质不管是Autowired、ApplicationContext.getBean()、还是BeanFactory.getBean()最终都是从这个Map里按key找value或者在没有缓存的情况下触发一次Bean的创建过程。2.2 Bean的创建时机决定了你怎么拿Bean的创建时机分为两种饿汉式容器启动时就创建好所有单例Bean。SpringBoot默认的singleton作用域就是这种好处是启动时就能发现配置错误坏处是启动慢、占内存。懒汉式第一次获取时才创建。可以通过Lazy注解或在Bean的Bean配置上设置lazy-init来开启。这里有个实战中的经验如果项目里某个Bean初始化特别耗时比如启动时要加载大量数据到内存缓存建议设置成懒加载避免拖慢应用启动。但要注意懒加载的Bean在用的时候第一次获取会有一点延迟如果是在高并发路径上这个延迟可能被放大。2.3 Bean的scope对获取方式的影响获取Bean之前一定要确认这个Bean的作用域。默认是singleton单例整个容器只有一个实例。但还有prototype原型每次获取都新建一个实例。还有request、session、application这三种Web场景作用域。对于prototype的Bean从单例池里就找不到现成的实例了每次getBean都会执行一遍完整的创建流程。如果你用Autowired注入一个prototype的Bean到单例Bean里那实际上注入进去的还是那一个实例并不会每次都用新的。这也是一个常见的坑后面会专门讲。3. 最常见的获取方式依赖注入3.1 Autowired属性注入这是初学者最熟悉的方式。在类里直接声明一个字段加上Autowired注解Service public class OrderService { Autowired private UserService userService; public void createOrder() { userService.getUserInfo(1L); } }原理是Spring在创建OrderService这个Bean的时候检测到userService字段上有Autowired就会通过反射把这个字段赋值赋值来源就是容器里的UserServiceBean。注意这里的注入逻辑Spring首先按类型byType查找UserService如果找到唯一的一个就直接注入如果找到多个同类型的Bean就会按字段名byName去找对应的beanName。比如接口UserService有两个实现类UserServiceImpl1和UserServiceImpl2字段名叫userService就找不到对应的beanName这时会报NoUniqueBeanDefinitionException。解决办法是用Qualifier指定具体的beanNameAutowired Qualifier(userServiceImpl1) private UserService userService;或者用Resource(name userServiceImpl1)。这里说个实际经验属性注入的代码写起来最简洁但存在一些隐患。第一个隐患是它绕过了构造器导致Bean在创建时可能处于部分初始化状态——字段是后面反射塞进去的如果反射失败或者顺序不对可能出现空指针。第二个隐患是它让类与Spring容器强耦合单元测试的时候必须用SpringBootTest或者反射工具去塞值很不方便。第三个隐患是IDE会提示Field injection is not recommended。3.2 构造器注入Spring官方推荐的注入方式其实是构造器注入尤其是Spring 4.3之后如果类只有一个构造器甚至可以不写AutowiredService public class OrderService { private final UserService userService; public OrderService(UserService userService) { this.userService userService; } }如果类里有多个构造器就需要在要用的那个构造器上加上AutowiredService public class OrderService { private final UserService userService; Autowired public OrderService(UserService userService) { this.userService userService; } }为什么推荐构造器注入核心原因有三个。第一依赖不可变性。字段用final修饰Bean在创建完成后依赖关系就不会再变防止后续被意外修改。第二空指针风险更小。构造器在Bean实例化时就把依赖传进去了不会出现“对象已经创建但依赖还是null”的状态。Spring创建Bean的流程是先实例化、再填充属性如果属性注入在这个中间步骤出问题可能得到一个半初始化的Bean构造器注入在实例化阶段就完成依赖绑定失败就直接创建失败不会留下一个坏对象。第三循环依赖能被提前发现。构造器注入的循环依赖在容器启动阶段就会报错而不是运行到某个方法时才炸。这样问题暴露得越早修复成本越低。SpringBoot官方文档也明确推荐构造器注入。我在实际项目里新写的代码基本都是构造器注入尤其是核心的Service层。3.3 Setter注入Setter注入就是通过setter方法注入依赖加AutowiredService public class OrderService { private UserService userService; Autowired public void setUserService(UserService userService) { this.userService userService; } }这种方式的定位比较尴尬项目的启动速度和编码效率不如构造器代码简洁度又不如属性注入。但在某些场景下它有价值——比如父类里有一些公共依赖需要子类自定义处理或者依赖需要支持运行时替换不适合做成final字段。另外Spring对setter注入和属性注入的底层处理机制是一样的都是在Bean实例化后通过反射调用方法或赋值字段。3.4 Resource和Autowired的异同很多初学者会把Resource和Autowired混用其实它们来自不同的体系。Resource是JDK提供的注解javax.annotation.ResourceSpring只是做了适配支持。它的查找逻辑是先按名字byName找不到再按类型byType。Autowired是Spring自己的注解先按类型再按名字。在实际使用中如果同一个接口有多个实现用Resource指定名字会非常方便Resource(name userServiceImpl2) private UserService userService;但Resource不支持Primary和Qualifier的配合也不支持构造器注入。如果你需要更精细的注入控制还是用Autowired更顺手。4. 绕不开的容器上下文ApplicationContextAware4.1 什么时候需要主动拿容器依赖注入适合绝大多数场景但有些情况下你没法用Autowired工具类/静态方法里需要调用Spring管理的Bean。在非Spring管理的类里比如某个自定义线程池的任务类需要获取Bean。在配置类或某些动态代理的场景里需要在运行时按不同的条件拿不同的Bean。在监听器、定时任务框架的某些回调方法里拿不到注入的依赖。这时候就要通过ApplicationContextAware来获取容器。实现这个接口的类会在Bean初始化阶段被Spring回调setApplicationContext方法把容器对象传进来。4.2 工具类的标准写法我在项目里常用的是一个叫SpringContextUtils的工具类写法如下Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtils.applicationContext applicationContext; } public static T T getBean(ClassT clazz) { return applicationContext.getBean(clazz); } public static Object getBean(String beanName) { return applicationContext.getBean(beanName); } public static T T getBean(String beanName, ClassT clazz) { return applicationContext.getBean(beanName, clazz); } }这里有个非常关键的细节applicationContext是静态字段而setApplicationContext是实例方法。Spring在创建这个工具类的Bean时会先实例化对象然后回调setApplicationContext把容器引用赋给静态字段。坑点在哪里如果你在这个工具类的setApplicationContext方法里用了applicationContext来获取其他Bean而这些Bean又在创建过程中依赖了SpringContextUtils就会形成循环依赖。我自己遇到过这种情况某个配置类在初始化时需要调用SpringContextUtils.getBean()但此时SpringContextUtils的setApplicationContext还没执行完静态字段还是null直接空指针。解决方案有两个一是不要在Bean的初始化阶段调用工具类把调用时机推迟到实际业务执行时二是用ObjectProvider配合延迟获取后面会讲到。4.3 为什么静态字段会被Spring警告Component的Bean默认是singleton也就是说整个应用运行期间只有一个SpringContextUtils实例。用静态字段持有容器引用等于把这个单例的实例变量“提升”成了全局变量。这样做的好处是可以在任何地方通过SpringContextUtils.getBean()直接调用不需要先注入工具类坏处是静态字段在单元测试里很难清理多个测试类之间会互相污染。如果你在写测试代码建议在每个测试类的BeforeEach里重新设置容器或者在测试配置里排除掉这个工具类的自动扫描避免测试上下文互相干扰。4.4 实现ApplicationContextAware与直接注入ApplicationContext的对比其实不用实现ApplicationContextAware直接在类里注入ApplicationContext也能拿到容器Service public class OrderService { Autowired private ApplicationContext applicationContext; public void doSomething() { UserService userService applicationContext.getBean(UserService.class); } }两种方式都行。区别在于实现ApplicationContextAware是在Bean创建早期就被回调而且可以在静态工具类里保存容器直接注入ApplicationContext则更直观适合在具体的业务类里临时用一下容器。我个人习惯是如果只在一个类里用直接注入ApplicationContext如果需要在很多非Spring管理的类里用就封装一个静态工具类。5. 更底层的获取方式BeanFactory与ObjectProvider5.1 BeanFactory和ApplicationContext是什么关系ApplicationContext是BeanFactory的子接口它在BeanFactory的基础上扩展了国际化、事件发布、资源加载等功能。所以ApplicationContext本身就是一个BeanFactory你从它里面getBean本质上还是在调BeanFactory的getBean方法。BeanFactory的getBean是最底层的入口BeanFactory beanFactory applicationContext.getParentBeanFactory();实际上我们很少直接操作BeanFactory因为它的方法比较底层出错信息也不够友好。但在某些框架集成场景比如自己写一个Spring Boot Starter你会在BeanFactoryPostProcessor或BeanDefinitionRegistryPostProcessor里用到BeanFactory这时候就需要直接调用getBean、getBeanDefinition、containsBean这些方法。5.2 ObjectProvider延迟获取与多候选Bean的解药ObjectProvider是Spring 4.3引入的它是ObjectFactory的增强版提供了更丰富的获取方式。它的核心作用是延迟获取和优雅处理“找不到Bean”的情况。Service public class OrderService { Autowired private ObjectProviderUserService userServiceProvider; public void doSomething() { // 如果容器里有UserService就获取没有就返回null UserService userService userServiceProvider.getIfAvailable(); if (userService ! null) { userService.getUserInfo(1L); } // 如果有多个UserService实现可以拿到所有的 ListUserService userServices userServiceProvider.stream().collect(Collectors.toList()); } }ObjectProvider有几个常用的方法getIfAvailable()有就返回没有返回null不抛异常。getIfUnique()如果只有一个候选Bean就返回多个就返回null。getObject()和getBean类似没有会抛NoSuchBeanDefinitionException。stream()获取所有候选Bean的Stream。orderedStream()按照Order注解排序后的Stream。ObjectProvider最大的价值有两个。第一是解决“可选依赖”的问题某个Bean不一定要存在存在就用不存在也不用报错。这在框架开发、插件化架构里非常常见。第二是解决prototype Bean在单例Bean里失效的问题。因为ObjectProvider每次getObject()都相当于一次getBean会触发新的实例创建所以用它来获取prototype Bean能拿到新实例。5.3 prototype Bean的“失效”陷阱这是很多人在实战中踩过的大坑。先看代码Component Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class MyPrototypeBean { public MyPrototypeBean() { System.out.println(创建了一个新实例); } } Service public class TestService { Autowired private MyPrototypeBean myPrototypeBean; public void test() { System.out.println(myPrototypeBean); } }上面这段代码不管调用多少次test()打印出来的都是同一个对象。因为MyPrototypeBean在被注入到TestService时就被创建了一次之后TestService始终持有这个引用并不会因为Bean定义是prototype而每次重新创建。解决方案之一就是用ObjectProviderService public class TestService { Autowired private ObjectProviderMyPrototypeBean myPrototypeBeanProvider; public void test() { MyPrototypeBean bean myPrototypeBeanProvider.getObject(); System.out.println(bean); } }还有另一种方案是Lookup注解原理是Spring通过CGLIB生成子类每次调用带Lookup的方法时动态实现getBean逻辑。但这个写法比较hack不常用。5.4 ApplicationContext.getBean的三种重载如果你拿到了ApplicationContext它有多个getBean重载常用的有三个// 按beanName获取返回Object类型需要自己强转 Object bean applicationContext.getBean(userService); // 按类型获取不需要强转 UserService userService applicationContext.getBean(UserService.class); // 按beanName Class类型获取双重校验 UserService userService applicationContext.getBean(userService, UserService.class);第二种和第三种写法都比较推荐因为类型安全。第一种写法在老代码里见得多但强转容易出错比如beanName拼错或者类型不匹配会抛出BeanNotOfRequiredTypeException。这里还要补充一个点applicationContext.getBean(Class)在容器里存在多个同类型Bean时会抛NoUniqueBeanDefinitionException。如果你需要拿到指定的那个可以配合Primary这个不需要改调用代码或者在调用时指定beanName。6. 那些容易忽略的Bean命名和配置细节6.1 beanName是怎么生成的Spring对Component和Service这类注解的beanName默认规则是类名首字母小写。UserServiceImpl的beanName是userServiceImplOrderService的beanName是orderService。但对于Bean注解beanName默认是方法名。这里有一个很隐蔽的坑如果类名或方法名以连续的大写字母开头比如URLTemplateServiceSpring默认的beanName生成规则会把“URLTemplateService”变成“URLTemplateService”吗不是实际上Introspector.decapitalize的规则是如果前两个字符都是大写则beanName保持不变即URLTemplateService。这个规则直接导致了热搜词里的那个问题——“java bean 大写字母开头的变量json时就变成小写了”。实际发生的情况是字段名是URLTemplate这种以大写字母开头的命名Jackson在序列化时可能把首字母变成小写导致JSON字段名变成URLTemplate还是urlTemplate的问题一直让人困惑。更常见的是这类Bean在注入时beanName的大小写和预期不一致导致byName查找失败。解决方案是使用Component(urlTemplateService)显式指定beanName或者在序列化DTO上配合JsonProperty注解明确JSON字段名。6.2 配置文件中定制Bean名在Bean方法上你可以直接指定名字Configuration public class AppConfig { Bean(customName) public MyService myService() { return new MyService(); } }这样容器里的beanName就是customName。很多从XML配置转过来的项目习惯用这种方式保持配置迁移的一致性。6.3 排除扫描和自定义扫描路径获取Bean之前Bean得先被扫描到。SpringBoot默认扫描启动类所在包及子包。有时候你发现Autowired注入失败报NoSuchBeanDefinitionException第一反应应该是看看这个Bean所在的包是不是在扫描范围之外。解决办法有三种。一是在启动类上加ComponentScan指定额外的包路径二是把需要扫描的类移到启动类的子包下三是在Configuration配置类里用Bean手动注册。6.4 Primary与Order的取舍当同一个类型有多个Bean时Autowired默认按类型注入会失败。解决方案有三个Primary在某个实现类上标注表示它是默认的首选。Autowired遇到多个候选时优先选择Primary的那个。Qualifier(beanName)注入时指定要哪个优先级比Primary高。Resource(name beanName)从JDK注解体系指定名字。Primary适合“大多数时候用默认实现特殊场景再用Qualifier指定”的情况。比如MessageService接口有SmsMessageService和EmailMessageService两个实现默认是短信就在SmsMessageService上加Primary。Order则不同它影响的是多个同类型Bean在集合注入时的顺序Autowired private ListMessageService messageServices;如果不加Order这个List的顺序是Bean定义加载的顺序通常不稳定。加了Order(1)、Order(2)就按数字升序排列。这个在策略模式、责任链模式里非常常用。7. 循环依赖获取Bean时最头疼的问题7.1 什么是循环依赖为什么会报错循环依赖就是A依赖BB依赖ASpring在创建A时需要B创建B时需要A形成死循环。Spring解决不了所有循环依赖尤其是构造器注入的循环依赖。为什么会报错看这段经典报错日志里的关键信息Error creating bean with name a: Requested bean is currently in creation: Is there an unresolvable circular reference?Spring在创建Bean时会把正在创建的beanName放到一个singletonsCurrentlyInCreation集合里如果创建过程中发现又要创建自己就说明循环依赖了。7.2 为什么属性注入能解决构造器注入不能Spring对属性注入循环依赖的解决方案是靠三级缓存。简单说A在创建时提前把一个“早期引用”没有填充属性的半成品对象暴露出来B拿到这个引用后能完成自己的创建然后A再继续填充属性。三级缓存的名字分别是singletonObjects一级缓存存放完全创建好的单例Bean。earlySingletonObjects二级缓存存放提前暴露的早期引用。singletonFactories三级缓存存放ObjectFactory用于生成早期引用。构造器注入为什么不行因为构造器注入发生在Bean实例化阶段A还没new出来对象就无法提前暴露B想提前拿A的引用也拿不到。所以构造器循环依赖会直接创建失败。7.3 如何排查循环依赖在SpringBoot 2.6及以上版本默认禁止了循环依赖启动即报错。如果你在旧项目升级时遇到循环依赖报错要先分清是属性注入的还是构造器注入的。排查步骤看报错日志中的Requested bean is currently in creation确认是哪些Bean在循环。用IDE的依赖分析插件比如Idea的Spring Assistant画出Bean依赖图。找出循环链路一般就两三个类互相引用。重构方案把互相引用的逻辑拆开比如A不需要直接依赖B而是通过事件、消息或者提取公共接口来解耦。最省事的临时方案是在application.yml里设置spring: main: allow-circular-references: true但我不建议这么干。这个配置等于把安全检查关掉了如果后续有人新增了新的循环依赖容器不再报错坏味道就藏在代码里了。7.4 用Lazy打破循环依赖除了重构还有一个相对优雅的办法在依赖的注入点加Lazy。加了这个注解后Spring不会在Bean创建时立即注入依赖而是注入一个代理对象真正调用方法时才去容器里拿目标Bean。Service public class A { Autowired Lazy private B b; }这种方式能用但会让依赖关系变得隐晦排查问题时需要多看一层代理。我一般只在循环依赖的“出口”使用也就是说循环链上的某一环加Lazy就够了不要每个注入点都加。8. 获取Bean的冷门但好用的小技巧8.1 通过ApplicationRunner预先获取Bean有时候你想在应用启动完成时主动拿一批Bean做初始化工作比如把策略处理器注册到一个Map里。这时候可以用ApplicationRunner或CommandLineRunner在Spring容器完全启动后执行Component public class StrategyInitializer implements ApplicationRunner { Autowired private ApplicationContext applicationContext; private final MapString, MessageService messageServiceMap new HashMap(); Override public void run(ApplicationArguments args) { MapString, MessageService beans applicationContext.getBeansOfType(MessageService.class); beans.forEach((name, instance) - messageServiceMap.put(name, instance)); } }getBeansOfType是一个容易被忽略但很强大的方法它会返回所有指定类型的Beankey是beanName。这个API在处理策略模式、过滤器链、注册表时非常好用比一个个注入然后手动add要省事得多。8.2 从HttpServletRequest里拿Spring容器在一些老旧的过滤器或拦截器里如果既没有实现ApplicationContextAware也不是Spring管理的Bean可以通过WebApplicationContextUtils从ServletContext里拿容器WebApplicationContext context WebApplicationContextUtils .getWebApplicationContext(servletContext); UserService userService context.getBean(UserService.class);这种方式现在很少用了因为SpringBoot的拦截器、Filter自己都能通过注入拿到容器。但在和第三方非Spring框架集成时它可能是唯一可行的路子。8.3 通过实现BeanFactoryAware拿到的工厂级容器BeanFactoryAware和ApplicationContextAware类似但回调传进来的是更底层的BeanFactory。区别在于BeanFactory不能直接发布事件、不能访问资源功能上比ApplicationContext弱。一般用ApplicationContextAware就够用了只有在自己写Spring扩展、对性能极其敏感时才需要直接用BeanFactory。8.4 Autowired在静态字段上为什么不好使这是初学者经常碰到的想在工具类的静态字段上直接Autowired注入一个Bean发现Spring根本不管。Component public class StaticUtils { Autowired private static UserService userService; }上面的写法是无效的userService永远是null。原因很简单Spring的依赖注入是通过对象实例来完成的静态字段属于类而不是实例Spring的依赖注入处理器在填充字段时根本不会处理static字段。正确做法是像前面的SpringContextUtils那样通过实例方法回调保存静态引用。9. 常见报错与排查速查表整理了我在实际项目里遇到的几种典型报错以及对应的解决方案。报错信息关键字是什么问题常规解决办法NoSuchBeanDefinitionException容器里没有这个Bean检查扫描路径、注解是否漏加、Bean方法返回类型NoUniqueBeanDefinitionException同类型有多个Bean用Primary、Qualifier指定或ObjectProviderBeanCurrentlyInCreationException循环依赖重构依赖关系或用Lazy延迟注入BeanNotOfRequiredTypeExceptionbeanName和类型不匹配修改getBean调用传对Class类型UnsatisfiedDependencyException依赖注入失败看cause里的详细异常通常是被注入Bean的构造器抛错Error creating bean with name xxxBean创建过程中出现异常继续看cause里的根因比如数据库连接失败、配置缺失每次排查Bean相关报错有一个笨但有效的思路把问题拆成“Bean有没有被扫描到”和“Bean创建时依赖有没有满足”两个方向。先看有没有被扫描到注解有没有加对扫描包路径是否正确Bean方法有没有被Configuration所在的类加载。再看依赖有没有满足检查Autowired字段的类型和beanName检查构造器参数尤其是用了final字段的构造器注入如果有循环依赖就会在这一步暴露。10. 从获取Bean引出的工程化建议聊了这么多最后给点工程实践层面的建议。第一明确“能用依赖注入就别手动getBean”。手动获取Bean看着灵活但代价是让代码依赖了容器的全局状态单元测试、代码阅读、依赖分析都更困难。只要能用字段或构造器注入解决的依赖关系就不要绕道去ApplicationContext里get。第二在框架代码、通用模块、工具类里统一封装一个Bean获取工具类。团队里不同人写的获取方式如果五花八门有的注入ApplicationContext有的实现ApplicationContextAware有的用Autowired放在工具类里排查问题时就很难一眼看懂。我倾向于封装一个像SpringContextUtils这样的工具类所有非Spring管理的类都从这个工具类拿Bean。第三SpringBoot的自动装配看似“黑盒”但核心还是容器那一套。遇到奇奇怪怪的“注入失败”“创建失败”不要急着加配置强行绕过先搞清楚Bean从定义到实例化经历了哪几步。我会在排查时打开Spring的debug日志或者直接在BeanPostProcessor的实现类里打断点一步步看Bean在哪个环节断掉的。第四版本升级时关注行为变化。SpringBoot 2.6开始默认禁止循环依赖Spring Boot 3.0开始要求JDK 17及以上底层很多实现也变成了Jakarta EE命名空间。这些变化会直接影响Bean的创建和获取方式。升级前先看官方Release Notes不要照搬旧写法。我在实际开发中最后常用的其实还是那么几个业务代码里用构造器注入框架集成代码里用ApplicationContextAware封装工具类处理多实现策略时用ObjectProvider和getBeansOfType。把这几种用熟了遇到冷门场景再回头翻BeanFactory和BeanPostProcessor基本上就不会再被Bean的问题卡住了。
返回列表