Bean(对象):
其实就是Spring框架对于java中“对象”这个概念的另一个说法。当然,不是代码中所有的对象都是bean,只有被IoC容器管理的才叫Bean。
DI(依赖注入):
Dependency Injection,称为依赖注入。最常见的是,一个对象A中某个属性是对象B,那么我们称对象A依赖对象B,创建对象A时需要注入对象B,这叫依赖注入。
(当然,依赖注入并不仅限于属性的注入,还可以包括方法参数的注入等)
IoC(控制反转)
背景引入
传统的对象的控制权是开发者拥有的。开发者需要自己创建(常说的new一个对象),自己调用,自己销毁。但是一旦程序代码量多了,体量变大,便会出现如下问题:
1、数量庞大:手动创建,手动调用的对象很多,代码实现很多。
2、位置不定:在代码中的任何一个地方都可能存在创建、调用对象的情况,后续更改维护困难。
3、回收麻烦:开发者需要自己销毁或回收对象,去管理它的生命周期。
4、手动注入:在多层依赖注入场景下,需要手动处理递归依赖关系。
所以Spring框架提供了这么一种机制,叫IoC,Inversion Of Control,控制反转。
即开发者不再自己创建、销毁、管理对象,而是它自己提供一个IoC容器,只要开发者在创建项目工程时导入相应的启动器或者依赖,这个容器便自动创建相关需要使用的对象。开发者要用时从容器中获取相关对象即可。
原始代码
这个机制的实现方式分为两种,基于XML实现和基于注解实现。
我们以一个传统的数据库配置对象为例,传统手动创建的结构如下,一个空的maven项目。
pom文件引入依赖坐标:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
</dependencies>
DataConfig类代码如下:
package org.exmaple.ioc;
import lombok.Data;
@Data
//注意这个@Data只是帮我们自动创建setter和getter方法等,是lombok依赖的相关注解,和Ioc没有任何关系
public class DataConfig {
private String url;
private String drivername;
private String username;
private String password;
}
Main类代码如下:
package org.exmaple.ioc;
public class Main {
public static void main(String[] args) {
DataConfig dataConfig = new DataConfig();
dataConfig.setUrl("localhost:3306");
dataConfig.setDrivername("mysql");
dataConfig.setUsername("root");
dataConfig.setPassword("123456");
System.out.println(dataConfig);
}
}
运行Main类代码,我们可以发现这个对象的实例被创建且成功打印。
基于xml实现(代码1)
整体项目结构如下:
其中spring.xml的创建过程如下,选择Spring配置。
(注意这个选项是需要先完成下面pom.xml相关坐标导入之后才能看见的)
(一般都命名为applicationContext.xml。这里为了展示方便命名成spring.xml的hhh)
首先我们需要在pom文件中引入IoC需要的依赖坐标,因为IoC容器本身的实现也是底层的一段程序,那么它的执行肯定也需要依赖程序的上下文,所以需要提供spring上下文的相关依赖。
<!-- 引入spring上下文的依赖坐标。-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.3</version>
</dependency>
spring.xml代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.exmaple.ioc.DataConfig" id="config">
<property name="url" value="localhost:3306"></property>
<property name="drivername" value="mysql"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
Main类代码如下:
package org.exmaple.ioc;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
System.out.println(context.getBean("config"));
}
}
最后运行Main程序,我们可以发现这个对象的实例被创建且成功打印。
代码详解
观察spring.xml代码,我们发现,其实他就相当于用xml语言去实现new方法和set方法
1、我们的new对象,是用new+类名从而找到类,然后创建类的实例。譬如原始代码的new DataConfig()
而xml中是通过class="类的全路径"从而找到类,然后创建类的实例。譬如代码1的
class="org.exmaple.ioc.DataConfig"
2、而创建类的实例后,我们需要用一个变量去接收它,所以创建一个变量并给变量名。
譬如原始代码的DataConfig dataConfig
而xml中是通过id="变量名"来创建一个变量并给变量名,去接收类的实例。譬如代码1的
id="config"
3、之后我们需要给这个变量的相关属性赋值,譬如原始代码中的dataConfig.setUrl("xxx");
而xml中是通过<property name="a" value="b">,去给a属性赋值b的,譬如代码1的
<property name="url" value="localhost:3306"></property>
4、最后,我们手动创建对象的原始代码就可以直接调用对象了。
但是xml中的如上步骤,只是相当于预定义了对象的相关属性。
因为IoC容器需要加载我们的xml配置文件,才知道需要创建这么一个对象。也就是说如果没有接下来Main类中的相关代码,而只有xml中的代码,这个对象是不存在在IoC容器中的,甚至IoC容器本身都不存在,因为IoC容器本身也是ApplicationContext的实例。需要被实例化。
于是我们必须先通过xml文件来实例化一个IoC容器。具体做法是,用ApplicationContext(IoC容器的上层接口)的new ClassPathXmlApplicationContext(“xml文件名”)方法,使得Spring框架根据XML文件创建并管理Spring容器(就是IoC容器)。也就是代码1中的
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
随后我们通过他的getBean方法来获取具体的对象,也就是代码1中的
context.getBean("config")
优劣对比
如上,我们可以看到原来传统手动创建对象的劣势:
1、数量庞大:手动创建,手动调用的对象很多,代码实现很多。
2、位置不定:在代码中的任何一个地方都可能存在创建、调用对象的情况,后续更改维护困难。
3、回收麻烦:开发者需要自己销毁或回收对象,去管理它的生命周期。
4、手动注入:在多层依赖注入场景下,需要手动处理递归依赖关系。
其中因为将对象都放在了xml文件中,所以第二点有所改善。同时对象交给了IoC容器进行管理,所以第三点有所改善。
但第一点,因为还是要自己编写xml代码,所以依旧要实现很多代码。
同时第四点,代码1中没有相关展示,但是如果一个xml中的id为A的bean引入了另一个id为B的bean,我们还是要在那个A的bean中使用ref进行引用的,并且确保依赖的B先被创建完成,再将其注入到A中,也就是说还是需要手动处理递归依赖关系的。
为此产生了基于注解的IoC实现。
基于注解实现(代码2)
基于注解实现实际上可以细分为两种实现,第一种是用配置类实现,第二种是扫包+
基于配置类实现
用一个java类来替代xml文件,在这里是BeanConfiguration,整体项目结构如下:
BeanConfiguration类代码如下:
package org.exmaple.configuration;
import org.exmaple.ioc.DataConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfiguration {
@Bean
public DataConfig dataConfig(){ //这里的dataConfig绑定的是context.getBean("dataConfig")中的dataConfig
//如果这个方法改成了public DataConfig Config(){ 那么后者也应该改成context.getBean("Config")
DataConfig dataConfig = new DataConfig();
dataConfig.setUrl("localhost:3306");
dataConfig.setDrivername("mysql");
dataConfig.setUsername("root");
dataConfig.setPassword("123456");
return dataConfig;
}
}
Main代码如下:
package org.exmaple.ioc;
import org.exmaple.configuration.BeanConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
System.out.println(context.getBean("dataConfig"));//这个是通过类名
// System.out.println(context.getBean(DataConfig.class));//也可以通过类来获取
}
}
代码详解
可以看到BeanConfiguration类的代码和我们传统手动创建对象的代码长得很相似,甚至可以说是单纯做了两个步骤。
第一,将手动创建对象的代码放到一个函数中,并用@Bean标记
第二,在bean外面套了个配置类的壳,且配置类用@Configuration来标记。
第一步可以理解,因为代码量和传统手动创建对象是差不多的,而且还可以通过@Bean的标记让IoC容器来管理这个Bean,从而实现统一管理和自动装配。
但第二步该怎么理解呢?为什么要套这个配置类的壳呢?
首先是因为有了配置类,那么配置类里不仅可以定义多个bean,还可以定义bean之间的依赖关系。同时与其他注解配合使用,譬如与其他注解如@ComponentScan
、@PropertySource
等配合使用,进一步配置应用程序的扫描、属性加载等功能。
所以遵循了这两步的写法才是规范化、工业化的写法。具体展开这两步的步骤就是:
1、定义Bean:使用@Bean
注解,将一个方法声明为一个bean的定义方法,并将其返回值作为bean注册到Spring容器中。
2、声明配置类:@Configuration
标识的类表示一个配置类,它通常用于替代传统的XML配置文件,里面含有Bean的相关定义和其他一些配置。
最后想说的是,黑马的Ioc和AOP这方面讲得是比较一般的,推荐另外看其他人的视频补全。我这里推荐【2022版】2小时学会Spring核心机制IoC和AOP_哔哩哔哩_bilibili