SpringIoC&DI
文章目录
什么是spring
Spring是⼀个开源框架, 他让我们的开发更加简单. 他⽀持⼴泛的应⽤场
景, 有着活跃⽽庞⼤的社区, 这也是Spring能够⻓久不衰的原因.
但是这个概念相对来说, 还是⽐较抽象.
我们⽤⼀句更具体的话来概括Spring, 那就是: Spring 是包含了众多⼯具⽅法的 IoC
容器
那问题来了,什么是容器?什么是 IoC
容器?接下来我们⼀起来看
Spring的两大核心思想:Ioc
AOP
什么是容器
容器是⽤来容纳某种物品的(基本)装置。⸺来⾃:百度百科
⽣活中的⽔杯, 垃圾桶, 冰箱等等这些都是容器.
我们想想,之前课程我们接触的容器有哪些?
-
List/Map -> 数据存储容器
-
Tomcat -> Web 容器
-
学校 -> 学生容器
什么是IoC
IoC:控制反转
也就是说 Spring 是⼀个"控制反转"的容器
IoC思想
举一个方便理解的例子:造车
传统方式
先设计轮子tire
,设计底盘bottom
,设计车身framework
,设计汽车car
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.run();
}
}
public class Car {
private Framework framework;
public Car(){
framework = new Framework();
System.out.println("cat init........");
}
public void run() {
System.out.println("car run.........");
}
}
public class Framework {
private Bottom bottom;
public Framework() {
bottom = new Bottom();
System.out.println("framework init.....");
}
}
public class Bottom {
private Tire tire;
public Bottom() {
tire = new Tire();
System.out.println("bottom init.....");
}
}
public class Tire {
private int size = 17;
public Tire() {
System.out.println("tire size = 17");
}
}
运行:
tire size = 17
bottom init.....
framework init.....
cat init........
car run.........
IoC写法
spring的工作
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(17);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.run();
}
}
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........");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("framework init...");
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("bottom init...");
}
}
public class Tire {
private int size;
public Tire(int size) {
System.out.println("tire size = " + size);
}
}
在传统的代码中对象创建顺序是:Car -> Framework -> Bottom -> Tire
改进之后解耦的代码的对象创建顺序是:Tire -> Bottom -> Framework -> Car
我们发现了⼀个规律,通⽤程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了
Framework
,Framework
创建并创建了 Bottom
,依次往下,⽽改进之后的控制权发⽣的反转,不再
是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由
当前类控制了.
这样的话, 即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC
的
实现思想。
这部分代码, 就是IoC
容器做的⼯作.
从上⾯也可以看出来, IoC
容器具备以下优点:
资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。
-
资源集中管理:
IoC
容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC
容器中去取就可以了 -
我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度.
Spring
就是⼀种IoC
容器, 帮助我们来做了这些资源管理
DI
DI
: Dependency
Injection
(依赖注⼊)
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
上述代码中, 是通过构造函数的⽅式, 把依赖对象注⼊到需要使⽤的对象中的
IoC
是⼀种思想,也是"⽬标", ⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI
就属于
具体的实现。所以也可以说, DI
是IoC
的⼀种实现
Spring IoC/DI
对象的管理:
- 存对象
@Component
- 取对象
@AutoWired
IoC详细用法
五大注解:
@Controller
@Service
@Component
@Repository
Configuration
方法注解:
@Bean
获取Bean的方式
-
通过类型获取 Bean:
使用ApplicationContext
的getBean()
方法根据类型获取 Bean。// 假设有一个 UserService 接口和其实现类 UserServiceImpl UserService userService = applicationContext.getBean(UserService.class);
-
通过名称获取 Bean:
使用ApplicationContext
的getBean()
方法根据 Bean 的名称获取。// 假设有一个名为 "userService" 的 Bean UserService userService = (UserService) applicationContext.getBean("userService");
-
通过类型和名称获取 Bean:
使用ApplicationContext
的getBean()
方法根据类型和名称获取 Bean。// 假设有一个名为 "userService" 的 Bean UserService userService = applicationContext.getBean("userService", UserService.class);
-
通过 @Autowired 注解自动注入:
在需要使用的地方使用@Autowired
注解进行自动注入。@Autowired private UserService userService;
-
通过构造函数参数注入:
在构造函数中接收 Bean 对象作为参数。public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } }
-
通过@Resource 注解注入:
使用@Resource
注解进行注入,可以指定 Bean 的名称。@Resource(name = "userService") private UserService userService;
@Controller(控制器存储)
@Controller
public class UserController {
public void sayHi(){
System.out.println("hello, UserController...");
}
}
// 根据类型去拿
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
// ApplicationContext : Spring 上下文、运行环境
ApplicationContext context = SpringApplication.run(IocApplication.class, args);
UserController bean = context.getBean(UserController.class);
bean.sayHi();
}
}
@Service(服务存储)
@Service
public class UserService {
public void syaHi(){
System.out.println("hello UserService...");
}
}
// 根据名称去拿
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserService userService = (UserService) context.getBean("userService");
userService.syaHi();
}
}
@Component(组件存储)
// 根据名称("userComponent")和类型(UserComponent.class)来获取的
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserComponent userComponent = context.getBean("userComponent", UserComponent.class);
userComponent.sayHi();
}
}
@Component
public class UserComponent {
public void sayHi(){
System.out.println("hello,UserComponent...");
}
}
@Repository(仓库存储)
@Repository
public class UserRepository {
public void sayHi(){
System.out.println("hello, userRepository");
}
}
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserRepository userRepository = (UserRepository) context.getBean("userRepository");
userRepository.sayHi();
}
}
@Configuration(配置存储)
@Configuration
public class UserConfig {
public void sayHi(){
System.out.println("hello,userConfig");
}
}
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserConfig userConfig = context.getBean(UserConfig.class);
userConfig.sayHi();
}
}
为什么要有这么多注解?
-
@
Controller
:控制层, 接收请求, 对请求进⾏处理, 并进⾏响应. -
@
Servie
:业务逻辑层, 处理具体的业务逻辑. -
@
Repository
:数据访问层,也称为持久层. 负责数据访问操作 -
@
Configuration
:配置层. 处理项⽬中的⼀些配置信息
五大注解,从概念上还被赋予了别的含义
@Component
:用于将一个类标识为组件,并将其纳入 Spring 的管理中。它是通用的注解,可以用于任何层次的组件,如控制器、服务、存储库等。@Repository
:用于标识一个数据访问层(DAO)的类。它表示一个持久化操作相关的类,负责与数据库或其他数据存储进行交互。@Service
:用于标识一个服务层(Service)的类。它表示一个业务逻辑相关的类,通常用于封装和处理复杂的业务逻辑。@Controller
:用于标识一个控制器层(Controller)的类。它表示一个处理 HTTP 请求和响应的类,通常用于处理用户的请求并返回相应的视图或数据。除了具备让Spring管理的功能之外,接口的入口,必须为@Controller@Configuration
:用于标识一个配置类,它是 Spring 中定义 bean 的一种方式。通过在类上添加@Configuration
注解,可以将该类声明为一个配置类,其中可以定义 bean 的创建和配置。
这五个注解之间有一定的关系和应用场景:
@Component
是一个通用的注解,用于将一个类标识为组件。它可以用于任何层次的组件,包括控制器、服务、存储库等。其他四个注解@Repository
、@Service
、@Controller
都是特定类型的组件,它们实际上是通过@Component
注解派生而来的,具有更具体的语义。@Repository
注解通常用于标识数据访问层(DAO)的类,表示它是与数据库或其他数据存储进行交互的组件。@Repository
注解还具有将数据库相关的异常转换为 Spring 的数据访问异常的功能。@Service
注解通常用于标识服务层(Service)的类,表示它是应用程序中的一个服务组件。服务层通常封装和处理复杂的业务逻辑,并与其他组件进行交互。@Controller
注解通常用于标识控制器层(Controller)的类,表示它是处理 HTTP 请求和响应的组件。控制器层接收用户的请求,调用适当的服务层组件进行处理,并返回相应的视图或数据。@Configuration
注解用于标识配置类,其中定义了创建和配置 bean 的方法。配置类通常包含@Bean
注解,用于声明和定义 Spring bean。其他组件类可以通过@Autowired
注解注入这些配置类中定义的 bean。
这些注解之间的关系是:@Repository
、@Service
、@Controller
注解都是通过 @Component
注解派生而来的,它们具有更具体的语义,用于在组件层次上提供更明确的分类和区分。而 @Configuration
注解用于标识配置类,提供了声明和定义 bean 的方式。通过使用这些注解,我们可以更好地组织和管理应用程序中的组件,并实现依赖注入和其他特定的功能。
程序的应用分层,调用流程如下:
方法注解@Bean
类注解是添加到某个类上的, 但是存在两个问题:
-
使⽤外部包⾥的类, 没办法添加类注解
-
⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
举个例子:
单对象
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserInfo userInfo = context.getBean(UserInfo.class);
System.out.println(userInfo);
}
}
报错。
原因就是在 Spring 框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
这样既可。
多对象
@Bean
注解定义的对象,默认名称为方法名
@Configuration
public class BeanConfig {
@Bean
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
@Bean
public UserInfo userInfo1(){
UserInfo userInfo1 = new UserInfo();
userInfo1.setId(6);
userInfo1.setName("6666");
userInfo1.setAge(66);
return userInfo1;
}
}
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserInfo userInfo = context.getBean("userInfo",UserInfo.class);
System.out.println(userInfo);
UserInfo userInfo1 = context.getBean("userInfo1",UserInfo.class);
System.out.println(userInfo1);
}
}
重命名
@Configuration
public class BeanConfig {
@Bean("u1")
public UserInfo userInfo(){
UserInfo userInfo = new UserInfo();
userInfo.setId(5);
userInfo.setName("zhangsan");
userInfo.setAge(18);
return userInfo;
}
}
@SpringBootApplication
public class IocApplication {
public static void main(String[] args) {
UserInfo userInfo = context.getBean("u1",UserInfo.class);
System.out.println(userInfo);
}
}
扫描路径
Q: 使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?
A: 不⼀定(原因:bean想要⽣效,还需要被Spring扫描)
DI详解
依赖注入有三种方式:
- 属性注入
- 构造方法注入
- Setter注入
属性注入
@Autowired
@Controller
public class UserController {
// 把 UserService 注入进来
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
}
}
构造方法注入
@Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
}
}
@Controller
public class UserController {
private UserService userService;
private UserConfig userConfig;
public UserController(UserService userService, UserConfig userConfig) {
this.userService = userService;
this.userConfig = userConfig;
}
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
userConfig.sayHi();
}
}
当只有一个构造函数的时候,Spring 会知道使用哪个
当有多个构造函数的时候,Spring 会使用默认无参的构造函数,如果没有这个函数,Spring 会报错
Caused by: java.lang.NoSuchMethodException: org.haobin.ioc.demo.Controller.UserController.<init>()
at java.lang.Class.getConstructor0(Class.java:3082) ~[na:1.8.0_361]
at java.lang.Class.getDeclaredConstructor(Class.java:2178) ~[na:1.8.0_361]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78) ~[spring-beans-5.3.23.jar:5.3.23]
... 18 common frames omitted
同样的 我们可以使用@Autowired
指定构造函数
@Controller
public class UserController {
private UserService userService;
private UserConfig userConfig;
public UserController(){
}
public UserController(UserService userService) {
this.userService = userService;
}
@Autowired
public UserController(UserService userService, UserConfig userConfig) {
this.userService = userService;
this.userConfig = userConfig;
}
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
userConfig.sayHi();
}
}
Setter注入
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
}
}
如果需要注入多个属性,就需要注入多个@Autowired
@Controller
public class UserController {
@Autowired
private UserService userService;
@Autowired
private UserComponent userConponent'
public void sayHi(){
System.out.println("hello, UserController...");
userService.syaHi();
userComponent.sayHi();
}
}
三种注入优缺点
-
属性注⼊
- 优点: 简洁,使⽤⽅便;
- 缺点:
- 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)
- 不能注⼊⼀个Final修饰的属性
-
构造函数注⼊(Spring 4.X推荐)
-
优点:
-
可以注⼊final修饰的属性
-
final修饰的属性有一个要求,需要满足一下条件
-
-
注⼊的对象不会被修改
-
依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
-
通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
-
-
缺点:
- 注⼊多个对象时, 代码会⽐较繁琐
-
-
Setter注⼊(Spring 3.X推荐)
- 优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
- 缺点:
- 不能注⼊⼀个Final修饰的属性
- 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.
@Autowired存在的问题
@Controller
public class UserController2 {
@Autowired
private UserInfo userInfo;
public void sayHi(){
System.out.println("hello, UserController...");
System.out.println(userInfo);
}
}
Description:
Field userInfo in org.xxx.ioc.demo.Controller.UserController2 required a single bean, but 2 were found:
- u1: defined by method 'userInfo' in class path resource [org/haobin/ioc/demo/config/BeanConfig.class]
- userInfo1: defined by method 'userInfo1' in class path resource [org/haobin/ioc/demo/config/BeanConfig.class]
当同样的类型存在多个对象的时候,可能会报错
@Autowired
private UserInfo userInfo;
先根据名称来获取,如果获取到了,正确响应…
如果没有获取到,就根据类型匹配,此时,如果匹配到多个,则报错。
通常做法:
不使用变量名称来指定获取某个bean,而是通过其他手段来指定bean的名称。
通常我们会认为变量名的修改不影响我们的业务逻辑处理
如何解决上述问题呢?Spring提供了以下⼏种解决⽅案:
- @Primary
- @Qualifier
- @Resource
1. @Primary
@Primary
注解用于标识一个 Bean 为首选项,当存在多个候选 Bean 时,被标记为 @Primary
的 Bean 会被优先选择进行注入。
示例:
@Component
@Primary
public class PrimaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
@Component
public class SecondaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
在这个示例中,当需要注入 DataSource
类型的 Bean 时,如果没有明确指定使用哪个 Bean,Spring 将会选择使用 PrimaryDataSource
。
2. @Qualifier
@Qualifier
注解用于指定注入的 Bean 名称或者 Bean 的限定符(Qualifier),用于消除歧义,明确指定要注入的是哪个 Bean。
示例:
@Component
@Qualifier("primary")
public class PrimaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
@Component
@Qualifier("secondary")
public class SecondaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
@Component
public class DataSourceService {
@Autowired
@Qualifier("primary") // 指定注入 primary Bean
private DataSource dataSource;
}
在这个示例中,DataSourceService
类中的 dataSource
字段会被注入 PrimaryDataSource
Bean。
3. @Resource
@Resource
注解是 JavaEE 提供的一种依赖注入方式,Spring 也支持该注解。它默认按照名称(即 Bean 的名称)进行自动装配,当无法按照名称匹配时,再按照类型匹配。
示例:
@Component
public class PrimaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
@Component
public class SecondaryDataSource implements DataSource {
// 实现 DataSource 接口的相关逻辑
}
@Component
public class DataSourceService {
@Resource // 默认按照名称匹配,会注入 PrimaryDataSource Bean
private DataSource dataSource;
}
在这个示例中,DataSourceService
类中的 dataSource
字段会被注入 PrimaryDataSource
Bean,因为 PrimaryDataSource
是唯一匹配的 Bean。
总结
告诉Spring管理bean、bean的存储
-
类注解:五大注解
-
方法注解:@Bean
Bean的名称
-
五大注解
类名首字母小写,如果前两位字母均为大写,则为原类名
也可以指定Bean的名称 指定方法:
@Controller("beanName")
-
@Bean
默认名称:方法名
也可以指定名称
@Bean("beanName")
获取Bean的方式
BeanFeactory和ApplicationContext区别
八股文回答
ApplicationContext context = SpringApplication.run(IocApplication.class, args);
UserController bean = context.getBean(UserController.class);
bean.sayHi();
UserService userService = (UserService) context.getBean("userService");
userService.syaHi();
UserComponent userComponent = context.getBean("userComponent", UserComponent.class);
userComponent.sayHi();
扫描路径
Spring 默认扫描路径是 启动类所在的路径。