一、IoC详解
控制反转(Inversion of Control,IoC)是一种软件设计原则,其核心思想是将程序中的对象创建、依赖管理和生命周期控制的权力从应用程序代码转移到外部容器或框架,从而降低组件间的耦合度,提升代码的灵活性和可维护性。
而Spring IoC就是将对象的控制权交给Spring的IoC容器,由IoC容器创建和管理对象,也就是Bean的存储。
1.1 Bean的存储
有两类注解可以实现Bean的存储:
(1)类注解:@Controller 、@Service 、@Repository 、@Compenent 、 @Configuration
(2)方法注解:@Bean
1.1.1 @Controller(控制器存储)
使用@Controller存储Bean的代码如下,在类前加上@Controller注解即可:
存储完成后,需要学习如何从Spring中获取到存储的对象:
1.使用ApplicationContext对象接收启动类run方法的返回值
2.使用getBean方法获取对象
(可以认为下图context中就存储着Spring容器中的所有对象)
接下来启动程序,观察结果:
可以看到成功调用print方法,说明获取对象成功
如果将@Controller注解去掉,在启动程序:
可以看到报错信息为:找不到类型为UserController的对象
在上面获取Bean的方法中,其实还有其它方式可以获取:(带有注释的为常用的3个获取Bean的方法)
根据类型获取Bean我们比较清楚(如上面我们根据类型UserController获取到了Bean),但是对并不是由我们创建的而是Spring进行管理的,如何知道对象名从而获取到对象?
其实,关于Bean(对象)的命名是有约定的:
<1>Bean的名称使用小写开头,后面使用驼峰式大小写;
如:UserController的对象名为userController、User的对象名为user
<2>如果类名的前两个字母均为大写,那么Bean的名称为类名本身;
如:UController的对象名为UController
了解了Bean的命名规则,我们使用上面常见的3种获取Bean的方法来获取:
启动程序,发现3个对象的地址一样,说明上面3种方式获取到的对象是同一个:
可以看到,上述3种获取Bean的方法中,根据类型获取Bean是最简单的,但是当同种类型的Bean有多个时,Spring会由于不知道获取哪个Bean而报错,这点在了解到方法注解@Bean时再做示范。
1.1.2 @Service (服务存储)
使用@Service存储Bean的代码如下:
获取bean的代码:
启动程序,成功调用print方法,说明获取对象成功:
同样的,去掉@Service注解,会使Spring找不到相应类型的对象:
1.1.3 @Repository(仓库存储)
存储Bean:
获取Bean:
启动程序:
去掉@Repository注解,观察结果:
1.1.4 @Component(组件存储)
存储Bean:
获取Bean:
启动程序:
去掉@Component注解,观察结果:
1.1.5 @Configuration(配置存储)
存储Bean:
使用Bean:
启动程序:
去掉@Configuration注解,观察结果:
可以看到,上面5种类注解的功能似乎差不多,但是为什么需要那么多注解来实现同样的功能?
1.2 多注解管理对象的作用?
其实这个和前面的应用分层是相呼应的, 程序员再看到类注解后,就可以直接了解当前类的用途:
• @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
• @Servie:业务逻辑层, 处理具体的业务逻辑.
• @Repository:数据访问层(Dao层),也称为持久层. 负责数据访问操作
• @Configuration:配置层. 处理项目中的一些配置信息.应用分层的调用流程如下:
类注解之间的关系:
可以看到,这些注解中都有一个@Component注解,说明它们属于@Component的”子类“,@Component是一个元注解(可以注解其它的类注解),而@Controller、@Service、@Repository、@Configuration被称为@Component的衍生注解。
1.3 通过方法注解@Bean存储Bean
前面我们学习了类注解,仔细思考不难发现,类注解存在一定的局限性:
<1>使用外部包里的类,没法使用类注解;
<2>一个类需要多个对象(如需要使用Student类创建多个学生对象),也没法使用类注解(类注解只能管理一个对象)
对于上述场景,就需要使用方法注解@Bean,使用@Bean直接前,需要详细了解它的作用:
<1>@Bean方法必须返回一个对象,返回的对象会被Spring容器管理;
<2>方法内部创建的对象如果不是返回值,就不会被Spring管理
<3>返回null或非对象会导致异常
<4>通过@Bean被Spring管理的对象名默认为方法名
接下来,我们学习如何使用@Bean注解:
接下来,尝试获取bean:
启动程序:
结果却报错了,提示没有Student这个类型的对象可明明已近使用了@Bean注解了,原因是什么?
原因:方法注解要配合类注解使用
加上类注解,再次启动程序:
成功获取到Spring中的Stuednt对象:
在上面的过程中,已近了解了@Bean的用法,但是上面同类型的对象也还是只有一个,接下来我们尝试注册多个同类型对象:
管理多个bean:
获取bean:
启动程序:
发现结果报错了,提示希望或取一个对象,但是同类型的对象有两个(不知道获取哪个)
对此我们可以使用 对象名 + 类型 来获取对象:
再次启动程序,观察结果:
可以看到,通过@Bean成功注册到多个同类型对象
!!!小知识:
不管是类注解还是方法注解使Spring管理的对象,如果不想使用默认的对象名,都可以通过修改注解的属性来自定义对象名如:
上面两个对象的名称不再使默认的方法名(s1、s2),而是自定义的student1、student2。
(其它注解的修改方式类似)
二、DI详解
什么是依赖注入(DI)?
将对象所需的依赖(其他对象或服务)由外部容器(如 Spring IoC 容器)注入,而非由对象自行创建或查找。
Spring中依赖注入的三种方式:
1. 属性注入(Field Injection)
2. 构造方法注入(Constructor Injection)
3. Setter 注入(Setter Injection)
2.1 属性注入
属性注入是使用@Autowired实现的,以UserService类注入到UserController类中为例:
在启动类中获取UserController对象:
启动程序:
可以看到,Service对象被成功注入
接下来,去掉@AutoWired注解,再次启动程序:
报错了,提示信息说userService为null,说明去掉@Autowried不能成功注入
2.2 构造方法注入
启动程序,可以看到也是注入成功了:
如果有多个构造方法,会怎么样呢?
再次启动程序,观察结果:
报错了,报错信息为userServer为null,因此Spring默认使用的是无参构造方法
因此,如有多个构造方法,需要使用@AutoWried注解指定使用哪个构造方法,如:
再次启动程序:
使用了目标构造方法,成功注入
2.3 setter注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解
启动程序,可以看到成功注入UserService对象:
去掉@AutoWired,再次运行程序:
报错,报错信息为userService为null
2.4 三种注入方式的优缺点
① 构造函数注入(推荐)
方式:通过构造函数参数注入依赖。
优点:
强制依赖在对象创建时提供,确保对象完全初始化。
支持 final 字段,实现不可变对象。
② Setter 方法注入
方式:通过 setter 方法注入依赖。
优点:适用于可选依赖或需动态更新依赖的场景。
③ 属性注入
方式:直接在字段上使用 @Autowired。
缺点:隐藏依赖关系,破坏封装性,难以测试
2.5 @AutoWried存在的问题
当同一类型存在多个bean时,会出现问题(不知道注入哪个):
启动程序:
启动失败,失败原因是有两个Student对象,不知道注入哪个。
对于上述问题, Spring提出了以下3种解决方案:
(1)@Primary(和方法注解@Bean一起使用,表示默认注入哪个对象)(不常用)
(2)@Qualifier(与@AutoWried一起使用,别是指定注入哪个对象)
(3)@Resource(代替@Autowried,通过name属性指定注入哪个对象)
一、@Primary
运行程序,可以看到,成功注入Student对象(student1):
二、@Qualifier
运行程序,可以看到,同样成功注入Student对象(student1):
三、@Resource
运行程序,可以看到,成功注入Student对象(student1):
![]()
2.6 @AutoWried与@Resource的区别
• @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
• @Autowired 默认是按照类型注⼊,而@Resource是按照名称注入. 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean.