Java EE 进阶:5. Spring IoC&DI(第二弹):Spring IoC

目录

[3.IoC 详解](#3.IoC 详解)

[3.1 Bean 的存储](#3.1 Bean 的存储)

[3.1.1 @Controller(控制器存储)](#3.1.1 @Controller(控制器存储))

[3.1.2 @Service(服务存储)](#3.1.2 @Service(服务存储))

[3.1.3 @Repository(仓库存储)](#3.1.3 @Repository(仓库存储))

[3.1.4 @Component(组件存储)](#3.1.4 @Component(组件存储))

[3.1.5 @Configuration(配置存储)](#3.1.5 @Configuration(配置存储))

[3.2 为什么要这么多注解?](#3.2 为什么要这么多注解?)

[3.3 方法注解@Bean](#3.3 方法注解@Bean)

[3.3.1 定义多个对象](#3.3.1 定义多个对象)

3.3.2方法注解要配合类注解使用

[3.3.3 重命名 Bean](#3.3.3 重命名 Bean)

小结

[3.3.4 额外补充:传参](#3.3.4 额外补充:传参)

[3.4 扫描路径](#3.4 扫描路径)


书接上文:Java EE 进阶:5. Spring IoC&DI(第一弹):Spring IoC~~

3.IoC 详解

我们前面说 Spring 作为一个容器,是管理对象的,一个存一个取~~

通过上面的案例, 我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作.

前面我们提到 IoC 控制反转,就是将对象的控制权交给Spring的IOC容器,由 IOC 容器创建及管理对象。

也就是bean的存储.

3.1 Bean 的存储

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加一个注解: @Component

而Spring框架为了更好的服务web应用程序, 提供了更丰富的注解.

共有两类注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.

  2. 方法注解:@Bean.

接下来我们分别来看

3.1.1 @Controller(控制器存储)

使用 @Controller 存储 bean 的代码如下所示:

java 复制代码
package com.bit.ioc.controller;

import org.springframework.stereotype.Controller;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 17:00
 */
@Controller//将对象存储在Spring中
public class HelloController {
    public void print(){
        System.out.println("do Controller");
    }
}

如何观察这个对象已经存在Spring容器当中了呢?

接下来我们学习如何从Spring容器中获取对象

java 复制代码
package com.bit.ioc;

import com.bit.ioc.controller.HelloController;
import lombok.val;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);
        //接下来拿到存的 HelloController
        HelloController bean = context.getBean(HelloController.class);
        bean.print();

//        //没有Spring应该这么写:
//        HelloController controller = new HelloController();
//        controller.print();
    }
}

我们发现它的父类是 ApplicationContext,其中 Application→应用,Context→上下文可以理解为 Spring 的上下文→Spring 的运行环境,Spring 帮我们管理的对象就在这里面放着呢,当然,也包括一些环境变量、配置、数据......

ApplicationContext 翻译过来就是: Spring 上下文
因为对象都交给 Spring 管理了,所以获取对象要从 Spring 中获取,那么就得先得到 Spring 的上下文
关于上下文的概念
上学时, 阅读理解经常会这样问: 根据上下文, 说一下你对XX的理解
在计算机领域, 上下文这个概念, 咱们最早是在学习线程时了解到过, 比如我们应用进行线程切换的时候,切换前都会把线程的状态信息暂时储存起来,这里的上下文就包括了当前线程的信息,等下次该线程又得到CPU时间的时候, 从上下文中拿到线程上次运行的信息
这个上下文, 就是指当前的运行环境, 也可以看作是一个容器, 容器里存了很多内容, 这些内容是当前运行的环境

Bean 的名称就和学号一样,是唯一的

运行后观察日志,发现确实已经从容器中获取到这个对象了👇

获取bean对象的其他方式

上述代码是根据类型来查找对象, 如果Spring容器中, 同一个类型存在多个bean的话, 怎么来获取呢?

ApplicationContext 也提供了其他获取bean的方式, ApplicationContext 获取bean对象的功能, 是父类BeanFactory提供的功能.

java 复制代码
public interface BeanFactory {

    // 1. 根据bean名称获取bean
    Object getBean(String var1) throws BeansException;

    // 2. 根据bean名称和类型获取bean
    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    // 3. 按bean名称和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的bean
    Object getBean(String var1, Object... var2) throws BeansException;

    // 4. 根据类型获取bean
    <T> T getBean(Class<T> var1) throws BeansException;

    // 5. 按bean类型和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的bean
    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

}

常用的是上述1,2,4种, 这三种方式,获取到的bean是一样的

其中1,2种都涉及到根据名称来获取对象. bean的名称是什么呢?
Spring bean是Spring框架在运行时管理的对象, Spring会给管理的对象起一个名字.
比如学校管理学生, 会给每个学生分配一个学号, 根据学号, 就可以找到对应的学生.
Spring也是如此, 给每个对象起一个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.

Bean 命名约定

我们看下官方文档的说明: Bean Overview :: Spring Framework

程序开发人员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId),Spring容器将为该 bean生成唯一的名称.

命名约定使用Java标准约定作为实例字段名. 也就是说,bean名称以小写字母开头,然后使用驼峰式大小写.

比如
类名: UserController, Bean的名称为: userController
类名: AccountManager, Bean的名称为: accountManager
类名: AccountService, Bean的名称为: accountService

也有一些特殊情况, 当有多个字符并且第一个和第二个字符都是大写时, 将保留原始的大小写. 这些规则与java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同.

比如

类名: UController, Bean的名称为: UController
类名: AManager, Bean的名称为: AManager

根据这个命名规则, 我们来获取Bean.

java 复制代码
package com.bit.ioc;

import com.bit.ioc.controller.HelloController;
import lombok.val;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);
        //接下来拿到存的 HelloController
        //方式一:根据类型获取对象
        HelloController bean1 = context.getBean(HelloController.class);
        bean1.print();

//        //没有Spring应该这么写:
//        HelloController controller = new HelloController();
//        controller.print();
        //方式二:根据名称获取对象
        HelloController bean2=(HelloController) context.getBean("helloController");
        bean2.print();
        //方式三:根据名称和类型获取对象
        HelloController bean3=(HelloController) context.getBean("helloController",HelloController.class);
        bean3.print();

        System.out.println(bean1);
        System.out.println(bean2);
        System.out.println(bean3);
    }
}

运行结果:发现都能够成功获取对象并打印~~

地址相同,说明是同一个对象

答疑:有第一种不就好了吗??第二种还得强转,第三种还得写那么长??

第一种是根据类型来获取对象的,那么如果这个类型有多个对象,你咋办??

好比老师点名,巧了,有个重名的~~

所以第一种方式的使用场合只适用于:该对象类型的对象只有一个,一旦有多个,这种方式就出错了

第二种方式肯定不会重复,但保不齐就可能名字写错~~

第三种方式就需要都匹配上~~

当前咱们这种创建对象的方式只会创建一个对象,创建多个对象我们还没讲到~~

获取bean对象, 是父类BeanFactory提供的功能
ApplicationContext VS BeanFactory(常见面试题)
• 继承关系和功能方面来说:Spring 容器有两个顶级的接口:BeanFactory 和
ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能力,而
ApplicationContext 属于 BeanFactory 的子类,它除了继承了 BeanFactory 的所有功能之外,
它还拥有独特的特性,还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持.
• 从性能方面来说:ApplicationContext 是一次性加载并初始化所有的 Bean 对象,而
BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)

如果把@Controller删掉, 再观察运行结果

报错信息显示: 找不到类型是: com.example.demo.controller.UserController的bean

闲聊:学会看日志~~日志发生改变了说明上一个问题解决了,比如下面这行代码(@Controller 还是不加),只是把 bean1 的内容全部注释掉~~

java 复制代码
package com.bit.ioc;

import com.bit.ioc.controller.HelloController;
import lombok.val;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);
        //接下来拿到存的 HelloController
//        //方式一:根据类型获取对象
//        HelloController bean1 = context.getBean(HelloController.class);
//        bean1.print();

//        //没有Spring应该这么写:
//        HelloController controller = new HelloController();
//        controller.print();
        //方式二:根据名称获取对象
        HelloController bean2=(HelloController) context.getBean("helloController");
        bean2.print();
        //方式三:根据名称和类型获取对象
        HelloController bean3=(HelloController) context.getBean("helloController",HelloController.class);
        bean3.print();

//        System.out.println(bean1);
        System.out.println(bean2);
        System.out.println(bean3);
    }
}

此时我们发现虽然还是 找不到 Bean ,但是错误日志后面的内容已经改变了👇

3.1.2 @Service(服务存储)

使用 @Service 存储 bean 的代码如下所示:

java 复制代码
package com.bit.ioc.service;

import org.springframework.stereotype.Service;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 20:03
 */
@Service//此时也是交给Spring管理了
public class UserService {
    public void print(){
        System.out.println("do Service");
    }
}

读取 bean 的代码:

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);

        UserService bean=context.getBean(UserService.class);
        bean.print();
    }
}

