每次面试都被问,说说你对Spring IoC 和 DI的理解
没有 Spring 的时候,我们怎么写代码
先回忆一下"远古"时代的 Java 开发。
假如你有个UserService,里面要调UserDao去查数据库:
publicclassUserService{privateUserDaouserDao=newUserDao();// 自己 newpublicUsergetUserById(intid){returnuserDao.findById(id);}}看起来没毛病对吧?
问题在哪呢——UserService跟UserDao死死绑在一起了。哪天你要换一个UserDao的实现(比如原本是 MySQL 版,现在要换成带缓存的),你就得去改UserService的代码。
一个两个还好,一个项目几百上千个类这么互相new,那耦合度简直就是一坨缠绕的耳机线,牵一发而动全身。
这就是Spring IoC 要解决的问题。
IoC 到底是什么
IoC,全称 Inversion of Control,控制反转。
名字很唬人,其实就一句话:创建对象的控制权不要攥在自己手里,交给容器去管。
没有 IoC 之前:你自己new,你说了算。
有了 IoC 之后:你告诉 Spring “我需要什么”,Spring 帮你造好,你要用的时候直接从容器里拿。
翻译成人话:以前你吃饭得自己去买菜洗菜切菜炒菜,现在你只要把菜单丢给食堂,等着端盘子就行。
代码就变成了这样:
publicclassUserService{@AutowiredprivateUserDaouserDao;// 不用 new,Spring 帮你注入publicUsergetUserById(intid){returnuserDao.findById(id);}}UserService只管用,不管建。谁建的?Spring 建的。怎么进来的?依赖注入进来的。
DI 依赖注入:IoC 的具体实现
IoC 是一种思想,DI(Dependency Injection)是这个思想的具体实现方式。
Spring 怎么知道UserService依赖了UserDao?你告诉它的——通过注解或者配置文件。然后 Spring 在创建UserService的时候,自动把UserDao注入进去。
Spring 支持三种注入方式:
1. 字段注入(最常用)
@AutowiredprivateUserDaouserDao;最简洁,写起来方便,所以大家最爱用。贴个注解就完事了。
底层原理不复杂:Spring 通过反射拿到这个字段的Field对象,然后调用field.set(bean, 依赖对象),直接给属性赋值。
2. Setter 注入
@AutowiredpublicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}底层是通过反射获取 setter 方法的Method对象,然后method.invoke(bean, 依赖对象)调用 setter 完成注入。
3. 构造器注入
@AutowiredpublicUserService(UserDaouserDao){this.userDao=userDao;}底层是拿到有参构造器的Constructor对象,然后constructor.newInstance(依赖1, 依赖2...),创建 Bean 的同时就把依赖注进去了。
构造器注入目前是 Spring 官方推荐的方式。好处显而易见:依赖在对象创建时就强制到位,不会出现空指针,而且对单元测试友好——你直接new UserService(mockDao)就能测,不需要搞什么反射注入。
Bean 到底是怎么造出来的:反射是核心
不管哪种注入方式,底层都逃不开一个东西——反射。
Spring 启动时的大概流程是这样的:
扫描:扫包,找到所有带
@Component、@Service、@Repository、@Controller之类注解的类,拿到全类名。反射创建实例:
Class.forName(全类名)加载类,然后通过反射创建对象。检查生命周期注解:如果方法上标了
@PostConstruct,创建完 Bean 后执行;标了@PreDestroy,销毁 Bean 前执行。依赖注入:检查字段、setter 方法、构造器上的
@Autowired或@Resource注解,把依赖的 Bean 注入进去。放进容器:Bean 创建好了、依赖也注入了,放进 IoC 容器里(本质就是一个大的 Map),等着你用。
整个过程,反射从头忙到尾。
工厂模式:IoC 容器的外壳
从设计模式的角度看,IoC 容器就是一个超级工厂。
普通的工厂模式,一个工厂负责创建一类对象。Spring 的 IoC 容器负责创建、组装、管理应用中所有 Bean 的生命周期。
你要用的时候,不需要知道这个 Bean 是怎么来的:
ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");UserServiceuserService=context.getBean(UserService.class);或者现在更常见的,直接@Autowired注入:
@AutowiredprivateUserServiceuserService;这就是工厂模式的威力——使用者不关心创建细节,只管拿成品。
总结:串起来想一想
整个 Spring IoC 的运转逻辑就是一条线:
- 你通过注解或 XML告诉 Spring 哪些类是 Bean、Bean 之间谁依赖谁
- Spring 启动时用反射扫描你的配置,动态创建 Bean 实例
- 根据依赖关系,用反射把被依赖的 Bean 注入进去(setter / 构造器 / 字段三种方式)
- 处理
@PostConstruct、@PreDestroy等生命周期回调 - 把创建好的 Bean 放进IoC 容器(工厂模式的体现)
- 你需要用的时候,从容器里取就行了
理解了这一套,Spring Boot 自动配置、条件装配那些高级特性,本质上也是基于 IoC 容器做的扩展——万变不离其宗。
我是小饼干,如果这篇帮你理清了 IoC 和 DI,点个赞吧!。
