
学 Spring 的时候依赖注入这个概念本身不算难但是注入方式有三种教程里各写各的看完之后我反而更纠结了到底该用哪个后来在项目中三种都试过一遍也踩了一些坑总算搞清楚了它们各自适合什么场景。这里记录一下。先回顾一下依赖注入是什么用一句话说就是你不用自己new对象了Spring 容器帮你把需要的东西送过来。举个例子UserService需要用到UserRepository来查数据库。传统写法是在类里面直接new一个出来但这样 UserService 就和某个具体的 Repository 实现绑死了。依赖注入的做法是你只声明我需要一个 UserRepositorySpring 容器会在运行时把合适的实现塞给你。那问题就来了Spring 是怎么把东西塞给你的这就有了三种方式。第一种构造器注入在构造函数里接收依赖。ServicepublicclassUserService{privatefinalUserRepositoryuserRepository;publicUserService(UserRepositoryuserRepository){this.userRepositoryuserRepository;}}这是 Spring 官方推荐的方式。我一开始不理解为什么推荐它觉得写起来还挺啰嗦的每个依赖都要写一遍构造函数参数。后来踩了几个坑才明白它好在哪里。第一个好处是对象在创建出来的那一刻所有必需的依赖就已经到位了。不会出现用到一半发现某个依赖是 null 的情况。这个听起来理所当然但其他方式真的不保证这一点。第二个好处是依赖字段可以声明为final。final意味着这个引用一旦赋值就不会再变在多线程环境下天然安全不用担心哪个地方偷偷把它改了。第三个好处是做单元测试的时候特别方便。脱离了 Spring 容器你直接new UserService(mockRepository)就能测试不需要启动任何 Spring 的东西。这个在写测试的时候体感很明显。还有一个小细节Spring 4.3 之后如果一个类只有一个构造函数Autowired注解可以省略。所以写起来也没那么啰嗦了。第二种Setter 注入容器先把对象创建出来然后调用 Setter 方法把依赖传进去。publicclassPaymentService{privatePaymentGatewaygateway;AutowiredpublicvoidsetGateway(PaymentGatewaygateway){this.gatewaygateway;}}这种方式的好处是灵活。对象创建之后你还可以在运行过程中重新调用 Setter 换一个依赖实现进去。但问题也很明显对象刚创建出来的时候依赖可能还没注入。如果在这期间有人调了业务方法gateway还是 null直接就空指针了。我在一个小项目里用过 Setter 注入当时觉得代码看着还行。后来有次改代码忘了配置某个 Bean 的注入跑起来之后隔了很久才报 NullPointerException排查半天才发现是 Setter 没被调到。如果是构造器注入启动的时候就会报错不用等到运行时才炸。所以 Setter 注入比较适合那种有没有都行的依赖。比如某个服务有一个可选的通知功能不配就用默认的配了就覆盖。这种场景下 Setter 注入的灵活性就有用了。第三种字段注入直接在字段上加Autowired不写构造函数也不写 Setter。ServicepublicclassOrderService{AutowiredprivateOrderRepositoryorderRepository;}代码最少一眼看过去特别干净。我刚开始学 Spring 的时候最喜欢这种写法因为省事。但用了一段时间之后问题就出来了。首先是依赖被藏起来了。你从类的构造函数和公开方法上完全看不出这个类需要什么才能运行。新接手的人看代码以为OrderService很简单拿来用才发现背后还藏着一个OrderRepository得先把这个也配好才行。其次是没法声明final。因为Autowired字段注入是 Spring 通过反射在对象创建之后才赋值的final字段不允许这样操作。最让我头疼的是写测试的时候。你想给orderRepository传一个 Mock 对象进去但它是 private 字段也没有 Setter。要么启动 Spring 测试容器要么用反射工具强行塞进去。一个简单的单元测试搞得特别复杂。后来我翻了一些开源项目的代码发现成熟的代码库几乎不用字段注入。Spring 官方也不推荐在生产代码里大量使用。所以到底该怎么选踩完坑之后我自己的规则是这样的大部分情况下用构造器注入。代码虽然多写几行但依赖关系清清楚楚启动时就能检查完所有依赖写测试也最方便。Setter 注入留给可选依赖。就是那种没有也能跑有了更好的场景。字段注入能不用就不用。它写起来确实最省事但省下来的那几行代码后面排查问题和写测试的时候都得还回去。最后放一张总结表方便以后回来翻方式代码量依赖安全final测试友好推荐场景构造器注入稍多保证可以好核心业务绝大多数场景Setter 注入中等不保证不行一般可选依赖有默认值字段注入最少不保证不行差尽量别用