观察运行结果, 发现成功从Spring中获取到UserService对象, 并执行UserService的 print 方法

同样的, 把注解@Service删掉, 再观察运行结果

发现报的错误跟之前一样,找不到 Bean 的类型~~

3.1.3 @Repository(仓库存储)

使用 @Repository 存储 bean 的代码如下所示:

java 复制代码
package com.bit.ioc.resp;

import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 20:11
 */
@Repository//此时也是交给Spring管理了
public class UserRepository {
    public void print(){
        System.out.println("do Repository");
    }
}

读取 bean 的代码:

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);

        UserRepository bean=context.getBean(UserRepository.class);
        bean.print();
    }
}

观察运行结果, 发现成功从Spring中获取到UserRepository 对象, 并执行UserRepository 的 print 方法

同样的, 把注解@Repository删掉, 再观察运行结果

发现报的错误跟之前一样,找不到 Bean 的类型~~

3.1.4 @Component(组件存储)

使用 @Component 存储 bean 的代码如下所示:

java 复制代码
package com.bit.ioc.component;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 20:17
 */
@Component//此时也是交给Spring管理了
public class UserComponent {
    public void print(){
        System.out.println("do Component");
    }
}

读取 bean 的代码:

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context=SpringApplication.run(SpringIocDemoApplication.class, args);

        UserComponent bean=context.getBean(UserComponent.class);
        bean.print();
    }
}

