Spring IoC 详解

发布于:2025-04-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

Spring IoC& DI 详解 中对 IoC已经有了介绍,下面对 IoC 进行详细介绍。

IoC,即控制反转,在之前我们编写程序的时候,我们都是自己 new 出来一个对象,然后自己去管理这个对象,但是这有时候有些麻烦,那么我们可以将这些对象交给 Spring 进行管理,就相当于将对象的控制权交给 Spring,即控制反转。那么,我们应该如何将对象交给 Spring 管理呢?

Spring 的一大特点就是使用注解,在这里也是一样,使用类注解或者方法注解将对象交给 Spring 去管理。下面来一一介绍这些注解。

一、类注解

分为 @Controller,@Service,@Repository,@Component,@Configuration。

既然是类注解,就是指定在类上面的。

相同点:

这几个注解在使用方面没有太大区别,现使用 @Component 来进行讲解。

现有一个 StudentComponent 类,如下:

@Component
public class StudentComponent {
    public void method() {
        System.out.println("component");
    }
}

现在我们想要拿到这个类的一个对象,就需要使用如下方法:

@SpringBootApplication
public class SpringBootDemo2025416Application {

	public static void main(String[] args) {

		//获取 Spring 上下文对象
		ApplicationContext context = SpringApplication.run(SpringBootDemo2025416Application.class, args);

		//根据类型获取
		StudentComponent student1 = context.getBean(StudentComponent.class);
		student1.method();

		//根据名称获取,需要进行类型转换
		StudentComponent student2 = (StudentComponent) context.getBean("studentComponent");
		student2.method();

		//根据类型和名曾获取
		StudentComponent student3 = context.getBean("studentComponent", StudentComponent.class);
		student1.method();
	}

}

这个类是 Spring 的启动类,我们在 conten 中获取我们交给 Spring 进行管理的对象。

代码运行结果如下:

通过上面的例子,我们可以知道,从 Spring 中获取对象的方式有三种,即:根据类型获取、根据对象名称获取、根据类型和对象名称获取。

在这里我们需要注意,我们并没有给这个对象指定名称,但为什么还能通过名称获取到这个对象呢?

在 Spring 中,会根据类名分配一个默认的对象名,规则如下:

1)类的首字母是大写的,其后的字母是小写的,如 StudentComponent,那么默认的对象名就是 studentComponent;

2)类的前两个首字母都是大写的,如 ABComponent,那么默认的对象名就是类名本身;

针对上面获取对象的方法,如果有多个对象,那么 Spring 就无法区分需要获取的是哪个对象,那么就会报错,这是我们就需要指定对象名称,这在后面的 @Bean 注解会进行讲解。

这里我们需要思考一下,上面的三个对象是同一个对象吗,还是不同的对象?我们可以将这三个对象的地址打印出来,结果如下:

从上图可以看出,这三个对象是同一个对象。

如果我们去掉 @Component 注解,会发生什么呢?

代码运行结果如下:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.gjm.demo.component.StudentComponent' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1261)
	at com.gjm.demo.SpringBootDemo2025416Application.main(SpringBootDemo2025416Application.java:17)

这是错误信息,异常为 NoSuchBeanDefinitionException,即在容器中找不到 StudentComponent 对象。这是因为由于我们要从容器中去获取一个对象,这就需要容器中必须得先存在这个对象才能去获取,加了 @Component 注解就相当于将这个对象放到了容器中,这个我们才能在容器中获取到这个对象。

不同点:

在上面我们说了这五个注解的相同用法,接下来开始介绍这五个注解的不同点。

@Controller 用于控制层;

@Service 用于业务逻辑层;

@Repository 用于数据层;

@Configuration 用于配置层;

@Component 用于组件层。

在实际应用中,这五个注解的边界并不是很清晰,虽然可以混用,但不同的注解表示这个类的不同的涵义,最好按照规定使用注解。

但是 @Controller 不能与其他注解提婚,这个注解是进行前后端交互用的,其他的注解都没有这个功能。

接下来我们看一下这五个注解的源码:

@Controller

@Service

@Repository

@Configuration

@Component

通过查看源码可以知道,@Controller, @Service,@Repository,@Configuration这四个注解中均含有 @Component 注解,即这四个注解均是 @Component 的衍生注解。

二、方法注解 

通过上面的介绍我们可以看出,无论我们怎么获取对象,这些对象均是同一个对象,但如果我们有多个对象呢,上面的注解就不管用了。下面介绍一个方法注解 @Bean。

既然是方法注解,那么就是指定在方法上的。

接下来来看一段代码:

public class StudentComponent {

    @Bean
    public Student s1() {
        return new Student("zhangsan", 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }
}


@SpringBootApplication
public class SpringBootDemo2025416Application {

	public static void main(String[] args) {

		//获取 Spring 上下文对象
		ApplicationContext context = SpringApplication.run(SpringBootDemo2025416Application.class, args);


		Student student1 = (Student)context.getBean("s1");
		System.out.println(student1);

		Student student2 = (Student)context.getBean("s2");
		System.out.println(student2);

	}

}

当我们运行这段代码后,发现报错了,错误代码如下:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 's1' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:895)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1362)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1243)
	at com.gjm.demo.SpringBootDemo2025416Application.main(SpringBootDemo2025416Application.java:18)

