


专栏:JavaEE 进阶跃迁营
个人主页:手握风云
目录
[一、Spring 是什么](#一、Spring 是什么)
[1.1. 容器](#1.1. 容器)
[1.2. IoC](#1.2. IoC)
[二、IoC 介绍](#二、IoC 介绍)
[2.1. 传统程序开发](#2.1. 传统程序开发)
[2.2. 解决方案](#2.2. 解决方案)
[2.3. IoC 程序开发](#2.3. IoC 程序开发)
[2.4. IoC 优势](#2.4. IoC 优势)
[三、DI 介绍](#三、DI 介绍)
[四、Bean 的存储](#四、Bean 的存储)
[4.1. @Controller(存储器存储)](#4.1. @Controller(存储器存储))
[4.2. @Service(服务存储)](#4.2. @Service(服务存储))
[4.3. @Component(组件存储)](#4.3. @Component(组件存储))
[4.4. @Repository(仓库存储)](#4.4. @Repository(仓库存储))
[4.5. @Configuration(配置存储)](#4.5. @Configuration(配置存储))
[4.6. 为什么需要多类注解](#4.6. 为什么需要多类注解)
一、Spring 是什么
Spring 框架就像一个帮你打理代码里 "工具" 的智能收纳盒,核心作用是让写程序变得更简单。平时写代码时,你要用到的各种 "功能模块"(比如处理数据的组件、响应网页请求的组件),本来得自己一个个 "造出来"(比如手动创建对象),还得费劲琢磨这些模块之间怎么配合 ------ 比如要造辆车,得自己先做轮子、再做底盘、最后拼车身,少一步都不行,改个轮子尺寸还得重新调整底盘和车身。但有了 Spring,你不用自己 "造模块" 了:只要告诉 Spring 哪些模块需要它管,它就会把这些模块存进 "收纳盒" 里。等你要用某个模块时,也不用自己去找,Spring 会主动把你需要的模块 "递到手上"(这就是依赖注入)。要是某个模块要换版本、改功能,你也不用动其他代码,Spring 会帮你把新模块换好,不用你从头调整所有关联的部分。
1.1. 容器
"容器"泛指可容纳物品的工具,在IT领域特指一种轻量级、可移植的软件封装技术,将应用及其所有依赖(代码、库、配置文件等)打包,实现跨环境一致运行。
1.2. IoC
IoC 全称是控制反转,是 Spring 框架的核心思想,本质是把对象创建和管理的 "控制权" 从开发者编写的业务代码中,转移到了 Spring 容器手里。以前开发时,要用到某个对象(比如造汽车需要的轮子、底盘),得自己用 new 关键字手动创建,一旦底层对象有变化(比如轮子尺寸改了),所有依赖它的上层代码(底盘、车身、汽车)都得跟着改,代码耦合度很高。而有了 IoC,我们不用自己创建对象了,只需通过注解(比如 @Controller、@Service)告诉 Spring 哪些对象要交给它管理,Spring 启动时会自动创建这些对象并保存;等程序需要用某个对象时,也不用自己找,直接从 Spring 容器里获取(比如用 @Autowired 注入),哪怕底层对象变了,上层代码也不用修改,这样就解耦了代码,让程序更易维护。
以下是 Spring IoC 的官方文档:

二、IoC 介绍
如果我们现在有这么个需求,造一辆车。
2.1. 传统程序开发
按照传统程序开发的流程,我们想造一辆车,需要先造出车身,而车身需要依赖底盘,底盘又需要依赖轮子。

java
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
java
public class Car {
private FrameWork frameWork;
public Car() {
this.frameWork = new FrameWork();
System.out.println("Car init......");
}
public void run() {
System.out.println("Car run......");
}
}
java
public class FrameWork {
private Bottom bottom;
public FrameWork() {
this.bottom = new Bottom();
System.out.println("FrameWork init......");
}
}
java
public class Bottom {
public Tire tire;
public Bottom() {
this.tire = new Tire();
System.out.println("Tire init......");
}
}
java
public class Tire {
private int size = 20;
public Tire() {
System.out.println("The size of tire: " + size);
}
}
如果我们想给轮胎构造方法添加一个 size 参数,那么就需要把前面几个类里面的对象也需要修改,这就是"高耦合"。
2.2. 解决方案
在传统程序开发中,汽车(Car)、车身(Framework)、底盘(Bottom)、轮胎(Tire)存在严格的上层依赖下层关系,一旦最底层的轮胎(如尺寸)发生变化,整个调用链上的所有类都需同步修改,耦合度极高。针对这一问题,1.2.3节提出的解决方案核心是**倒置依赖关系并通过"注入传递"替代"内部创建"以实现解耦**。具体而言,首先改变依赖逻辑,从传统的"根据轮胎设计底盘、根据底盘设计车身、根据车身设计汽车",转变为"先确定汽车整体需求,再依次推导车身、底盘、轮胎的设计要求",使依赖关系倒置为"轮胎依赖底盘、底盘依赖车身、车身依赖汽车";其次,摒弃每个类自行创建下级依赖对象的方式,改为在类的外部创建下级依赖对象后,通过传递(注入)的方式将其提供给当前类(例如,汽车类不再内部创建车身对象,而是通过构造函数接收外部已创建好的车身对象)。这种方式下,即使下级依赖类的创建逻辑发生变化(如参数增减),当前类也无需修改任何代码,彻底切断了类与类之间的强耦合关联,最终实现程序的灵活适配与易维护性提升。

java
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(20);
Bottom bottom = new Bottom(tire);
FrameWork frameWork = new FrameWork(bottom);
Car car = new Car(frameWork);
car.run();
}
}
java
public class Tire {
private int size = 20;
public Tire(int size) {
this.size = size;
System.out.println("The size of tire: " + size);
}
}
java
public class Bottom {
public Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("Tire init......");
}
}
java
public class FrameWork {
private Bottom bottom;
public FrameWork(Bottom bottom) {
this.bottom = bottom;
System.out.println("FrameWork init......");
}
}
java
public class Car {
private FrameWork frameWork;
public Car(FrameWork frameWork) {
this.frameWork = frameWork;
System.out.println("Car init......");
}
public void run() {
System.out.println("Car run......");
}
}
2.3. IoC 程序开发
在传统程序开发中,对象之间的依赖关系由开发者主动通过 new 关键字创建,例如上面"造汽车" 的案例:Car 需要自行创建 FrameWork,FrameWork 再创建 Bottom,Bottom 又创建 Tire,形成 "上层类控制下层类" 的依赖链。这种模式下,若底层类(如Tire的尺寸需要修改)发生变化,整个依赖链上的所有类(Bottom、FrameWork、Car)都需同步修改,导致代码耦合度极高,维护成本高。
而 IoC 程序开发彻底反转了这种控制权:不再由使用方类创建依赖对象,而是由 IoC 容器统一负责所有对象(即 Spring 中的 "Bean")的创建、初始化与生命周期管理。当应用程序中的某个类(如 Car)需要依赖其他对象(如 FrameWork)时,无需自行创建,只需通过依赖注入的方式(如构造函数注入、属性注入、Setter 注入),由 IoC 容器将预先创建好的依赖对象 "注入" 到当前类中。例如文档中改造后的造车案例,IoC 容器先创建 Tire 对象,再将其注入 Bottom,Bottom 注入 FrameWork,FrameWork 最终注入 Car,此时即便 Tire 的实现逻辑修改,Bottom、FrameWork、Car 等上层类无需任何调整,彻底解决了传统开发的高耦合问题。

2.4. IoC 优势
IoC 实现了资源的集中化管理,减少重复开发与配置成本。IoC 容器作为第三方管理者,统一负责对象(Bean)的创建、初始化、生命周期管控与配置,开发者无需在代码中重复编写 new 对象的逻辑,也无需关注对象创建的细节。
并且,IoC 提升了代码的灵活性与通用性,适配更多开发场景。一方面,依赖关系的 "倒置" 让程序设计更符合 "开闭原则"------ 新增功能时,只需在容器中新增 Bean 配置,现有业务逻辑无需修改,即可通过容器注入新的依赖实例;另一方面,IoC 的实现不依赖特定框架,例如构造方法注入基于 JDK 原生规范,即便更换开发框架,核心的依赖注入逻辑仍可复用,降低了技术选型的约束成本。
三、DI 介绍
Spring DI(Dependency Injection,依赖注入)是 Spring 框架核心思想 IoC(控制反转)的具体实现,其核心是容器在运行期间动态为应用程序提供运行时所依赖的资源(通常是对象),从而实现对象之间的解耦,让开发者无需手动创建依赖对象,专注于业务逻辑开发。
IoC 是一种设计思想,强调 "对象的控制权由开发者转移到 Spring 容器",DI 是 IoC 思想的落地实现,从 "应用程序视角" 描述 ------ 当应用程序需要某个依赖对象时,容器主动将该对象 "注入" 到应用程序中,而非应用程序自行创建。
四、Bean 的存储
共有两类注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法注解:@Bean
4.1. @Controller(存储器存储)
java
package com.yang.test1_22_1.controller;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void hello() {
System.out.println("hello userController");
}
}
java
package com.yang.test1_22_1;
import com.yang.test1_22_1.controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1221Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1221Application.class, args);
// 从上下文中获取 UserController 的 Bean 实例
UserController bean = context.getBean(UserController.class);
bean.hello();
}
}
这里的 ApplicationContext(应用上下文)是 Spring 框架的核心接口之一,属于 IoC(控制反转)容器的高级形态。这个"应用上下文"可以理解为当程序正在运行时,"围绕着程序转的所有东西"------ 包括程序里的各种 "工具"(Bean)、工具之间的搭配规则、程序需要的配置等。

除了上述方式,还有其他的方式可以获取 Bean 实例。
|-------------------------------------------------|--------------------------------|---------|
| 方法 | 描述 | 返回值 |
| getBean(Class<T> requiredType) | 如果存在,返回唯一匹配给定对象类型的bean实例。 | <T> T |
| getBean(String name) | 返回指定bean的实例,该实例可以是共享的,也可以是独立的。 | Object |
| getBean(String name, @Nullable object ... args) | 返回指定bean的实例,该实例可以是共享的,也可以是独立的。 | Object |
java
package com.example.test1_25_1;
import com.example.test1_25_1.user.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1251Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1251Application.class, args);
// 从上下文中获取 UserController 的 Bean 实例
UserController bean = context.getBean(UserController.class);
bean.setName("Yang");
bean.hello();
UserController userController1 = (UserController) context.getBean("userController");
userController1.hello();
UserController userController2 = context.getBean("userController", UserController.class);
userController2.hello();
System.out.println(bean);
System.out.println(userController1);
System.out.println(userController2);
}
}

我们会发现3个对象的内存地址的16进制的结果是一样的,这个其实也是一种实现单例模式的思想。
Spring Bean 命名核心规则:每个 Bean 拥有一个或多个标识符(主名 + 别名),所有标识符在托管该 Bean 的 IoC 容器中必须唯一;通常一个 Bean 仅需一个主标识符,多个标识符时额外的视为别名。
通用命名约定:遵循Java 实例字段的驼峰命名法 ,首字母小写,后续单词首字母大写,如accountManager、userDao、loginController;统一命名可提升配置可读性,也便于 Spring AOP 按名称批量匹配 Bean。
自动命名规则:类路径组件扫描时,容器会为未显式命名 的组件自动生成 Bean 名,规则为:取类的简单类名 ,并按 java.beans.Introspector.decapitalize 规则转换:常规情况下,将简单类名首字母转为小写;特殊情况下,若类名前两个字符均为大写,则保留原始大小写。

java
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
char[] chars = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
如果我们把上面传入的参数改为"UserController",再次运行程序,就会出现"No bean named 'UserController' available"的异常,这就是因为Spring 容器在注册 Bean 时,默认会使用类名首字母小写作为 Bean 的名称。

还有一种错误,如果我们忘加了 @Controller 注解,就会出现"No qualifying bean of type 'com.example.test1_25_1.user.UserController' available"的异常,这就是没有将 UserController 实例化作为 Bean 管理起来。

4.2. @Service(服务存储)
java
package com.yang.test1_25_2.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void hello() {
System.out.println("Hello UserService...");
}
}
java
package com.yang.test1_25_2;
import com.yang.test1_25_2.service.UserService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1252Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1252Application.class, args);
UserService bean = context.getBean(UserService.class);
bean.hello();
}
}

4.3. @Component(组件存储)
java
package com.yang.test1_25_3.component;
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void hello() {
System.out.println("Hello UserComponent...");
}
}
java
package com.yang.test1_25_3;
import com.yang.test1_25_3.component.UserComponent;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1253Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1253Application.class, args);
UserComponent bean = context.getBean(UserComponent.class);
bean.hello();
}
}