观察运行结果, 发现成功从Spring中获取到UserComponent 对象, 并执行UserComponent 的 print 方法

同样的, 把注解@Component删掉, 再观察运行结果

发现报的错误跟之前一样,找不到 Bean 的类型~~

3.1.5 @Configuration(配置存储)

使用 @Configuration 存储 bean 的代码如下所示:

java 复制代码
package com.bit.ioc.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 20:22
 */
@Configuration//此时也是交给Spring管理了
public class UserConfig {
    public void print(){
        System.out.println("do Configuration");
    }
}

读取 bean 的代码:

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        UserConfig bean = context.getBean(UserConfig.class);
        bean.print();
    }
}

观察运行结果, 发现成功从Spring中获取到UserConfiguration 对象, 并执行UserConfiguration 的 print 方法

同样的, 把注解@Configuration删掉, 再观察运行结果

发现报的错误跟之前一样,找不到 Bean 的类型~~
这就是我们的五大注解~~

这五大注解的作用就是告诉 Spring 帮我们管理对象,当我们标了这五个注解之后,我们的 Spring 就会去帮我们创建一个对象,然后放到 Spring 容器中~~

当然我们也可以给它指定名称,它默认的名称是类名的小驼峰写法,我们也可以去指定名称,比如指定为:hello👇(下面以 Controller 为例,其他四个跟这个一摸一样~~)

java 复制代码
package com.bit.ioc.controller;

import org.springframework.stereotype.Controller;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CoderYanger
 * Date: 2026-10-28
 * Time: 17:00
 */
@Controller("hello")
public class HelloController {
    public void print(){
        System.out.println("do Controller");
    }
}

比较一下加和不加的差异👇下面是没加的

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        HelloController bean1=(HelloController) context.getBean("helloController");
        bean1.print();

        UserService bean2=context.getBean(UserService.class);
        bean2.print();

        UserRepository bean3=context.getBean(UserRepository.class);
        bean3.print();

        UserComponent bean4 = context.getBean(UserComponent.class);
        bean4.print();

        UserConfig bean5 = context.getBean(UserConfig.class);
        bean5.print();
    }
}

我们发现对于 Controller,当类名为 HelloController 时,获取 helloController 是可以获取成功的👇

