当前位置: 首页 > news >正文

总结 Spring 中存储 Bean 的相关注解以及这些注解的用法.

目录

1 Bean的存储

1.1 @Controller(控制器存储)

1.2 @Service(服务存储)

1.3 @Repository(仓库存储) 

1.4 @Component(组件存储)

1.5 @Configuration(配置存储)

2 为什么要这么多类注解?

3 ⽅法注解@Bean

3.1 ⽅法注解要配合类注解使⽤

3.2 定义多个对象

3.3 Bean传递参数

3.4 重命名Bean

4 扫描路径

5 总结


在了解Bean的存储之前, 需要先知道IOC这个思想, IOC就是将控制权反转, 将创建依赖对象的权利交给Spring, 而不是原来的使用方对象来创建依赖对象了. 那么将创建对象的控制权交给Spring的IOC容器后, 就由IOC容器来创建并管理对象, 这也就是Bean的存储

1 Bean的存储

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

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

2. ⽅法注解:@Bean.

1.1 @Controller(控制器存储)

使⽤@Controller 存储bean的代码如下所⽰:

@Controller //将对象存储在Spring中
public class UserController {
   
    public void doController() {
        System.out.println("doController...");
    }
}

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

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

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        // 获取Spring上下文
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
        // 从Spring上下文中获取对象
        UserController bean = context.getBean(UserController.class);
        // 使用对象
        bean.doController();
    }
}

观察运⾏结果,发现成功从Spring中获取到Controller对象,并执⾏了Controller的doController方法.

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

也就是Spring找不到这个对象, 因为没有将这个bean存储在Spring中.

获取bean对象的其他⽅式

上述代码是根据类型来查找对象,如果Spring容器中, 同⼀个类型存在多个bean的话, 怎么来获取呢? ApplicationContext 也提供了其他获取bean的⽅式, ApplicationContext获取bean对象的功能, 是⽗ 类BeanFactory提供的功能

常用的获取bean的方式是通过bean的名称获取, bean的类型获取, bean的名称和类型一起获取.

这其中涉及到了bean的名称来获取对象, 那么bean的名称是什么呢?

Spring bean是Spring框架在运⾏时管理的对象, Spring会给管理的对象起⼀个名字.

⽐如学校管理学⽣, 会给每个学⽣分配⼀个学号, 根据学号, 就可以找到对应的学⽣. Spring也是如此, 给每个对象起⼀个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.

程序开发⼈员不需要为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.

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
        //从Spring上下⽂中获取对象
        //根据bean类型,从Spring上下⽂中获取对象
        UserController userController1 = context.getBean(UserController.class);
        userController1.doController();
        //根据bean名称,从Spring上下⽂中获取对象
        UserController userController2 = (UserController) context.getBean("userController");
        userController2.doController();
        //根据bean名称 + 类型,从Spring上下⽂中获取对象
        UserController userController3 = context.getBean("userController", UserController.class);
        userController3.doController();

        System.out.println(userController1);
        System.out.println(userController2);
        System.out.println(userController3);
    }
}

 地址⼀样, 说明对象是同一个.

1.2 @Service(服务存储)

使⽤@Service 存储bean的代码如下所⽰:

@Service
public class UserService {
    public void doService() {
        System.out.println("do Service...");
    }
}

读取bean的代码:

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
        UserService userService = context.getBean(UserService.class);
        userService.doService();
    }
}

1.3 @Repository(仓库存储) 

使⽤ @Repository 存储bean的代码如下所⽰:

@Repository
public class UserRepository {
    public void doRepository() {
        System.out.println("do Repository...");
    }
}

读取bean的代码和上面的基本差不多.

1.4 @Component(组件存储)

使⽤@Component 存储bean的代码如下所⽰:

@Component
public class UserComponent {
    public void doUserComponent() {
        System.out.println("do UserComponent....");
    }
}

读取bean的代码和上面的基本差不多.

1.5 @Configuration(配置存储)

使⽤@Configuration 存储bean的代码如下所⽰:

@Configuration
public class UserConfig {
    public void doUserConfig() {
        System.out.println("do UserConfig...");
    }
}

读取bean的代码和上面的基本差不多.

2 为什么要这么多类注解?

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

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

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

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

• @Configuration:配置层.处理项⽬中的⼀些配置信息.

这和每个省/市都有⾃⼰的⻋牌号是⼀样的. ⻋牌号都是唯⼀的,标识⼀个⻋辆的.但是为什么还需要设置不同的⻋牌开头呢. ⽐如陕西的⻋牌号就是:陕X:XXXXXX,北京的⻋牌号:京X:XXXXXX,甚⾄⼀个省不同的县区也 是不同的,⽐如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样. 这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地.

类注解之间的关系 

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

@Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持 久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择.

⽐如杯⼦有喝⽔杯,刷⽛杯等,但是我们更倾向于在⽇常喝⽔时使⽤⽔杯,洗漱时使⽤刷⽛杯. 

以上介绍的五大注解只能加在类上, 并且只能加在自己的代码上. 如果你引入了一个第三方的jar包, 并且也希望交给Spring管理, 是没有办法加五大注解的,

3 ⽅法注解@Bean

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

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

2. ⼀个类,需要多个对象,⽐如多个数据源

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

我们先来看看⽅法注解如何使⽤:

// 这个是@Data是lombok插件的注解
@Data
public class UserInfo {
    private Integer id;
    private String name;
    private Integer age;
}
public class BeanConfig {

    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }
}

