Spring Bean实例化方式详解:从构造器到工厂方法一次打通

发布时间:2026/10/3 23:26:00

Spring Bean实例化方式详解:从构造器到工厂方法一次打通 Spring 里的 bean 实例化方式说白了就是搞清楚一个问题容器到底是怎么把一个类变成对象的。很多人写了很久的 Component、Service结果一旦要接管第三方 jar 包里的类就愣住了——类的源码不在你手上构造器能不能用、有没有工厂方法都不是你说了算。这篇博文专门用“非自定义 bean”来做演示也就是拿 JDK 自带的类SimpleDateFormat、Calendar、DateFormat 这类把 Spring 的构造器实例化、静态工厂实例化、实例工厂实例化全部跑一遍再补上 FactoryBean 和 Bean 两种写法把 bean 实例化方式这个点一次打通。1. 为什么用“非自定义 bean”来演示实例化方式1.1 先纠一个学习误区很多初学者会默认认为bean 就是“类上面加个注解或者 XML 里写个 class 属性容器帮我 new 一下”。这个理解在你自己写的类上是成立的因为你的类往往有公开的无参构造器怎么折腾都行。但真实的项目里大量 bean 来自第三方依赖比如数据库连接池、Redis 客户端、消息队列的 producer这些类的设计者不会为 Spring 专门留一个无参构造器。更麻烦的情况是有的类构造器是 private 的典型的单例有的类本身是抽象类根本不能 new还有的类创建对象必须传入一堆必填参数。这些场景下容器不能靠默认逻辑“瞎 new”必须由配置者明确告诉它你该走哪条路。这个“告诉容器怎么创建对象”的过程就是 bean 的实例化方式。1.2 用 JDK 自带类做实验的好处标题里说的“非自定义 bean”意思就是这些类不是我们写的我们无法通过修改源码去迁就容器。用它来演示实例化方式有一个特别好的实验效果把变量隔离了。如果我们自己定义一个 Student 类再配一个bean classcom.example.Student你很难分清楚“实例化成功”是因为 Spring 本来就会这样做还是因为你的类刚好写了无参构造器。而换成 JDK 自带的类比如java.util.Calendar它是抽象类你第一反应就是“这玩意儿怎么 new 出来”这时候再去研究 factory-method印象会深得多。这些 JDK 类还有一个好处不需要引入任何第三方依赖Maven 里只要一个 spring-contextJDK 自带的类就能撑起全部演示场景。做实验的成本低踩坑的成本也低。2. 构造器实例化最直观的bean方式2.1 无参构造器容器最省事的“new”构造器实例化是最基础的方式。配置里只要给一个 class 全限定名容器启动时就会通过反射找到这个类调用它的无参构造器创建对象。bean idstringList classjava.util.ArrayList/这样一个 bean 定义等价于new ArrayList()。注意 class 属性必须是全限定名因为 Spring 底层的Class.forName()需要完整包名才能加载类。如果只写ArrayList容器会直接抛ClassNotFoundException。测试代码很简单ClassPathXmlApplicationContext context new ClassPathXmlApplicationContext(beans.xml); ArrayList? list context.getBean(stringList, ArrayList.class); System.out.println(list.size()); // 0这里有个很容易忽略的细节getBean的时候我传了ArrayList.class作为第二个参数Spring 会帮你做类型转换和校验。如果类型不匹配会得到BeanNotOfRequiredTypeException。2.2 带参构造器与 constructor-arg 的匹配现实里很多第三方的类没有无参构造器必须传参数才能创建。比如java.text.SimpleDateFormat它最常用的构造器是SimpleDateFormat(String pattern)需要一个日期格式字符串。这个时候配置要变成bean iddateFormatter classjava.text.SimpleDateFormat constructor-arg valueyyyy-MM-dd/ /bean这里constructor-arg就是给构造器传参。Spring 拿到“yyyy-MM-dd”这个字符串会先尝试做类型转换匹配到SimpleDateFormat(String)这个构造器然后创建对象。最终ctx.getBean(dateFormatter)拿到的就是一个能直接用的、pattern 为yyyy-MM-dd的 SimpleDateFormat。如果构造器有多个参数就写多个constructor-arg顺序必须和构造器参数顺序一致。Spring 还支持用index、name属性显式指定参数位置或参数名这在构造器重载较多时特别有用能避免参数匹配歧义。2.3 构造器实例化的两个坑第一个坑类没有无参构造器而你又没给 constructor-arg。Spring 启动时不会等你自己想起来它会直接抛BeanCreationException错误信息里会写明No default constructor found。所以看到这个异常第一反应不是去改代码而是去检查这个 bean 定义有没有把构造参数补全。第二个坑构造器参数类型不匹配。比如构造器是SimpleDateFormat(int)你给了一个“abc”Spring 在类型转换阶段就会失败。Spring 对 String 转 int、转 boolean 这些基础类型转得很顺畅但转成自定义对象就不行了必须用ref引用另一个 bean。提示constructor-arg的 value 是给“基本类型或字符串”参数用的ref是给“另一个 bean”参数用的。记不清的时候宁愿多写一点也别让容器替你猜。3. 静态工厂实例化处理“不能直接 new”的类3.1 factory-method 的工作原理有些类你就是没法 new比如抽象类java.util.Calendar、构造器私有的单例类又或者类设计者约定“只能用静态方法获取实例”。这时候就要用静态工厂方式。XML 里写法是class指定工厂类factory-method指定静态方法名。bean idcalendar classjava.util.Calendar factory-methodgetInstance/Spring 看到factory-method不会再尝试调用构造器而是执行Calendar.getInstance()这个静态方法把方法返回值作为 bean 放进容器。本质上就是反射调用Calendar.class.getMethod(getInstance).invoke(null)。这样做有什么意义最直接的意义是你写配置的人不需要关心那个类能不能 new你只关心怎么拿到它的合法实例。Calendar是抽象类你不可能new Calendar()但getInstance()能给你一个基于默认时区的GregorianCalendar实例这就够了。测试一下Calendar calendar context.getBean(calendar, Calendar.class); System.out.println(calendar.getTime());容器里这个 bean 的类型是Calendar实际运行时才确定真实子类。Spring 对这类“声明类型和运行时类型不一致”的情况是接受的因为容器只管往你手里塞东西具体是什么类型要看工厂方法的返回值。3.2 带参静态工厂方法与参数的坑静态工厂方法也可以是带参数的。比如java.text.DateFormat有个静态方法getDateInstance(int style)它根据 style 返回不同格式的 DateFormat。配置写法是这样的bean idshortDateFormat classjava.text.DateFormat factory-methodgetDateInstance constructor-arg value2/ /bean注意这里constructor-arg不是给构造器传参而是给静态工厂方法传参。Spring 看到 factory-method 后面跟了 constructor-arg会把它解析为工厂方法的实参列表。value 是 2对应DateFormat.MEDIUM最终得到的是一个中等精度的日期格式化器。这个环节我踩过一个很隐蔽的坑如果 factory-method 的方法名在类里存在多个重载版本Spring 必须靠参数个数和类型去选择调用哪一个。一旦你传的参数类型和所有重载版本都对不上容器会抛NoSuchMethodException。比如getDateInstance(int)需要 int你却在 value 里传了一个无法转为 int 的字符串报错时只提示方法找不到不会告诉你参数类型不匹配。排查的时候一定要把工厂方法的签名和 constructor-arg 逐个对上。3.3 静态工厂 vs 构造器的选择思路选构造器还是静态工厂不是凭喜好而是看类的设计。日常判断就三步看看类有没有可访问的构造器有就直接用构造器方式。构造器是 private 的、类本身是抽象类、或者类注释里明确写了“请用 getInstance 获取”必须走静态工厂。如果创建对象还需要读配置文件、准备环境、初始化缓存通常静态工厂也不够用了得往下看实例工厂。注意静态工厂方法本身必须是 public static 的。如果类里有个同名的实例方法Spring 是区分得清的因为factory-method属性专门走静态方法这条逻辑不会乱调用。4. 实例工厂实例化先有工厂 bean再有产品 bean4.1 什么时候必须用实例工厂静态工厂有个先天限制方法必须写在类自身上而且不能使用实例状态。现实场景里工厂往往是有状态的——它内部维护了一个配置项、一个缓存、一个连接池这些状态必须在创建产品之前先初始化好。这时候就要用实例工厂。实例工厂在 XML 里的关键属性是factory-bean和factory-method。factory-bean指向一个已经定义好的工厂 beanfactory-method是工厂 bean 上的实例方法名。我写个例子工厂自己定义但生产出来的产品是 JDK 自带的 SimpleDateFormatpackage com.example.factory; import java.text.SimpleDateFormat; public class MyFormatterFactory { private String pattern yyyy-MM-dd HH:mm:ss; public void setPattern(String pattern) { this.pattern pattern; } public SimpleDateFormat createFormatter() { return new SimpleDateFormat(pattern); } }然后在 beans.xml 里配置两个 bean。第一个是工厂 bean 本身它通过构造器实例化 属性注入完成初始化第二个是产品 bean它不写 class只写 factory-bean 和 factory-methodbean idformatterFactory classcom.example.factory.MyFormatterFactory property namepattern valueyyyy/MM/dd/ /bean bean idformatter factory-beanformatterFactory factory-methodcreateFormatter/容器处理formatter这个 bean 时会先确保formatterFactory已经被创建好再调用它的createFormatter()实例方法把返回的 SimpleDateFormat 当作 product bean。测试SimpleDateFormat formatter context.getBean(formatter, SimpleDateFormat.class); System.out.println(formatter.format(new Date()));输出应该是“2026/05/12”这种斜杠格式说明工厂里的pattern属性值通过 setter 注入生效了。这个例子把“实例工厂”和“属性注入”串在一起非常直观。4.2 factory-bean 的注意事项实例工厂看着简单实际使用里有几个坑要记牢。第一factory-bean 引用的必须是一个已经存在于容器中的 bean id。写错 id 会得到NoSuchBeanDefinitionException而且报错信息只提示找不到formatterFactory不会提示你写错了。这种错排查起来很费时间所以命名规范一定要统一后缀统一用Factory。第二product bean 的定义里不要写 class 属性写了反而容易误导自己。Spring 更关心的是工厂方法返回什么而不是你写了什么 class。第三工厂方法的调用时机取决于产品 bean 的 scope。如果产品是 singleton容器只在启动时调用一次工厂方法后面拿到的都是同一个实例如果产品配了scopeprototype每次 getBean 都会重新调用一次工厂方法。这个细节一旦理解就能解释为什么有些“每次调用都该是新对象”的工厂产品会出现“看起来像是同一个对象”的诡异问题。4.3 实例工厂和静态工厂的本质区别一句话总结静态工厂是“类直接生”实例工厂是“个体生”。前者不需要先创建任何对象后者必须有一个存活在容器里的工厂对象。前者适合无状态的工具类后者适合有初始化逻辑、有配置状态的生产线。从这个角度回头看你会发现 Spring 把这两种方式都封装在 XML 的“两个关键词”里了但背后一个是Method.invoke(静态方法)一个是先getBean(工厂)再Method.invoke(实例方法)。把这两条路径在脑子里串起来后面看任何框架源码里对 bean 工厂的抽象就不会晕了。5. FactoryBean 与 Bean容器视角的另类实例化5.1 FactoryBean把自己伪装成产品的 beanSpring 里还有一种特殊的接口叫FactoryBeanT。凡是实现它的 bean容器会先创建这个“工厂 bean”本身然后调用它的getObject()方法把返回值当作真正暴露给外界的 bean。我用它包装一个LocalDate的生产过程package com.example.factory; import org.springframework.beans.factory.FactoryBean; import java.time.LocalDate; public class LocalDateFactoryBean implements FactoryBeanLocalDate { Override public LocalDate getObject() { return LocalDate.now(); } Override public Class? getObjectType() { return LocalDate.class; } Override public boolean isSingleton() { return true; } }XML 里的配置非常简单bean idtoday classcom.example.factory.LocalDateFactoryBean/注意这里 class 写的是LocalDateFactoryBean但你在外面getBean(today)拿到的不是 LocalDateFactoryBean而是LocalDateLocalDate today context.getBean(today, LocalDate.class); System.out.println(today);这就是 FactoryBean 的“伪装”能力定义的是工厂暴露的是产品。老项目里做 AOP 时常见的ProxyFactoryBean就是这个机制的经典应用——你配一个 ProxyFactoryBeangetBean 拿到的却是代理对象。5.2 用 前缀把 FactoryBean 本身拿出来有时候你就是想拿到 FactoryBean 本体比如想确认它的配置或者直接调它的方法。Spring 防住了这一点在 bean 名字前面加一个前缀。LocalDateFactoryBean localDateFactoryBean context.getBean(today, LocalDateFactoryBean.class);这个是 Spring 的保留约定不是什么正则语法。很多人第一次看到别人代码里写xxx会一脸懵其实就是“我要工厂本体不要产品”的意思。提示FactoryBean和factory-bean一字之差含义完全不同。前者是一个接口实现类的实例本身就是“带产品的工厂”后者是 XML 里的一个属性用来配置“实例工厂方式”两者不要混淆。5.3 Bean 注解与工厂方法的关系到了 Spring Boot 时代大多数人不会再写 XML 了而是用 Configuration 类里的 Bean 方法。很多人没意识到Bean 方法的本质就是实例工厂方法配置类本身是工厂 beanBean 方法是工厂方法返回值就是产品 bean。我把上面三种方式用 Java Config 重写一遍package com.example.config; import com.example.factory.LocalDateFactoryBean; import com.example.factory.MyFormatterFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.text.SimpleDateFormat; import java.util.Calendar; import java.time.LocalDate; Configuration public class InstantiationConfig { Bean public SimpleDateFormat dateFormatter() { return new SimpleDateFormat(yyyy-MM-dd); } Bean public Calendar calendar() { return Calendar.getInstance(); } Bean public MyFormatterFactory formatterFactory() { MyFormatterFactory factory new MyFormatterFactory(); factory.setPattern(yyyy/MM/dd); return factory; } Bean public SimpleDateFormat formatter(MyFormatterFactory factory) { return factory.createFormatter(); } Bean public LocalDate today() throws Exception { return new LocalDateFactoryBean().getObject(); } }这里最值得讲的是formatter(MyFormatterFactory factory)这个方法。Spring 看到 Bean 方法有参数会自动从容器里按类型找到formatterFactory这个 bean 传进来效果等同于 XML 里的factory-bean但写法轻盈得多。这就是为什么很多人说 Java Config 比 XML 更好维护——依赖关系变成方法参数编译器能帮你检查。如果项目里还保留了老的 XML 配置可以用ImportResource(classpath:beans.xml)导入让 XML 和 Java Config 里的 bean 共存。理解 XML 这些配置方式读老代码和框架源码时依然有用不是学了 Spring Boot 就可以完全丢掉的。6. 常见问题与排查实录6.1 报错速查表把这次实验过程中最常踩的错整理成一张表以后照着查就行报错信息典型原因处理办法ClassNotFoundExceptionclass 写成了简单类名改成全限定名比如java.util.ArrayListNo default constructor found或NoSuchMethodException类没有无参构造器构造器实例化缺参数补constructor-arg或换静态工厂/实例工厂方式NoSuchBeanDefinitionException: No bean named xxxbean id 写错或 factory-bean 引用不存在检查 id 拼写确认工厂 bean 已定义BeanCreationException附带Factory method ... threw exception工厂方法内部抛异常先单独调用工厂方法确认业务逻辑是否正常拿到的 bean 类型和预期不一致工厂方法返回类型与声明类型有偏差检查工厂方法返回值类型必要时强转或用具体类型 getBean多次 getBean 却拿到同一个对象产品是 singleton 且工厂方法不返回新实例确认是否需要scopeprototype并检查 isSingleton 返回值6.2 本次实验踩过的三个经典坑第一个坑是静态工厂方法的参数类型匹配。我一开始给DateFormat.getDateInstance传的是valueMEDIUMSpring 没法把 “MEDIUM” 转成 int一直报NoSuchMethodException。后来才意识到 value 得写数字 2因为 Spring 对枚举和 int 常量字符串不会自动映射。建议各位传参之前先确认方法签名别想当然。第二个坑是instance factory 的工厂 bean 初始化顺序。我有一次在工厂 bean 的构造器里做一堆初始化逻辑结果那个构造器依赖一个还没注入的属性导致产品 bean 创建失败。排查下来发现Spring 创建工厂 bean 时先调构造器再注入 property如果初始化逻辑放在构造器里就等于在属性注入之前干活必然出错。正确的做法是把初始化逻辑放到InitializingBean接口的afterPropertiesSet()方法或者 XML 里的init-method上保证属性已经注入完成。第三个坑是FactoryBean 的名字带不带 。我一开始getBean(today, LocalDateFactoryBean.class)报错说类型不匹配我愣了好一会儿才想起要写today。这个知识点文档里其实有但不出一次错真的记不住。6.3 一个亲测好用的实验习惯做这个实验时强烈建议把 beans.xml 里的 bean 定义逐步添加每加一个就立刻跑一次测试而不是一口气写完再调试。我通常写一个简单的 main 方法用context.getBeanDefinitionNames()把容器里当前所有 bean 名打印出来for (String name : context.getBeanDefinitionNames()) { System.out.println(name); }这样每一步都能看到容器里到底注册了什么、漏了什么。顺序就是先跑通构造器再加静态工厂再加实例工厂最后上 FactoryBean。每轮只引入一个新知识点出错了也容易定位。Maven 依赖方面我建议用spring-context5.3.x 系列JDK 8 以上都能跑实验兼容性最好。如果你用的是 Spring 6 以上需要 JDK 17类路径和 API 没有大变化但报错信息会有细微差异排查时注意区分版本。我个人做过很多次这种小实验最深的体会是实例化方式不是考试知识点而是项目里真正天天会遇到的选择题。接手一个老系统看到 XML 里的 factory-bean 配置不再发懵自己搭工程知道想让容器管理一个不听话的第三方类时该走哪条路。建议你把文中的三个 JDK 类例子自己敲一遍改参数、故意写错、看报错整套流程走完Spring bean 实例化这件事就算真正长在脑子里了。
延伸阅读