也就说默认的名称是类名的小驼峰写法,接下来测试一下

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        HelloController bean1=(HelloController) context.getBean("helloController");
        bean1.print();

        UserService bean2=context.getBean(UserService.class);
        bean2.print();

        UserRepository bean3=context.getBean(UserRepository.class);
        bean3.print();

        UserComponent bean4 = context.getBean(UserComponent.class);
        bean4.print();

        UserConfig bean5 = context.getBean(UserConfig.class);
        bean5.print();

        HController bean6=(HController) context.getBean("hController");
        bean6.print();
    }
}

我们发现是拿不到的👇

那如果把小写改为大写("h" 改为 "H")👇我们发现就可以了

那如果我们觉得这个名称不好,我们想改个名称的话,就这么写(比如写个 aaa)👇

此时发现原本能找到的,但是把名字改成 aaa 之后就找不到了~~

此时我们改成 aaa 的话就可以拿到👇

java 复制代码
        HController bean6=(HController) context.getBean("aaa");
        bean6.print();

这也就证实了:Bean 的名称默认是小驼峰写法,如果说前两位都为大写,那么 Bean 的名称就是它自己,当然我们也可以通过注解来进行指定(这五种注解指定的方式都一样),这时 Spring 管理的时候就会以我们指定的名称去定义好~~

3.2 为什么要这么多注解?

这个也是和咱们前面讲的应用分层是呼应的. 让程序员看到类注解之后,就能直接了解当前类的用途.

@Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.

@Servie:业务逻辑层, 处理具体的业务逻辑.

@Repository:数据访问层,也称为持久层. 负责数据访问操作

@Configuration:配置层. 处理项目中的一些配置信息.

@Component:组件层

下面来测试一下 @Controller 和 @Service 的区别👇

java 复制代码
@ResponseBody
@Controller
//@Service
public class UserController {
    @RequestMapping("/hello")
    public String hello(){
        return "测试Controller和Service区别";
    }
}

启动服务后我们发现是可以正常响应的👇

接下来把 @Controller 去掉,换成 @Service👇

java 复制代码
@ResponseBody
//@Controller
@Service
public class UserController {
    @RequestMapping("/hello")
    public String hello(){
        return "测试Controller和Service区别";
    }
}

重启服务后,我们再来尝试发现就找不到路径了,报了 404👇

类似的,剩下三个注解也是一样的,只有 @Controller 才可以,这里就不再测试了~~

这就与 @Controller 的定义有关,表示的是控制层,接收请求,对请求进行处理,并进行响应,而请求的第一关就是 Controller,所以这个地方只能是 Controller,其他注解不可以

答疑:那其他注解,比如 Service 能不能和其他注解替换??

可以的,比如之前写的 图书管理系统 的代码,Service 层就是使用 @Component 👇

这 5 个注解,@Controller 不可以和其他注解替换,控制层必须使用 @Controller

在不固定场景下,控制层使用 @Service 也可能访问成功,但是不标准,且不稳定,大家直接忘记这个情况

其他注解可以替换,但是不建议,因为不同注解表示含义不同

就像 西安也有一些本地人开的海南的车牌,可能会给人误解,也可能会存在不必要的麻烦~~


这和每个省/市都有自己的车牌号是一样的.
车牌号都是唯一的, 标识一个车辆的. 但是为什么还需要设置不同的车牌开头呢.

比如陕西的车牌号就是:陕X:XXXXXX,北京的车牌号:京X:XXXXXX,甚至一个省不同的县区也是不同的,比如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,一样.

这样做的好处除了可以节约号码之外,更重要的作用是可以直观的标识一辆车的归属地.

程序的应用分层,调用流程如下:

类注解之间的关系

查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现

其实这些注解里面都有一个注解 @Component ,说明它们本身就是属于 @Component 的"子类".也就是 Component 的 衍生注解

比如杯子有喝水杯, 刷牙杯等, 但是我们更倾向于在日常喝水时使用水杯, 洗漱时使用刷牙杯.
更多资料参考:Classpath Scanning and Managed Components :: Spring Framework

但实际应用中,这 5 个注解的边界并没有那么清晰~~

3.3 方法注解@Bean

类注解是添加到某个类上的, 但是存在两个问题:

  1. 使用外部包里的类, 没办法添加类注解

  2. 一个类, 需要多个对象, 比如多个数据源

