在 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 就不知道需要使用哪一个了。