【Java EE】Spring IoC 与 DI

【Java EE】Spring IoC 与 DI 文章目录一、Spring、Spring MVC、Spring Boot三者关系二、IoC控制反转2.1 定义2.2 传统开发高耦合2.3 IoC解耦2.4 IoC容器两大核心能力三、DI依赖注入四、Bean如何存入Spring容器4.1 Component 与 AutowiredComponent 把类交给 Spring 管理Autowired 依赖注入整体依赖链错误日志4.2 五大类注解1. 注解层级关系2. Bean命名规则3. 容器获取Bean三种方式4.3 Bean方法注解1. 适用场景场景1第三方外部类无法加类注解场景2同一个类需要多个不同配置的 Bean2. 使用规范3. 命名规则4.4 Bean扫描范围与ComponentScan默认扫描规则ComponentScan与 SpringBootApplication 的关系手动自定义扫描路径BeanFactory 与 ApplicationContext五、依赖注入方式5.1 属性注入5.2 构造方法注入5.3 Setter方法注入六、多Bean注入冲突与解决方案多Bean注入冲突解决方案1. Primary设置默认Bean2. Qualifier Autowired指定Bean名称3. Resource按名称注入Autowired 和 Resource 区别一、Spring、Spring MVC、Spring Boot三者关系Spring底层框架本质是IoC容器提供IoC、AOP、事务、资源管理等基础能力Spring MVCSpring的子模块专门做Web接口开发提供RestController、RequestMapping等Web注解Spring BootSpring的脚手架简化Spring繁琐XML配置自动装配、内置服务器快速搭建项目。分层协作流程前端请求 → Spring MVC Controller → Spring Service业务层 → Spring Repository数据层 → 数据库其中对象管理、依赖装配全部由Spring IoC容器实现。二、IoC控制反转2.1 定义IoC全称Inversion of Control控制反转是一种设计思想传统开发控制权开发者手动new创建依赖对象上层主动创建下层代码高耦合IoC反转后控制权对象创建、销毁、依赖组装交给IoC容器Spring开发者只需要声明需要什么对象容器自动提供。2.2 传统开发高耦合需求造一辆车依赖关系Car → Framework车身 → Bottom底盘 → Tire轮胎在每个类内部new下级依赖staticclassCar{privateFrameworkframework;publicCar(){// 内部自己创建依赖强耦合frameworknewFramework();}}staticclassFramework{privateBottombottom;publicFramework(){bottomnewBottom();}}staticclassBottom{privateTiretire;publicBottom(){tirenewTire(17);}}缺陷底层类修改例如轮胎增加尺寸参数整条调用链Tire→Bottom→Framework→Car全部要修改牵一发而动全身耦合度极高。2.3 IoC解耦不再在类内部new依赖而是外部传入依赖publicclassMain{publicstaticvoidmain(String[]args){TiretirenewTire(21);BottombottomnewBottom(tire);FrameworkframeworknewFramework(bottom);CarcarnewCar(framework);car.run();}}publicclassCar{privateFrameworkframework;publicCar(Frameworkframework){this.frameworkframework;System.out.println(car init...);}publicvoidrun(){System.out.println(car run);}}publicclassFramework{Bottombottom;publicFramework(Bottombottom){this.bottombottom;System.out.println(framework init...);}}publicclassBottom{Tiretire;publicBottom(Tiretire){this.tiretire;System.out.println(bottom init...);}}publicclassTire{intsize;publicTire(intsize){this.sizesize;System.out.println(tire init,size: size);}}变化对象创建顺序反转传统Car先创建→ IoC模式Tire先创建解耦底层Tire修改上层Car、Framework代码完全不用改动统一管理所有对象由第三方容器统一创建、维护。2.4 IoC容器两大核心能力Spring就是标准IoC容器只做两件事存Bean把类交给Spring管理Spring创建对象存入容器取Bean需要使用对象时从容器获取自动装配依赖。BeanSpring容器中管理的所有对象统一称为Bean。在上面的例子中Car,Framework,Bottom,Tire对象管理就可以交给Spring来完成三、DI依赖注入DI全称Dependency Injection依赖注入是实现IoC的具体技术手段。IoC容器运行时自动将目标类需要的依赖对象动态注入到类中。IoC与DI关系区分IoC思想、目标控制权交给容器解耦DI实现方案通过注入方式完成对象装配。四、Bean如何存入Spring容器4.1 Component 与 AutowiredComponent 把类交给 Spring 管理Component是一个类级别的注解告诉 Spring“这个类由你来管理创建它的实例Bean并放进 IoC 容器中。”例如BookService和BookDao加上ComponentComponentpublicclassBookService{ComponentDatapublicclassBookDao{加上Component后Spring 在启动时就会自动创建BookService和BookDao的单例对象放在 IoC 容器中不需要手动new。注意RestController本身也包含了Component的功能所以加了RestController的类也自动被 Spring 管理。Autowired 依赖注入Autowired是一个字段/构造方法/Setter 级别的注解告诉 Spring“我需要一个该类型的对象你从 IoC 容器中帮我注入进来。”例如在需要实例的代码中Autowired 就不需要手动创建实例直接调用方法RestControllerRequestMapping(/book)publicclassBookController{AutowiredprivateBookServicebookService;RequestMapping(/getList)publicListBookInfogetList(){returnbookService.getList();}}ComponentpublicclassBookService{AutowiredBookDaobookDao;publicListBookInfogetList(){bookDao.mockData();returnbookDao.getBookList();}}BookController需要调用BookService的方法声明了private BookService bookService字段加上Autowired后Spring 在创建BookController实例时会去 IoC 容器中找到BookService类型的 Bean 并自动赋值给这个字段同理Spring 创建BookService时也会自动把BookDao注入进去这样就不需要写privateBookServicebookServicenewBookService();// ❌ 不需要手动创建整体依赖链BookController (RestController) │ Autowired ▼ BookService (Component) │ Autowired ▼ BookDao (Component)它们之间的依赖关系完全由 Spring 通过Autowired自动装配开发者只需要声明我需要什么不需要关心怎么创建。注解角色解决的问题Component注册告知 Spring 帮我创建和管理这个类的实例Autowired注入告知 Spring 把我需要的依赖从容器中自动拿给我两者配合就实现了控制反转IoC对象的创建和组装不再由自己控制而是交给 Spring 容器统一管理。错误日志假设BookDao没有Component注解直接使用Autowired实例化这个类FieldbookDao incom.daisy.springbookdemo.service.BookServicerequired a bean of type com.daisy.springbookdemo.dao.BookDao that could not be found.表示需要一个com.daisy.springbookdemo.dao.BookDao这样类型的对象但是在IoC容器中找不到因为缺少了Component假设BookDao使用了ComponentBookService中没有使用Autowired注解直接实例化这个类java.lang.NullPointerException:Cannotinvokecom.daisy.springbookdemo.dao.BookDao.mockData()becausethis.bookDaoisnull抛出空指针异常因为Autowired在Spring中没有取出BookDao也就无法进行赋值因此这个字段就是null4.2 五大类注解1. 注解层级关系Controller、Service、Repository、Configuration底层都标注了Component是Component衍生注解注解使用分层作用Controller控制层接收前端请求Web接口Service业务逻辑层处理业务逻辑Repository数据层操作数据库DAOConfiguration配置层项目配置类Component通用组件不属于以上分层的工具类这五个注解的共同作用都是把类交给Spring管理。区别Controller是控制层的注解作用是接受参数返回响应因此控制层只能使用Controller注解其余的注解不能发挥作用剩余四个注解大部分情况功能都相同但是不同注解的含义不同最好按标准使用2. Bean命名规则默认规则类名首字母小写如BookService→ bean名称bookService特殊规则类名前两个字母大写名称不变如UController→UController自定义名称在注解中加上字符串Service(book)手动指定Bean名称。3. 容器获取Bean三种方式通过ApplicationContextSpring上下文容器入口获取// 1. 根据类型获取BookServiceservicecontext.getBean(BookService.class);// 2. 根据Bean名称获取BookServiceservice(BookService)context.getBean(bookService);// 3. 名称类型双重匹配BookServiceservicecontext.getBean(bookService,BookService.class);4.3 Bean方法注解Bean是方法注解写在配置类的方法上将当前方法返回的对象注册为 Spring IoC 容器中的 Bean交给 Spring 统一管理。1. 适用场景场景1第三方外部类无法加类注解比如DataSource、Redis客户端、Mybatis工具类源码不在自己项目里不能给类加Service/Component只能用Bean创建对象交给容器。ConfigurationpublicclassDataSourceConfig{BeanpublicDataSourcedataSource(){DruidDataSourcedsnewDruidDataSource();ds.setUrl(jdbc:mysql://xxx);returnds;}}场景2同一个类需要多个不同配置的 Bean比如多数据源、多个 Redis 实例一个类要创建多个对象类注解只能生成一个BeanBean可以定义多个方法生成多个实例ComponentpublicclassStudentComponent{BeanpublicStudents1(){returnnewStudent(zhangsan,18);}BeanpublicStudents2(){returnnewStudent(lisi,18);}}2. 使用规范Bean不能单独生效必须配合Component或者其他类注解配置类使用否则Spring扫描不到这个方法无法注册Bean。推荐使用Configuration专门用于配置类Spring会做CGLIB代理优化。3. 命名规则默认名称不加name属性时Bean名称 方法名// bean名称userBeanpublicUseruser(){...}自定义名称通过name属性指定支持多个别名// bean别名u1、userOneBean({u1,userOne})//使用数组接收publicUseruser(){...}// 简写Bean(u1)publicUseruser(){...}4.4 Bean扫描范围与ComponentScan默认扫描规则默认扫描启动类所在包 所有子包。举例子启动类路径com.example.demo.SpringIocDemoApplication自动扫描范围com.example.demo下全部子包controller/service/repository/component…如果Bean类放在启动类同级包/上级包超出默认扫描范围Spring找不到直接报No qualifying bean。ComponentScanComponentScan是Spring的包扫描注解自动扫描指定包下所有标注了Component、Controller、Service、Repository、Configuration的类将这些类注册为Bean存入IoC容器。告诉Spring去哪里找带类注解的Bean。与 SpringBootApplication 的关系SpringBootApplication是复合注解源码内部已经包含ComponentScanSpringBootConfigurationEnableAutoConfigurationComponentScan// 内置了包扫描publicinterfaceSpringBootApplication{}这里ComponentScan的值就是启动类所在的目录手动自定义扫描路径当Bean不在启动类包下手动指定扫描包//手动指定包ComponentScan(com.example.demo)// 扫描多个包ComponentScan({com.example.demo,com.example.other})SpringBootApplicationpublicclassDemoApplication{}注意数组可以填多个包路径逗号分隔这只是一种解决扫描路径的方案但是很繁琐最优的方案是把启动类放在项目根包依靠默认扫描。BeanFactory 与 ApplicationContextBeanFactorySpring 底层顶层接口仅提供基础 Bean 获取能力懒加载使用时才创建 BeanApplicationContextBeanFactory 子接口企业级标准上下文容器启动时一次性加载所有单例 Bean额外支持国际化、事件发布、资源读取。五、依赖注入方式5.1 属性注入Field注入Autowired写在字段上RestControllerpublicclassBookController{// 属性注入AutowiredprivateBookServicebookService;}优点代码极简开发快缺点仅能在IoC容器中使用脱离容器直接new会空指针无法注入final常量依赖隐藏无法直观看到类依赖哪些对象。5.2 构造方法注入如果只有一个构造方法那么Spring也只能使用这个构造方法创建对象Autowired可以省略如果存在多个构造方法默认构造方法是无参版本可以使用Autowired指定默认的构造方法Spring官方推荐RestControllerpublicclassBookController{privatefinalBookServicebookService;// 类只有一个构造方法Autowired可省略publicBookController(BookServicebookService){this.bookServicebookService;}}优点支持final修饰依赖不可变实例化时依赖必须全部注入避免空指针JDK支持不依赖Spring容器单元测试可直接new传入Mock对象缺点依赖较多时构造方法参数冗长。5.3 Setter方法注入Autowired写在set方法RestControllerpublicclassBookController{privateBookServicebookService;AutowiredpublicvoidsetBookService(BookServicebookService){this.bookServicebookService;}}优点对象创建完成后重新对这个对象进行配置或注入可以动态调整缺点不能注入finalset方法可以被多次调用依赖可被多次修改存在数据风险。六、多Bean注入冲突与解决方案多Bean注入冲突当同一个类创建了多个BeanAutowired按类型查找会报错。ComponentpublicclassStudentComponent{//创建两个BeanBeanpublicStudents1(){returnnewStudent(zhangsan,18);}BeanpublicStudents2(){returnnewStudent(lisi,19);}}publicclassUserService{//Autowired按照Student类型查找AutowiredprivateStudentstu;publicvoidprint(){System.out.println(do Service!);System.out.println(stu);}}报错信息Fieldstudent incom.daisy.springiocdemo.service.UserServicerequired a single bean,but2were found:-s1:defined by methods1inclasspath resource[com/daisy/springiocdemo/component/StudentComponent.class]-s2:defined by methods2inclasspath resource[com/daisy/springiocdemo/component/StudentComponent.class]Thismay be duetomissingparameter name informationAction:Considermarking one of the beans asPrimary,updating the consumertoacceptmultiple beans,or usingQualifiertoidentifythe bean that should be consumedUserService中的student属性需要一个Bean但是找到了两个对象不知道该使用哪个对象就会报错Action中提供了两种解决方案。解决方案1. Primary设置默认Bean在其中一个Bean上添加注解Spring默认优先注入该对象ConfigurationpublicclassStudentComponent{PrimaryBeanpublicStudents1(){returnnewStudent(zhangsan,18);}BeanpublicStudents2(){returnnewStudent(lisi,19);}}2. Qualifier Autowired指定Bean名称ServicepublicclassUserService{//指定Bean名称为s1的对象Qualifier(s1)AutowiredprivateStudentstudent;publicvoidprint(){System.out.println(do Service!);System.out.println(student);}}Qualifier 也可以标注在Bean方法参数上精确指定注入哪个 Bean举个例子ComponentpublicclassStudentComponent{BeanpublicStringname1(){returnzhangsan;}BeanpublicStringname2(){returnlisi;}BeanpublicStudents1(Qualifier(name1)Stringn){returnnewStudent(n,18);}BeanpublicStudents2(){returnnewStudent(lisi,19);}}其中BeanpublicStudents1(Qualifier(name1)Stringn){returnnewStudent(n,18);}s1()方法需要注入一个String类型的参数n。但容器里有两个String类型的 beanname1→zhangsan、name2→lisi。**不加Qualifier**:NoUniqueBeanDefinitionException:找到2个String类型的Bean不知道用哪个加了Qualifier(name1)之后Spring 明确知道你要的是名字叫name1的那个 String bean即zhangsan所以s1创建出来的 Student 是Student(zhangsan, 18)而不是Student(lisi, 18)。3. Resource按名称注入Resource是Jakarta提供的注解可以直接通过name属性指定Bean名称ServicepublicclassUserService{Resource(names1)privateStudentstudent;publicvoidprint(){System.out.println(do Service!);System.out.println(student);}}对比第一中方式后两种方式更灵活不同场景下可以拿到不同的对象Autowired 和 Resource 区别对比项AutowiredResource来源Spring框架专属注解JSR-250 Java官方标准注解默认匹配规则优先按类型优先按名称找不到再匹配类型指定Bean名称必须配合Qualifier自带name属性直接指定required属性支持可设requiredfalse允许为空无该属性依赖必须存在Autowired在多个Bean匹配时如果字段名和Bean名称相同就会匹配不报错Resource按照优先但一定是在类型相同的前提下