当前位置: 首页 > news >正文

Spring的IoC与DI

目录

前言

一、应用分层

二、IoC

1. IoC 思想

2. Spring 项目的扫描路径

3. 五大类注解及方法注解

4. 获取对象

1. ApplicationContext 和 BeanFactory

2. Bean 的名称

3. 获取五大注解修饰的类的对象

4. 获取 @Bean 注解修饰的方法创建的对象

三、DI

1. 属性注入

2. 构造方法注入

3. Setter 方法注入

4. 三种注入方式的优缺点分析

5. @Autowired 存在的问题

6. @Autowaired 与 @Resource 的区别


前言

本文介绍了Spring框架中的核心概念与应用分层架构。首先阐述了应用分层思想,包括MVC模式和三层次架构,分析了二者的区别与联系。重点讲解了IoC(控制反转)思想及其实现方式,详细说明了Spring项目的扫描路径配置和五大类注解(@Controller、@Service等)的使用场景。文章还介绍了三种依赖注入方式(属性注入、构造方法注入和Setter注入)及其优缺点,并针对Autowired注解可能存在的问题给出了三种解决方案(@Primary、@Qualifier、@Resource)。最后对比了@Autowired和@Resource注解的区别,为Spring开发提供了全面的技术指导。


一、应用分层

应用分层是一种软件设计开发思想,将应用分成多个层次,每个层次各自负责各自的职责,多个层次协同提供完整的功能;

Spring MVC 设计模式就是应用分层的一种体现;

Spring MVC 中把整体系统分成了 Model,View,Controller 三个层次,实现了 Model,View,Controller 三部分逻辑的解耦;

当前更主流的开发方式是前后端分离,因此基于 MVC 思想,设计模式又进行了改进,把整体架构分为表现层,业务逻辑层,和数据层,也称为三层架构;

  • 表现层负责接受指令和返回结果;
  • 业务逻辑层,负责根据用户的请求计算响应;
  • 数据层负责存储用户数据或者给用户返回数据;

MVC 和三层架构的区别和联系:

MVC 是一种思想,以 Model,View,Controller 这种结构模式解决实际问题;Spring MVC 则是基于这种思想的一种软件架构;

三层架构是基于 MVC 思想做出改进的一种实现;

二者都是软件架构,目的都是实现代码的高内聚,低耦合;

三层架构区别于 MVC,分为了表现层,业务逻辑层和数据层;

二、IoC

1. IoC 思想

IoC 表示控制反转(Inverse of Control),即把 new 对象的控制权交给 Spring 进行管理,需要使用某个对象时,再从 IoC 容器中拿;

如果对象发生更改,直接更改相应对象的类即可,不需要更改其它类;

相比于在类中主动 new 对象,发生变动时,需要更改整个调用链的方式,实现了代码的解耦合,这是 Spring 的核心思想;

2. Spring 项目的扫描路径

Spring 项目通常是比较庞大的,除了开发者自己写的代码,还包括很多的依赖;

自己开发的目录,需要 Spring 进行扫描,将对象的控制权交给 Spring 管理,引入的依赖中,大部分是不需要 Spring 扫描的;少数需要扫描的,可以使用 @ComponentScan 注解配置;

@ComponentScan 注解用于配置当前要扫描的目录,可以配置多个;如果不配置,默认扫描目录就是当前目录;

@SpringBootApplication 注解表示当前修饰的类为 SpringBoot 项目启动类;

@SpringBootApplication 是一个复合注解,包含了 @ComponentScan,路径参数为空,表示扫描当前类的目录;

因此 @SpringBootAllication 修饰的类,即启动类所在的目录,就是 Spring 需要扫描的目录;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {/*** Exclude specific auto-configuration classes such that they will never be applied.* @return the classes to exclude*/@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};// ...
}

注意:需要交给 Spring 管理的对象,相应的类都应该和启动类在同一个目录;

3. 五大类注解及方法注解

项目中要把对象交给 Spring 进行管理,是通过加注解的方式实现的;

其中五大类注解有:@Controller,@Service,@Repository,@Component,@Configration;

方法注解有:@Bean

需要把对象交给 Spring 管理,就可以在类上加上五大注解其中之一,也可以在方法上加 @Bean 注解;

@Controller:用于加在表现层的类上;

@Service:用于加在业务逻辑层的类上;