这种场景, 我们就需要使用方法注解 @Bean

我们先来看看方法注解如何使用:

3.3.1 定义多个对象

Student.java👇

java 复制代码
@NoArgsConstructor//无参构造函数
@AllArgsConstructor//全参构造函数
@Data
public class Student {
    private String name;
    private Integer age;
}

StudentComponent.java👇

java 复制代码
@Component
public class StudentComponent {
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    public Student s2(){
        return new Student("Ayanger",12);
    }
}

启动类 SpringIocDemoApplication.java👇此时我们只能通过 . 的方式去拿到对象,比较麻烦~~

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        Student bean=context.getBean(Student.class);
        System.out.println(bean);
        //此时我们只能通过这个方式去拿到对象
        StudentComponent bean1=context.getBean(StudentComponent.class);
        Student student=bean1.s1();
    }
}

我们希望把 s1 和 s2 也交给 Spring 去进行管理,就可以写成👇

java 复制代码
@Component
public class StudentComponent {
    @Bean
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    @Bean
    public Student s2(){
        return new Student("Ayanger",12);
    }
}

此时我们通过👇的方式去拿对象,不用 . 的方式

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象,直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        //从Spring上下文中获取对象
        Student bean=context.getBean(Student.class);
        //使用对象
        System.out.println(bean);
    }
}

启动服务后运行👇我们观察错误日志

从报错信息中我们发现:我们加上 @Bean 之后就交给 Spring 进行管理了,但是 Spring 管理的时候肯定会给每一个对象起一个名字,而这个名字就是我们的方法名~~

对不对呢??我们来测试一下👇

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        Student bean1=(Student)context.getBean("s1");
        System.out.println(bean1);
        Student bean2=(Student)context.getBean("s2");
        System.out.println(bean2);
    }
}

发现确实能够从 Spring 中拿出来👇

所以 @Bean 就表示把这个方法(s1、s2)定义的方法交给 Spring 进行管理了,我们就可以从容器中直接获取了~~

也就是说,@Bean 可以针对同一个类, 定义多个对象.

3.3.2方法注解要配合类注解使用

在 Spring 框架的设计中,方法注解@Bean 要配合类注解才能将对象正常的存储到 Spring 容器中~~

答疑:那么加上 @Bean 之后还要加上五大注解吗??

必须要加上,如果不加,比如这里把 @Component 去掉👇

看看结果👇

所以@Bean 必须配合五大注解去进行使用,如果不加这五大注解,Spring 根本扫描不到,里面写的代码再咋好都是白搭~~

我们换成 @Service 发现也是可以的,就是交给 Spring 管理,让 Spring 扫描这里👇

java 复制代码
//@Component
@Service
public class StudentComponent {
    @Bean
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    @Bean
    public Student s2(){
        return new Student("Ayanger",12);
    }
}

3.3.3 重命名 Bean

可以通过设置 name 属性给 Bean 对象进行重命名操作,这一点在源码中也有体现👇

我们把 s1 的 Bean 重命名为 s3👇

java 复制代码
@Component
public class StudentComponent {
    @Bean("s3")
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    @Bean
    public Student s2(){
        return new Student("Ayanger",12);
    }
}
java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

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

再来运行,发现已经找不到 s1了👇

再来试试 s3👇

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        Student bean=(Student)context.getBean("s3");
        System.out.println(bean);
    }
}

此时我们发现就能找到了👇

再来看 Bean 的源码部分👇

其中 @AliasFor 的意思是别名

这里 value 属性的别名是 name,name 属性的别名是 value,所以它们两个是一摸一样的东西,后续写 name=...... 或者 value=......都是一样的~~

对于所有的注解,当我们没有去写属性名称的时候,默认为 value 属性,也就是👇

java 复制代码
    @Bean(value="s3")
    public Student s1(){
        return new Student("CoderYanger",21);
    }

又由于 value 等同于 name,所以也可以写成👇

java 复制代码
    @Bean(name="s3")
    public Student s1(){
        return new Student("CoderYanger",21);
    }

又由于在源码中 name 和 value 的类型都是 String\[\],是个数组,所以我们可以给赋多个值,比如👇

java 复制代码
    @Bean(name={"s3","s4"})
    public Student s1(){
        return new Student("CoderYanger",21);
    }