更多相关文章

2026/10/3 23:26:00

Python新闻推荐系统源码:爬虫+用户画像+MySQL+Flask闭环实现

简介:本资源是一套基于Python实现的个性化新闻推荐平台完整源码,面向高校计算机专业学生、推荐系统初学者及Web开发实践者,解决信息过载场景下用户兴趣建模与精准内容推送的核心问题。压缩包共23个文件,含9个Python源码&#xff0…

2026/10/3 23:26:00

中文创作者专用ComfyUI工作流操作系统

1. 项目本质与真实价值定位:这不是一个“安装包”,而是一套面向中文创作者的AI图像生成工作流操作系统你点开这个标题,第一反应可能是:“又一个一键安装包?”——错了。这根本不是传统意义上的“软件安装程序”&#x…

2026/10/3 23:26:00

综合能源系统优化调度:MPC与需求响应的建模与代码实现

简介:围绕纽约市一栋带有被动与主动蓄热办公楼的综合能源优化调度问题,这套MATLAB仿真代码以模型预测控制(MPC)为核心,实现蓄热需求响应,将三级需求费与日前电价纳入随机最优控制框架,并兼顾室内…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/3 23:56:01

从零搭建AI工程:从模型接入到Agent编排的完整实践指南

1. 项目概述:当你说“从零开始做AI工程”的时候,到底在说什么“ai-engineering-from-scratch”这个标题,我第一眼看到的时候其实挺感慨的。市面上讲“从零开始学AI”的文章多到泛滥,但绝大多数要么是教你怎么装个库跑个demo&#…