@Repository:用于加在数据层的类上;

@Component:用于加在组件相关的类上;

@Configration:用韵加在配置相关的类上;

@Bean:

  • 当有某些类不在扫描的路径上(例如,外部包的类),无法添加类注解,可以通过方法将对象创建好并返回;
  • 当某个类需要多个对象,也需要使用 @Bean 注解;

五大注解之间的关系和区别:

@Controller,@Service,@Repository,@Configration 都是复合注解,包含了 @Conponent;

从功能实现的角度,@Service,@Repository,@Component ,@Configration 是一致的,可以混用;只有 @Controller 是特殊的,不能混用;

从规范上,为了方便代码管理和阅读,是不能混用的,应尽量区分好;

注解源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {@AliasFor(annotation = Component.class)String value() default "";
}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {@AliasFor(annotation = Component.class)String value() default "";
}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {@AliasFor(annotation = Component.class)String value() default "";
}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {@AliasFor(annotation = Component.class)String value() default "";boolean proxyBeanMethods() default true;boolean enforceUniqueMethods() default true;
}@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {String value() default "";
}

4. 获取对象

对象被 Spring 管理,需要具备两个条件:

  • 1. 被 Spring 扫描到;
  • 2. 配置五大注解和 @Bean 使用;

获取对象,以类型匹配为主:

  • 按照类型和名称进行匹配,类型正确,名称正确,能获取到对象;
  • 类型能匹配,名称不能匹配,如果该类型的对象只有一个,则返回该对象;
  • 如果名称能匹配,类型不能匹配,抛出异常;

1. ApplicationContext 和 BeanFactory

获取对象可以通过 IoC 容器的上下文;

上下文指的是 Spring 的运行所需要的所有内容,可以理解为 Spring 环境;

可以通过 ApplicationContext 对象的 getBean()  方法获取;

ApplicationContext 是一个接口,继承了多个接口,包括 ListableBeanFactory,ListableBeanFactory 继承了 BeanFactory,getBean() 方法就是 BeanFactory 的方法;

ApplicationContext 和 BeanFactory 的关系及区别:

关系上,ApplicationContext 继承了 BeanFactory,是父子关系,BeanFactory 的功能,ApplicationContext 都有;除此之外,ApplicationContext 还具备环境,资源管理等功能;

性能上,BeanFactory 是懒加载,ApplicationContext 是提前加载;

2. Bean 的名称

getBean() 方法:

getBean() 可以通过类型 .class,也可以通过名称,还可以通过类型和名称获取;

通过类型获取,需要将要获取对象的类型 .class 传入;

通过名称获取:

  • 通常情况 Bean 的名称是类的首字母小写的小驼峰形式;
  • 如果类的前两个字母都是大写,则 Bean 的名称就是类名;
  • 如果是使用方法返回对象,Bean 的名称就是方法名;

注意:getBean() 方法通过三种方式拿到的 Bean 是同一个对象,因此可以知道 IoC 容器管理 Bean 采用的设计模式是单例模式;

3. 获取五大注解修饰的类的对象

@Controller
public class UserController {public void sayHi(){System.out.println("hi, user controller...");}
}@Controller
public class UController {public void sayHi(){System.out.println("hi, UController...");}
}@Service
public class UserService {public void doService(){System.out.println("do service...");}
}@Repository
public class UserRepository {public void doRepository(){System.out.println("do repository...");}
}@Configuration
public class UserConfig {public void doConfig(){System.out.println("do config...");}
}@Component
public class UserComponent {public void doComponent(){System.out.println("do component...");}
}
@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);// 从 IoC 容器中拿到对象// 1. 根据类型拿到对象UserController bean1 = context.getBean(UserController.class);bean1.sayHi();// 2. 根据名称拿到对象UserController bean2 = (UserController) context.getBean("userController");bean2.sayHi();// 3. 根据类型和名称拿到对象UserController bean3 = context.getBean("userController", UserController.class);bean3.sayHi();// 前两个字母大写的类,从 IoC 容器中拿对象// 1. 根据类型拿到对象UController bean4 = context.getBean(UController.class);bean4.sayHi();// 2. 根据名称拿到对象UController bean5 = (UController) context.getBean("UController");bean5.sayHi();// 3. 根据类型和名称拿到对象UController bean6 = context.getBean("UController", UController.class);bean6.sayHi();// UserServiceUserService serviceBean1 = context.getBean(UserService.class);serviceBean1.doService();UserService serviceBean2 = (UserService) context.getBean("userService");serviceBean2.doService();UserService serviceBean3 = context.getBean(UserService.class, "userService");serviceBean3.doService();// UserRepositoryUserRepository repoBean1 = context.getBean(UserRepository.class);repoBean1.doRepository();UserRepository repoBean2 = (UserRepository) context.getBean("userRepository");repoBean2.doRepository();UserRepository repoBean3 = context.getBean(UserRepository.class, "userRepository");repoBean3.doRepository();// UserConfigUserConfig configBean1 = context.getBean(UserConfig.class);configBean1.doConfig();UserConfig configBean2 = (UserConfig) context.getBean("userConfig");configBean2.doConfig();UserConfig configBean3 = context.getBean(UserConfig.class, "userConfig");configBean3.doConfig();// UserComponentUserComponent componentBean1 = context.getBean(UserComponent.class);componentBean1.doComponent();UserComponent componentBean2 = (UserComponent) context.getBean("userComponent");componentBean2.doComponent();UserComponent componentBean3 = context.getBean(UserComponent.class, "userComponent");componentBean3.doComponent();}}