4.4. @Repository(仓库存储)
java
package com.yang.test1_25_4.reposity;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void hello() {
System.out.println("Hello UserRepository");
}
}
java
package com.yang.test1_25_4;
import com.yang.test1_25_4.reposity.UserRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1254Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1254Application.class, args);
UserRepository bean = context.getBean(UserRepository.class);
bean.hello();
}
}

4.5. @Configuration(配置存储)
java
package com.yang.test1_25_5.configuration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
public void hello() {
System.out.println("Hello UserConfiguration...");
}
}
java
package com.yang.test1_25_5;
import com.yang.test1_25_5.configuration.UserConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Test1255Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Test1255Application.class, args);
UserConfiguration bean = context.getBean(UserConfiguration.class);
bean.hello();
}
}

4.6. 为什么需要多类注解
这个也是和咱们前面讲的应用分层是呼应的。让程序员看到类注解之后,就能直接了解当前类的用途。
- @Controller:控制层,接收请求,对请求进行处理,并进行响应;
- @Service:业务逻辑层,处理具体的业务逻辑;
- @Repository:数据访问层,也称为持久层。负责数据访问操作;
- @Configuration:配置层,处理项目中的一些配置信息;
- @Component:组件。
这就像车牌号一样,车牌号是唯一的,用于标识一辆车。不同的省之间,车牌号的第一位的省份缩写不一样,紧跟的第二位字母也会因各省的地区不同。这样既可以节约号码,又可以直接了解这辆车的所属地。
同样地,在一些企业当中,分为表现层、业务逻辑层、数据层。同样可以根据不同的注解,提高源码的可读性,又能精准定位问题所在。

我们来查看下类注解直接的关系:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;
/** @deprecated */
@Deprecated(
since = "7.0"
)
boolean enforceUniqueMethods() default true;
}
我们会发现,除了 Compnent 注解,其余的4个注解都依托于 Compnent,是 @Compnent 的一个元注解,这些注解也被称为 @Compnent 的衍生注解。