测试一下,发现确实都能获取到👇

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        Student bean1=(Student)context.getBean("s3");
        System.out.println(bean1);
        Student bean2=(Student)context.getBean("s4");
        System.out.println(bean2);
    }
}

得到的是同一个对象~~

name 等同于 value,而 value 又可以省略,所以也可以写成👇

java 复制代码
    @Bean({"s3","s4"})
    public Student s1(){
        return new Student("CoderYanger",21);
    }

那其他注解可不可以也这样??需要看的~~

比如 Component👇

它的类型是 String,而非 String\[\],所以它就不能给多个值~~

小结

五大注解

BeanName 默认为类名的小驼峰写法,如果类名前两位均为大写,BeanName 则为类名本身

@Bean

需要搭配五大注解使用,BeanName 默认为方法名

3.3.4 额外补充:传参

如果我们需要传参👇

java 复制代码
    @Bean
    public Student s2(String name){
        return new Student(name,12);
    }

Spring 会帮我们管理这个对象,管理这个对象就要执行这个方法,但是执行这个方法的话,就需要 String name 这样的参数,但是这样的参数又没有,这时 Spring 就会从 Spring 容器当中查找 String 类型的对象,赋值给 name,如果没有的话就会报错了👇

java 复制代码
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        Student bean=(Student)context.getBean("s2");
        System.out.println(bean);
    }
}

服务都运行不起来👇错误日志写的很清楚:第一个方法 s2 在 com.bit.ioc.component.StudentComponent 这个里面需要有一个 类型为 String,但是没有找到

此时如果我们想注入的话,就需要额外定义👇我们也需要有一个对象交给 Spring 管理

java 复制代码
    @Bean
    private String name(){
        return "Ayanger";
    }
    @Bean
    public Student s2(String name){
        return new Student(name,12);
    }

这样就会把 Ayanger 赋值给 name👇

当然,它的参数也有可能是一个对象👇

java 复制代码
@Component
public class StudentComponent {
    @Bean({"s3","s4"})
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    @Bean
    private String name(){
        return "Ayanger";
    }
    @Bean
    public Student s2(Student s3){
        return new Student(s3.getName(),12);
    }
}

那么此时 它就会在容器中根据 Student 这个类型去拿了👇

答疑:如果另一个类里面又有一个 name,应该注入哪个??

我们来测试一下👇

StudentComponent.java 中有个 Ayanger

java 复制代码
@Component
public class StudentComponent {
    @Bean({"s3","s4"})
    public Student s1(){
        return new Student("CoderYanger",21);
    }
    @Bean
    private String name(){
        return "Ayanger";
    }
    @Bean
    public Student s2(String name){
        return new Student(name,12);
    }
}

UserConfig.java 中有个 Ayanger00

java 复制代码
@Configuration//此时也是交给Spring管理了
public class UserConfig {
    @Bean
    private String name(){
        return "Ayanger00";
    }
    public void print(){
        System.out.println("do Configuration");
    }
}

运行👇发现:@Bean 下不能是 私有的

这个是语法问题,那么我们把这两个方法的 private 改成 public,再来尝试👇

我们发现 Baen 的 name 定义注册不了,因为在路径 com/bit/ioc/component/StudentComponent.class 下已经被定义了,说白了就是 不能重复,如果你想重复的话需要加上一些配置,但是这些配置咱们还没有讲~~

3.4 扫描路径

Q: 使用前面学习的四个注解声明的 bean,一定会生效吗?

A: 不一定(原因:bean想要生效,还需要被Spring扫描)

下面我们通过修改项目工程的目录结构,来测试bean对象是否生效:

还是上面的代码,一行不改,只是移动了启动类的位置,我们却发现报错了👇

解释: 没有 bean 的名称为 s2

为什么没有找到bean对象呢?

使用五大注解声明的bean,要想生效, 还需要配置扫描路径, 让Spring扫描到这些注解(关于 Spring 的扫描路径问题之前有过解释:Java EE 进阶:4.Spring Web MVC 入门(第三弹))

因为 Spring 的默认扫描路径是在 启动类所在路径及其子孙目录,所以我们把启动类挪到所涉及代码同一个包下👇

此时 Spring 就扫描到了👇