运行结果:

4. 获取 @Bean 注解修饰的方法创建的对象

@Bean 注解一定需要配合类注解才能将对象交给 IoC 容器管理;

@Data
public class UserInfo {private Integer id;private String userName;public void getUserInfo(){System.out.println("get user info...");}
}@Service
public class UService {@Beanpublic UserInfo getUserInfo(){return new UserInfo();}
}
@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);// 方法返回的 BeanUserInfo methodBean1 = context.getBean(UserInfo.class);methodBean1.getUserInfo();UserInfo methodBean2 = (UserInfo) context.getBean("getUserInfo");methodBean2.getUserInfo();UserInfo methodBean3 = context.getBean(UserInfo.class, "getUserInfo");methodBean3.getUserInfo();
}

注意:

获取方法返回的对象的时候,一定要注意提供方法的类需要使用五大注解之一修饰;

提供的方法,需要使用 @Bean 注解修饰;

三、DI

DI 是依赖注入(Dependency Injection),容器在运行期间,动态为应用程序提供运行时依赖的资源;

关于依赖注入,Spring 提供了三种方式:

  1. 属性注入;
  2. 构造方法注入;
  3. Setter 方法注入;

1. 属性注入

属性注入,是通过 @Autowired 注解实现的;

@Service
public class UserService {public void doService(){System.out.println("do service...");}
}@Controller
public class UserController {@Autowiredprivate UserService userService;public void sayHi(){userService.doService();System.out.println("hi, user controller...");}
}@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);// DIUserController bean = context.getBean(UserController.class);bean.sayHi();}

运行结果:

去掉 @Autowired 注解:

2. 构造方法注入

@Controller
public class UserController {private UserService userService;public UserController() {}@Autowiredpublic UserController(UserService userService) {this.userService = userService;}public void sayHi(){userService.doService();System.out.println("hi, user controller...");}
}

运行结果:

注意:使用构造方法注入,推荐增加无参的构造方法,并在注入的构造方法上面增加 @Autowired 注解,明确指定使用哪个构造方法;

3. Setter 方法注入

@Controller
public class UserController {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi(){userService.doService();System.out.println("hi, user controller...");}
}

运行结果:

4. 三种注入方式的优缺点分析

属性注入:

优点:

  • 简洁,使用方便;

缺点:

  • 只能用于 IoC 容器,非 IoC 容器会出现空指针异常;
  • 不能注入 final 修饰的属性;

构造方法注入:

优点:

  • 可以注入 final 修饰的属性;
  • 注入的对象不会被修改;
  • 依赖的对象在使用前会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法在类加载阶段就会执行;
  • 通用性好,构造方法是 JDK 支持的,更换框架也适用;

缺点:

  • 注入多个对象,代码会比较繁琐;

Setter 方法注入:

优点:

  • 方便在实例化之后,重新对该对象进行配置或注入;

缺点:

  • 不能注入 final 修饰的属性;
  • 注入对象可能会被改变,因为 setter 方法可能会被多次调用,有被改的风险;

5. @Autowired 存在的问题