里面说没有发现与一个名为 s1 的对象,那为什么我们加了 @Bean 注解,却说没有这个对象呢?

因为 @Bean 需要搭配类注解来使用,即在 StudentComponent 上面还需要加上一个类注解,上述的五大注解均可以使用,这里我么选择 @Component。

改动过的代码如下:

@Component
public class StudentComponent {

    @Bean
    public Student s1() {
        return new Student("zhangsan", 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }
}

@SpringBootApplication
public class SpringBootDemo2025416Application {

	public static void main(String[] args) {

		//获取 Spring 上下文对象
		ApplicationContext context = SpringApplication.run(SpringBootDemo2025416Application.class, args);


		Student student1 = (Student)context.getBean("s1");
		System.out.println(student1);

		Student student2 = (Student)context.getBean("s2");
		System.out.println(student2);

	}

}

接下来就可以获取到 s1 与 s2 这两个对象了,下面是代码的运行结果:

下面来看一下 @Bean 的源码:

我们可以知道,这里面有两个属性,value 和 name,这两个属性表示的含义均为对象的名称,即我们可以自定义对象的名称,这个名称可以是一个字符串,也可以是一个字符串数组,代码如下:

    @Bean({"s3", "s4"})
    @Bean("s3")
    public Student s1() {
        return new Student("zhangsan", 12);
    }

 这里写了两个 @Bean,在实际情况下只能只用一个。现针对 @Bean("s3") 进行讲解。

当我们将对象名称改了之后,若我们还是按照原来默认的名字去获取,代码的运行结果如下:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 's1' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:895)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1362)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1243)
	at com.gjm.demo.SpringBootDemo2025416Application.main(SpringBootDemo2025416Application.java:18)

代码运行错误,错误信息里说没有找到一个对象名称叫做 s1 的,因为当我们将对象名改名后,原本的对象名就无法使用,必须使用改动之后的名称,下面使用 s3 来获取对象,代码运行结果如下:

结果运行成功。 

但是在 s1 中,我们对 student 的属性是直接赋值的,当然,也可以通过传递参数来赋值,代码如下:

@Component
public class StudentComponent {

//    @Bean({"s3", "s4"})
    @Bean("s3")
    public Student s1(String name) {
        return new Student(name, 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }
}

但是,我们在 getBean 方法中,并没有找到一个方法可以给对象传递参数,那么应该怎么给 name 赋值呢?

在这种情况下,当我们将对象交给 Spring 进行管理,那么 Spring 就会在 Spring 容器中查找 String 类型的对象,并将对象赋给 name,代码如下:

@Component
public class StudentComponent {

//    @Bean({"s3", "s4"})
    @Bean("s3")
    public Student s1(String name) {
        return new Student(name, 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }

    //给 s1 传递参数
    @Bean
    public String name() {
        return "zhangsan";
    }
}

运行结果如下:

通过结果我们可以看到,已经成功给 name 赋值了。

但是在一个程序中,可能会有多个 name 对象,那么 Spring 会使用哪个对象给 name 赋值呢?

代码如下:

@Component
public class StudentComponent {

//    @Bean({"s3", "s4"})
    @Bean("s3")
    public Student s1(String name) {
        return new Student(name, 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }

    @Bean
    public String name() {
        return "zhangsan";
    }
}


@Configuration
public class StudentConfiguration {
    @Bean
    public String name() {
        return "zhangsan111";
    }
}

答案是会报错,报错信息如下:

Description:

The bean 'name', defined in class path resource [com/gjm/demo/config/StudentConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/gjm/demo/component/StudentComponent.class] and overriding is disabled.

 由于有两个相同i名字的 name 对象,Spring不允许有这种情况出现,就会报错。

那如果将两个 name 方法的名字修改为不同的名字,那么程序是否能成功运行?

代码如下:

@Component
public class StudentComponent {

//    @Bean({"s3", "s4"})
    @Bean("s3")
    public Student s1(String name) {
        return new Student(name, 12);
    }

    @Bean
    public Student s2() {
        return new Student("lisi", 14);
    }

    //修改名字为 name1
    @Bean
    public String name1() {
        return "zhangsan";
    }
}


@Configuration
public class StudentConfiguration {
    //修改名字为 name2
    @Bean
    public String name2() {
        return "zhangsan111";
    }
}

运行结果如下:

Description:

Parameter 0 of method s1 in com.gjm.demo.component.StudentComponent required a single bean, but 2 were found:
	- name1: defined by method 'name1' in class path resource [com/gjm/demo/component/StudentComponent.class]
	- name2: defined by method 'name2' in class path resource [com/gjm/demo/config/StudentConfiguration.class]

程序依然会报错,这是因为 Spring 只需要一个 Spring类型的数据,但是我们却提供了两个,那么这时 Spring 就不知道需要使用哪一个了。


网站公告

今日签到

点亮在社区的每一天
去签到