那如果说我就想让启动类在 controller 包下,我还不想改代码,咋办??通过@ComponentScan 来配置扫描路径~~

在 @ComponentScan 的源码中写清楚了别名是包的路径~~

当我们希望 Spring 能够扫描到 com.bit.ioc.component 时就可以写在 @ComponentScan 里👇

java 复制代码
package com.bit.ioc.controller;

@ComponentScan("com.bit.ioc.component")
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        Student bean=(Student)context.getBean("s2");
        System.out.println(bean);
    }
}

此时我们再来运行,发现就可以正常启动了👇

也就是说我现在启动类在 controller 中,通过 @ComponentScan 也达到了扫描 component 的效果,但是现在也就只能扫描 component,如果说我还写了一些别的,比如👇

java 复制代码
package com.bit.ioc.controller;

@ComponentScan("com.bit.ioc.component")
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        UserComponent bean1=context.getBean(UserComponent.class);
        bean1.print();
        UserConfig bean2=context.getBean(UserConfig.class);
        bean2.print();
        
        Student bean=(Student)context.getBean("s2");
        System.out.println(bean);
    }
}

此时又说 Config 找不到👇

也就是说 bean2 找不到,但是 bean1 找到了,这是因为 UserComponent 就在 component 的包里。而 UserConfig 不在👇

如果我们希望把所有的都识别了,那么我可以把扫描路径稍微写的再粗一点👇

java 复制代码
@ComponentScan("com.bit.ioc.component")
//↓↓↓
@ComponentScan("com.bit.ioc")

此时的扫描路径就是 com.bit.ioc 及其子目录了,我们来看结果👇发现就执行成功了

一般情况下我们是不用 @ComponentScan 这个注解的,直接把启动类放在最外层了,这样就能保证都能扫描到了

答疑:

Q1:这个注解可以传数组吗??

可以的,我们直接来看源码👇

找到别名为 value ,发现其类型是 String\[\],是一个数组,说明是可以配置多个路径的,跟 Bean 的一样👇

java 复制代码
package com.bit.ioc.controller;
@ComponentScan({"com.bit.ioc.component","com.bit.ioc.service"})
@SpringBootApplication
//加上这个注解之后,不管这个类名叫什么,它都是一个启动类
public class SpringIocDemoApplication {

    public static void main(String[] args) {
        //直接使用父类接收
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        UserComponent bean1=context.getBean(UserComponent.class);
        bean1.print();
        UserConfig bean2=context.getBean(UserConfig.class);
        bean2.print();

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

我们都知道,既然是一个数组,Java 中数组都要用 大括号{} 括起来,那为什么这里只加一个路径的时候可以省略大括号呢??因为这是注解~~

Q2:那为什么扫描路径是启动类所在的??

来看源码👇

我们发现 SpringBootApplication 里面就已经包括了 @ComponentScan 这个注解,默认值就是 @ComponentScan 所在路径,也就是 SpringBootApplication 所在路径了~~

推荐做法:

把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到

相关推荐
aiks_yuan1 小时前
Spring Boot 4 注入 ObjectMapper 报找不到 Bean:升级 Jackson 3 的改法和过渡方案(4.1.2 实测)
spring boot
青峰讲运维2 小时前
OpenStack Heat 编排:用模板一键交付云业务,告别手动点页面
开发语言·php·openstack
Rain的Java大神之路2 小时前
面试被LeetCode 27(移除元素)问懵?别再用两层 for 循环了:一道“简单题”吃透双指针原地删除,附面试加分话术 + 11 个避坑清单
后端·leetcode·面试
悟天特斯2 小时前
安防一体化:从“各管各的“到“全局联动“
java·大数据·网络·人工智能·物联网
70后写java的老码农2 小时前
Spring Boot 3.x + ShardingSphere 5.x + MyBatis-Plus 3.x:订单分库分表实战(含两个踩坑记录)
java
MyFreeIT2 小时前
Python Windows Manual
开发语言·python
霸道流氓气质2 小时前
AI 翻译插件:翻译记忆级联复用、术语混合检索与结构化质量门禁的 Vue3 + Spring AI 生产级实战
java·人工智能·spring
斯维赤2 小时前
OCR到底是接入第三方API 还是自己封装?
java·ocr
kiracrimson2 小时前
STL:List的使用和模拟实现
开发语言·c++·list