当同一个类型存在多个 Bean 时,使用 @Autowired 可能存在问题;

这里主要涉及通过方法创建对象的方式;

@Data
public class UserInfo {private Integer id;private String userName;public UserInfo() {}public UserInfo(Integer id, String userName) {this.id = id;this.userName = userName;}public void getUserInfo(){System.out.println("get user info...");}
}@Service
public class UService {@Beanpublic UserInfo getUserInfo1(){return new UserInfo(1, "zhangsan");}@Beanpublic UserInfo getUserInfo2(){return new UserInfo(2, "lisi");}
}@Controller
public class UController {@Autowiredprivate UserInfo userInfo;public void sayHi(){userInfo.getUserInfo();System.out.println("hi, UController...");}
}@SpringBootApplication
public class SpringIocApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);UController userInfoBean = context.getBean(UController.class);userInfoBean.sayHi();
}

运行结果:

由于 UserInfo 有两个 Bean,所以在获取/注入 Bean 的时候出现了错误;

解决办法:

Spring 提供了几种解决方案:

  • @Primary:当多个相同类型的 Bean 注入时,用于指定默认的 Bean;
  • @Qualifier:指定要注入的 Bean 对象,必须和 @Autowaired 配合使用;
  • @Resource:按照 Bean 的名称进行注入,通过 name 属性指定要注入的 Bean 的名称;

@Primary:

@Service
public class UService {@Primary@Beanpublic UserInfo getUserInfo1(){return new UserInfo(1, "zhangsan");}@Beanpublic UserInfo getUserInfo2(){return new UserInfo(2, "lisi");}
}

@Qualifier:

@Controller
public class UController {@Autowired@Qualifier("getUserInfo2")private UserInfo userInfo;public void sayHi(){userInfo.getUserInfo();System.out.println("hi, UController...");}
}

@Resource:

@Controller
public class UController {@Resource(name = "getUserInfo1")private UserInfo userInfo;public void sayHi(){userInfo.getUserInfo();System.out.println("hi, UController...");}
}

6. @Autowaired 与 @Resource 的区别

@Autowired 是 Spring 框架提供的注解,@Resource 是 JDK 提供的注解;

@Autowired 是默认按照类型注入,@Resource 是按照指定的名称注入,相比 @Autowired,支持更多的参数设置;


http://www.dtcms.com/a/414561.html

相关文章:

  • 做家装的网站有哪些安徽建工集团网站
  • 零知IDE——基于STM32F407VET6和雨滴传感器的多界面TFT降雨监测显示系统
  • 轻松在家构建AI集群,开启智能生活
  • 从PHP入门到公网部署:Web开发与服务器运维实战指南
  • 产品展示网站系统深圳app搭建
  • 40 dubbo和springcloud
  • (26)ASP.NET Core2.2 EF保存(基本保存、保存相关数据、级联删除、使用事务)
  • 西昌新站seo太原网站建设方案开发
  • 永久个人网站网站开发 设计文档
  • 天拓四方集团IoT平台在金属表面处理行业的智能化转型实践
  • 00-1-正则表达式学习心得:从入门到上瘾,再到克制
  • 【性能测试之正则表达式】正则表达式零基础入门:从“抄”到“写”,附性能测试实战案例
  • python-poppler - PDF文档处理Python绑定库
  • Android开发-Handler消息机制记录
  • 通信专业知识图谱​
  • 网站建设的页面要求一级域名二级域名
  • 基础镜像清理策略在VPS环境存储优化中的维护规范
  • The 2025 ICPC South America - Brazil First Phase
  • 开源 C# 快速开发(六)自定义控件--圆环
  • Calico 网络插件在 K8s 集群的作用
  • 蓝桥杯13届省题
  • 手机网站开发+图库类怎样在手机上建设网站
  • MySQL三层架构:从连接管理到数据存储
  • 嵌入式硬件——IMX6ULL时钟配置
  • 【用androidx.camera拍摄景深合成照片】
  • linux安装google chrome 谷歌浏览器
  • 从零起步学习Redis || 第二章:Cache Aside Pattern(旁路缓存模式)以及优化策略
  • 两性做受技巧视频网站喊别人做的网站不肯给代码
  • ESP32-S3入门第八天:往期知识回顾与实战练习
  • Claude Code 实战指南(三):AI辅助开发工作流 Spec Workflow MCP教程