2026/10/3 23:56:01

Carsim与Simulink联合仿真的车辆换道轨迹规划与跟踪

提起自动驾驶、智能网联汽车方向的课题,只要是涉及车辆运动控制的,几乎绕不开 Carsim 和 MATLAB/Simulink 这对黄金搭档。我之前做过一套基于 Carsim 与 Simulink 联合仿真的车辆换道轨迹规划与轨迹跟踪模型,跑了两个月,踩了不少坑…

2026/10/3 23:56:01

鸿业市政道路软件避坑指南:版本匹配、横断面与土方计算常见问题

简介:针对鸿业市政道路软件用户的常见问题解答文档,内容覆盖软件运行、土方、平面、纵断、横断、交叉口设计及其他模块,面向市政道路设计人员与相关专业学生,帮助解决菜单加载失败、土方计算异常、图面显示错乱等高频问题。压缩包…

2026/10/3 23:56:01

超级多智能体架构实战:DeepAgents编排、MCP工具接入与A2A通信

1. 从单体到集群:为什么我们需要超级多智能体1.1 一个真实的需求场景去年下半年我接手了一个企业内部知识助手的项目,需求听起来不复杂:帮员工查制度文档、走审批流程、生成周报。一开始我用的是单体 Agent 方案,一个模型加一堆工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