Spring 是包含了众多工具方法的IoC容器。IoC 是Spring的核心思想,例如前面章节所示:在类上面添加@RestController 和 @Controller 注解,,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。
IoC :Inversion of Control (控制反转),也就是说Spring是一个"控制反转"的容器。控制反转也就是 控制权 反转。 获得依赖对象的过程被反转了,也就是说,当需要某个对象时,传统开发模式中需要自己通过new创建对象,现在不需要再进行创建,把创建对象的任务交给容器,程序中只需要依赖注入 (DependencyInjection,DI)就可以了。 这个容器称为:IoC容器。Spring是⼀个IoC容器,所以有时Spring也称为Spring容器。
下面进行 IoC 案例说明
实现下面的需求:从右向左依次依赖。

按照每一个模块设计一个类,代码如下:
public class Car {
private Framework framework;
public Car(){
framework = new Framework();
System.out.println("car 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;
public Tire(){
this.size = 17;
System.out.println("size : " + size);
}
}这样设计的可维护性太低,耦合度太高,当需求变更时,对上述代码修改起来会非常麻烦,当修改尺寸时,修改如下:

从上述得出,当底层代码改动后,整个链上的所有代码都需要进行修改。
基于上述方案,将程序进行改造,将创建子类的方式改为注入传递的方式,代码如下:
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 Tire {
private int size;
public Tire(int size){
this.size = size;
System.out.println("size : " + size);
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire){
this.tire = tire;
System.out.println("Bottom init...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom){
this.bottom = bottom;
System.out.println("Framework init...");
}
}
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...");
}
}经过以上调整,无论底层类中如何变化,整个调用链不用做任何变化,就完成了代码之间的解耦合,使得更加灵活。这就是IoC思想。
IoC容器也就是控制反转器。IoC 容器具有以下优点:
DI: DependencyInjection(依赖注入)
容器在运行期间,动态的为应用程序提供运行时所依赖的资源,称之为依赖注入。
上述示例代码就是通过构造函数的方式,将依赖对象注入到需要使用的对象中的。
IoC 是一种思想,也是"目标",而思想只是一种指导原则,最终还是要有可行的落地方案,而DI就属于
具体的实现。所以也可以说,DI是IoC的一种实现。
Spring 框架为了更好的服务Web应用程序,提供了更丰富的注解。
共有两类注解类型:
使用@Controller 注解存储Bean ,代码如下:
@Controller
public class UserController {
public void sayHello(){
System.out.println("do controller..");
}
}加上注解后,此时这个对象就已经存储在Spring容器中了。
通过获取Spring上下文对象:发现成功获取到Controller对象
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserController bean = context.getBean(UserController.class);
System.out.println(bean);
}
}

可以根据名称来获取bean,那么bean 命名的规则是? 五大类注解让Spring管理Bean对象的默认取名方式如下:
使用@Service 注解存储bean代码如下,获取bean对象方式不变:
@Service
public class UserService {
public void doService(){
System.out.println("do service...");
}
}使用@Repository 注解存储bean代码如下,获取bean对象方式不变:
@Repository
public class UserRepository {
public String doRepository(){
return "do repository...";
}
}使用@Component 注解存储bean代码如下,获取bean对象方式不变:
@Component
public class UserComponent {
public String doComponent(){
return "do component...";
}
}使用@Configuration 注解存储bean代码如下,获取bean对象方式不变:
@Configuration
public class UserConfig {
public String doConfig(){
return "do config...";
}
}为什么要用这么多类注解呢,既然都是将对象交给Spring容器,使用哪个注解功能不是都一样吗,这样使用不同的注解是为了什么?
使用这么多类注解是为了应用的分层,当看到这些不同的注解,就能直接了解当前类的用途:
@Controller:控制层,接收请求,对请求进行处理,并进行响应.@Service:业务逻辑层,处理具体的业务逻辑.@Repository:数据持久层,也称为持久层.负责数据访问操作@Configuration:配置层.处理项目中的一些配置信息.@Controller,@Service,@Repository,@Configuration这四个注解都是@Component注解的衍生注解,可以理解为是父子关系。
类注解是添加到某个类上,但是类注解存在问题:
此时需要使用方法注解 @Bean
使用示例:
@Configuration
public class BeanConfig {
@Bean
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
}@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserInfo bean1 = context.getBean("user1",UserInfo.class);
System.out.println(bean1);
}
}
可以通过设置name属性给Bean对象进行重命名操作,如下代码示例:
@Configuration
public class BeanConfig {
@Bean(name = {"u1","userInfo"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
}此时可以使用 u1 获取UserInfo 对象:
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserInfo bean = (UserInfo) context.getBean("u1");
System.out.println(bean);
}
}
重命名代码也可以进行省略:@Bean({"u1","userInfo"}),@Bean("u1")
使用五大注解声明的Bean,生效必须配置扫描路径,让Spring扫描到这些注解,也就是通过 @ComponentScan 来配置这些路径。
但是默认扫描范围是SpringBoot 启动类所在的包及其子包,因此将启动类 SpringIocDemoApplication 放在希望扫描的包的路径下,那么定义的Bean 就都可以被扫描到。
依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。简单来说,就是把对象取出来放到某个类的属性中。
关于依赖注入,Spring 提供了三种方式:
属性注入使用 @Autowired 实现,示例:
@Service
public class UserService {
@Autowired
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}
构造方法注入是在类的构造方法中实现注入,示例:
@Controller
public class UserController {
//构造方法注入
private UserService userService;
public UserController(){
}
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
public void sayHello(){
userService.doService();
System.out.println("do controller..");
}
}Setter 注入和属性的 Setter 方法实现类似,只是在set方法时加上 @Autowired 注解,示例:
@Controller
public class UserController {
//Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHello(){
userService.doService();
System.out.println("do controller..");
}
}当同一类型存在多个bean时,使用@Autowired 会存在问题:
@Configuration
public class BeanConfig {
@Bean({"u1"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
@Bean({"u2"})
public UserInfo user2(){
return new UserInfo("lisi",20);
}
}@Service
public class UserService {
@Autowired
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
UserService bean = context.getBean(UserService.class);
bean.doService();
}
}此时,当有两个userInfo 对象时,获取上下文发现启动失败,信息如下:

报错原因是需要一个单一对象,但是找到了两个,因此Spring 提供了以下几种解决方案。
使用 @Primary 注解:当存在多个相同类型的Bean 注入时,加上 @Primary 注解,来确定默认Bean的实现。示例:
@Configuration
public class BeanConfig {
@Bean({"u1"})
public UserInfo user1(){
return new UserInfo("zhangsan",17);
}
@Bean({"u2"})
@Primary
public UserInfo user2(){
return new UserInfo("lisi",20);
}
}此时默认获取到的对象就是 user2 。
使用@Qualifier 注解:指定当前要注入的Bean对象。在其 value 属性中,指定注入bean对象的名称。
@Service
public class UserService {
@Autowired
@Qualifier("u1")
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}此时获取到对象为 user1。
使用@Resource 注解:是按照Bean 的名称进行注入。通过name属性指定要注入的bean 的名称。示例:
@Service
public class UserService {
@Resource(name = "u2")
private UserInfo userInfo;
public void doService(){
System.out.println(userInfo);
System.out.println("do service...");
}
}此时获取到的对象为 user2。