然⽽,当我们写完以上代码,尝试获取bean对象中的user时却发现,根本获取不到:

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
        
        //@Bean演示
        UserInfo userInfo = (UserInfo) context.getBean(UserInfo.class);
        System.out.println(userInfo);

      
    }

}

这是为什么呢?

3.1 ⽅法注解要配合类注解使⽤

在Spring框架的设计中,⽅法注解 @Bean要配合类注解才能将对象正常的存储到Spring容器中, 如下代码所⽰: 

@Configuration
public class BeanConfig {

    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }
}

再次运行代码, 就没有报错了. 

3.2 定义多个对象

对于同⼀个类,如何定义多个对象呢?

⽐ 如多数据源的场景,类是同⼀个,但是配置不同,指向不同的数据源

@Configuration
public class BeanConfig {

    @Bean
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }

    @Bean
    public UserInfo userInfo2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(2);
        userInfo.setName("lisi");
        userInfo.setAge(11);
        return userInfo;
    }

}

定义了多个对象的话,我们根据类型获取对象,获取的是哪个对象呢?

运行结果:

报错信息显⽰:期望只有⼀个匹配,结果发现了两个,userInfo,userInfo2.

从报错信息中,可以看出来, @Bean注解的bean,bean的名称就是它的⽅法名.

接下来我们根据名称来获取bean对象

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);
       

        //@Bean演示
        UserInfo userInfo = (UserInfo) context.getBean("userInfo");
        System.out.println(userInfo);

        UserInfo userInfo2 = (UserInfo) context.getBean("userInfo2");
        System.out.println(userInfo2);

    }

}

3.3 Bean传递参数

3.4 重命名Bean

可以通过设置name属性给Bean对象进⾏重命名操作,如下代码所⽰:

    @Bean(name = {"u1", "userInfo"})
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }

此时我们使⽤u1就可以获取到User对象了,如下代码所⽰:

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);     
        //@Bean演示
        UserInfo userInfo = (UserInfo) context.getBean("u1");
        System.out.println(userInfo);       
    }

}

name={} 可以省略,如下代码所⽰:

    @Bean({"u1", "userInfo"})
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }

只有⼀个名称时,{}也可以省略,如:

    @Bean("u1")
    public UserInfo userInfo() {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(1);
        userInfo.setName("zhangsan");
        userInfo.setAge(18);
        return userInfo;
    }

4 扫描路径

bean想要生效, 就要能够被Spring扫描到.

下⾯我们通过修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:

在运行代码:

@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);     
        //@Bean演示
        UserInfo userInfo = (UserInfo) context.getBean("u1");
        System.out.println(userInfo);       
    }

}

 

解释:没有bean的名称为u1.

为什么没有找到bean对象呢? 使⽤五⼤注解声明的bean,要想⽣效,还需要配置扫描路径,让Spring扫描到这些注解 也就是通过 @ComponentScan 来配置扫描路径.

@ComponentScan({"org.example"})
@SpringBootApplication
public class IocDemoApplication {

    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(IocDemoApplication.class, args);     
        //@Bean演示
        UserInfo userInfo = (UserInfo) context.getBean("u1");
        System.out.println(userInfo);       
    }

}

运行结果 

 

{} ⾥可以配置多个包路径 这种做法仅做了解,不做推荐使⽤ 

那为什么前⾯没有配置@ComponentScan注解也可以呢? @ComponentScan 注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中了

默认扫描的范围是SpringBoot启动类所在包及其⼦包 

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

5 总结

Bean的存是通过五大注解和@Bean实现的, 使用五大注解和@Bean时, Spring会给一个默认的名称. 

五大注解: BeanName是类名的小驼峰表示法, 如果前两个字母是大写, BeanName为类名.

@Bean: BeanName是方法名.

那如何修改BeanName呢?

UserService userService = (UserService) context.getBean("uuu");
userService.doService();

 

UserInfo userInfo = (UserInfo) context.getBean("uu");
System.out.println(userInfo);

 

相关文章:

  • python+requests接口自动化测试
  • git使用钩子文件出现错误
  • 《深入剖析 SmartInt 包装类:实现智能整数操作》
  • Netty源码—5.Pipeline和Handler二
  • 31天Python入门——第9天:再学函数
  • 推荐1款简洁、小巧的实用收音机软件,支持手机和电脑
  • 量子计算与人工智能的融合:下一代算力革命
  • 【CGE】社会核算矩阵构建(一):SAM基本结构
  • 【零基础入门unity游戏开发——2D篇】2D物理系统 —— 2D刚体组件(Rigidbody 2d)
  • 计算机网络的分类——按地理范围分类
  • uniapp运行到支付宝开发者工具
  • 使用Kafka 3.2.3内置的Zookeeper
  • 导游职业资格考试复习时间规划,合理安排高效备考
  • 江西南昌棒球城市·棒球1号位
  • 基于FPGA的16QAM+帧同步系统verilog开发,包含testbench,高斯信道,误码统计,可设置SNR
  • EMS小车技术特点与优势:高效灵活的自动化输送解决方案
  • 后端——AOP异步日志
  • 伊吖学C笔记(2、文件、启动、数学基础)
  • Python——成员变量
  • C语言入门教程100讲(40)文件定位
  • 三大白电巨头去年净利近900亿元:美的持续领跑,格力营收下滑
  • 言短意长|新能源领军者密集捐赠母校
  • 文化润疆|让新疆青少年成为“小小博物家”
  • 马上评丨机械停车库成“僵尸库”,设计不能闭门造车
  • 王文涛会见德国汽车工业协会主席穆勒
  • 旧衣服旧纸箱不舍得扔?可能是因为“囤物障碍”