【Spring】基于注解方式存取JavaBean:Spring有几种注入方式?有什么区别?

news2024/11/18 19:33:55

前言

 Hello,我是小黄。众所周知,Spring是一个开源的Java应用程序框架,其中包括许多通过注解实现依赖注入的功能。Spring提供了多种注入方式,可以满足不同的需求和场景。常见的注入方式包括构造函数注入、Setter方法注入和属性注入。不同的注入方式有不同的适用场景和优缺点。选择正确的注入方式可以提高应用程序的可维护性和性能。 本文以Spring的常见注解以及不同注入方式的优缺点为切入点进行深入讨论。 话不多说,正文开始~
表情1


文章目录

  • 前言
  • *操作准备
  • 1 什么是注解?
  • 2 基于注解存储 Bean 对象
    • 2.1 类注解方式
      • 2.1.1 如何存储 Bean?
      • 2.1.2 如何读取 Bean?
      • 2.1.3 读取时的常见问题及 Bean 命名规则
    • 2.2 方法注解方式
  • 3 基于注解获取 Bean 对象(对象装配)
    • 3.1 属性注入
    • 3.2 Setter 注入
    • 3.3 构造方法注入
    • 3.4 三种不同注入方式的优劣分析
    • 3.5 @Resource 注解
    • 3.6 常见错误:非唯一的 Bean 对象
  • 写在最后


*操作准备

 要想将对象成功存储到 Spring 中,就需要配置存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。这里采用 xml 文件的形式进行配置(有兴趣的小伙伴可以查阅笔者之前的Spring文章,采用配置类的形式进行纯注解的开发配置):

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置扫描路径 -->
    <content:component-scan base-package="com.hxh"/>
</beans>

 另外,Spring 操作需要的相关依赖,这里采用 Maven 的方式实现,相关依赖的配置如下:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>Spring_demo03</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- Spring相关的配置 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>
    </dependencies>
</project>

1 什么是注解?

注解是代码中特殊的标记,格式如下:

@注解名称(属性名称=属性值,属性名称=属性值......)

注解可以作用在类、方法、属性上。Spring针对Bean管理中创建对象提供注解,在 Spring 中,注解可以分为两大类:

  1. 类注解: 以下四个注解都可以用来创建bean实例,只是为了便于开发者清晰区分当前层。@Configuration@Service@Controller@Repository@Component
  2. 方法注解: @Bean

为什么需要这么多个类注解?

对于其余四个类注解,它们本身就是属于 @Component 的“⼦类”。五个类注解的功能是一致的,命名上将其区分,是为了让程序员看到类注解之后,就能直接了解当前类的用途:

  • @Controller:表示的是业务逻辑层;
  • @Servie:服务层;
  • @Repository:持久层;
  • @Configuration:配置层。

程序的工程分层


2 基于注解存储 Bean 对象

2.1 类注解方式

2.1.1 如何存储 Bean?

@Configuration 配置存储

@Configuration   // 将对象存储到 Spring 中
public class UserConfiguration {
    public void sayHello() {
        System.out.println("Hello, I'm userConfiguration !");
    }
}

@Service 服务存储

@Service  // 将对象存储到 Spring 中
public class UserService {
    public void sayHello() {
        System.out.println("Hello, I'm userService!");
    }
}

@Controller 控制器存储

@Controller  // 将对象存储到 Spring 中
public class UserController {
    public void sayHello() {
        System.out.println("Hello, I'm userController!");
    }
}

@Repository 仓库存储

@Repository  // 将对象存储到 Spring 中
public class UserRepository {
    public void sayHello() {
        System.out.println("Hello, I'm userRepository!");
    }
}

@Component 组件存储

@Component  // 将对象存储到 Spring 中
public class UserComponent {
    public void sayHello() {
        System.out.println("Hello, I'm userComponent!");
    }
}

2.1.2 如何读取 Bean?

这里以读取 StudentController 对象为例,由于我们采用的是标准的大驼峰命名法,因此,读取的时候将首字母小写, 就可以读取到相应的 JavaBean 了。
读取JavaBean

2.1.3 读取时的常见问题及 Bean 命名规则

倘若我们不按照标准的大驼峰命名法对类进行命名读取Bean时会有区别吗?
首先我们尝试创建类 IService,并通过首字母小写的方式读取 Bean:无法正常读取 Bean!
非标准命名格式下程序运行情况
为了探究异常发生的原因,我们需要深入 Spring 的源码,看看 Bean 的命名规则:AnnotationBeanNameGenerator.class
bean规则1
bean 对象的命名规则使用方法是 JDK Introspector 中的 decapitalize ⽅法,其源码如下:

  public static String decapitalize(String name) {
      if (name == null || name.length() == 0) {
          return name;
      }
      if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                      Character.isUpperCase(name.charAt(0))){
          return name;
      }
      char chars[] = name.toCharArray();
      chars[0] = Character.toLowerCase(chars[0]);
      return new String(chars);
  }

通过阅读源码,可以看出 bean 的命名规则: 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了~ 否则,就将首字母小写~ 这通常意味着将第一个字符从大写转换为小写,但在(不寻常的)特殊情况下,当有多个字符并且第一个和第二个字符都是大写时,我们不处理它。

2.2 方法注解方式

类注解用于标记类为Spring Bean,使用@ComponentScan扫描时,每个注解只会创建一个Bean对象,因此在同一个ApplicationContext容器中,获得的对象是同一个。

那么如果想要获取不同的对象怎么办呢? 下面我们就来聊一聊方法注解~

顾名思义,⽅法注解就是是放到某个⽅法上的,以下是简单的代码实现:需要注意的是 ⽅法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中

@Controller
public class StudentBeans {

    @Bean(name = {"s1", "s2"})  // 可以通过设置 name 属性给 Bean 对象进⾏重命名操作
    public Student student() {
        Student student = new Student();
        student.setId(1);
        student.setName("黄小黄");
        student.setAge(17);
        return student;
    }
    
    @Bean
    public Student student02() {
        Student student = new Student();
        student.setId(2);
        student.setName("蒲小七");
        student.setAge(16);
        return student;
    }
}

读取 Bean 的代码如下:

    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        Student s1 = context.getBean("s1", Student.class);
        Student s2 = context.getBean("s2", Student.class);
        // 打印 s1 和 s2 信息
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1 == s2 ? " + (s1 == s2));
        // 尝试获取 student 不同对象
        Student student02 = context.getBean("student02", Student.class);
        System.out.println(student02);
        System.out.println("s1 == student2 ? " + (s1 == student02));
    }

实现结果


3 基于注解获取 Bean 对象(对象装配)

在Spring中,获取 Bean 对象即把对象取出来放到某个类中,有三种方式来实现对象装配:

  1. 属性注入
  2. Setter 注入
  3. 构造方法注入

不同的注入方式有不同的适用场景和优缺点。以下案例则以将 Service 类注⼊到 Controller 类中为切入点,帮助大家了解三种注入方式的优缺点和区别~

3.1 属性注入

属性注入主要通过 @Autowired 实现,相关代码如下:

UserService

@Service
public class UserService {

    public void sayHello() {
        System.out.println("Hello, I'm userService!");
    }
}

UserController

@Controller
public class UserController {

    // 1. 使用属性注入的方式  不能注入不可变对象且违背单一设计原则
    @Autowired
    private UserService userService;

	public void sayHello() {
        // 调用 service 的方法
        userService.sayHello();
    }
}

此时,通过如下的代码,即可通过 userController 的 sayHello() 方法调用 service 中的 sayHello() 方法。其余两种装配实现方式,该部分代码等同,Service 也等同,就不再赘述了。

public class ApplicationTest01 {

    private static final String URL = "spring-config.xml";

    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext(URL);
        UserController userController = context.getBean("userController", UserController.class);
        userController.sayHello();
    }
}

在这里插入图片描述

3.2 Setter 注入

即,在 setter 方法前加上 @Autowired 注解。

核心代码

    // 2. set 注入 不能注入不可变对象但是更符合单一设计原则
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

3.3 构造方法注入

构造⽅法注⼊是在类的构造⽅法中实现注⼊。在类中添加带有参数的构造方法,Spring会根据参数类型和名称,在容器中找到相应的Bean进行注入。特别地,如果只有⼀个构造⽅法,那么 @Autowired 注解可以省略~

核心代码

    // 3. 构造方法注入
    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

3.4 三种不同注入方式的优劣分析

Spring的三种注入方式分别是属性注入、Setter 注入与构造方法注入,其区别如下:

  1. 属性注入:在类的属性上添加@Autowired注解,Spring会自动在容器中查找符合该属性类型的Bean进行注入。优点是简洁,使⽤⽅便;缺点是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指针异常)。同时,属性注入的方式不可注入不可变对象,且违背单一设计原则:
    在这里插入图片描述

  2. Setter注入:在类中添加setter方法,Spring会根据方法名称和参数类型,在容器中找到相应的Bean进行注入。主要特点是注入后对象的属性是可变的,可以随时修改。虽然在一定程度上相对符合了单一设计原则,但依然不可注入不可变对象:
    在这里插入图片描述

  3. 构造器注入:在类中添加带有参数的构造方法,Spring会根据参数类型和名称,在容器中找到相应的Bean进行注入。主要特点是注入后对象的属性是不可变的,一旦注入完成后就不能再改变,是 Spring 推荐的注⼊⽅式。解决了以上两种方式不可注入不可变对象的问题:
    在这里插入图片描述

📖 补充:单一设计原则

单一职责原则(Single Responsibility Principle)是一种设计原则,它建议一个类或模块只应该负责一项职责。这个原则通常被认为是最基本的设计原则之一。
使用单一职责原则可以带来如下好处:

  1. 降低模块的复杂度,使其更易于理解和修改。
  2. 提高模块的可复用性和可测试性,因为它们只关注一项职责。
  3. 降低系统的耦合性,因为每个模块都是独立的,不会依赖于其他模块。
  4. 更好地支持面向对象设计的原则,例如开放封闭原则。

3.5 @Resource 注解

在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊:

@Resource
private UserService userService;

@Resource 与 @Autowired 的区别:

  • @Autowired 来自于 Spring,而 @Resource 来自 JDK,后者相较前者支持更多的参数设置;
  • @Resource 只能用于 Setter 注入和 属性注入。

3.6 常见错误:非唯一的 Bean 对象

当出现以下多个 Bean,返回同⼀对象类型时程序会报错,参考代码如下:

@Component
public class BookBeans {

    @Bean
    public Book book01() {
        Book book = new Book();
        book.setName("计算机网络");
        return book;
    }

    @Bean
    public Book book02() {
        Book book = new Book();
        book.setName("计算机操作系统");
        return book;
    }
}

Controller

@Controller
public class BookController {

    @Autowired
    private Book book;

    public Book getBook() {
        return book;
    }
}

在这里插入图片描述

这是由于 Bean 对象不是唯一的导致的~, 解决方案如下:

  1. 使用 @Resource(name = "xxx") 来指定~
    在这里插入图片描述

  2. 使用 @Qualifier 注解定义名称~
    在这里插入图片描述


写在最后

 本文就如何将对象存储到 Spring 中分类注解与方法注解进行了阐述,同时,对 Bean 对象的命名规则进行了说明。从 Spring 中获取对象有三种方式,不同的注入方式有不同的适用场景,或更加简便,或有独有的特性。总的来说,Spring 的出现的确为我们开发提高了效率,但我们需要明确的是,任何一个工具的出现都要求我们在熟悉的基础上使用,才能事半功倍。
本文被 JavaEE编程之路 收录点击订阅专栏 , 持续更新中。
 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/699929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Redis】Redis的高可用与持久化

文章目录 一、Redis 高可用1. 概念2. 高可用技术以及作用2.1 持久化2.2 主从复制2.3 哨兵2.4 集群 二、Redis 持久化1. 持久化的功能2. Redis 持久化方式 三、RDB 持久化1. 概述2. 触发条件2.1 手动触发2.2 自动触发2.3 其他自动发机制 3. 执行流程4. 启动时加载 四、AOF 持久化…

Modin 入门学习

Modin 是一个 Python 第三方库&#xff0c;用于加速 Pandas 的 API 执行速度。原始的 Pandas 是单线程执行的&#xff0c;而 Modin 则重新打包了 Pandas 里面的 API&#xff0c;使其同时在多个内核中运行&#xff0c;提高硬件性能的利用率。 使用方法很简单&#xff0c;安装 M…

2.9C++多态

C 继承扩展 C继承在实际开发中它可以帮助我们实现代码重用&#xff0c;减少代码冗余&#xff0c;提高代码的可维护性和可扩展性。 通过继承&#xff0c;我们可以从已有的类中派生出新的类&#xff0c;新的类可以继承父类的属性和方法&#xff0c;并且可以添加自己的属性和方法…

selenium元素定位---ElementClickInterceptedException(元素点击交互异常)解决方法

目录 前言&#xff1a; 1、异常原因 2、解决方法&#xff1a; 前言&#xff1a; 当使用Selenium进行元素定位和交互时&#xff0c;可能会遇到ElementClickInterceptedException&#xff08;元素点击交互异常&#xff09;的异常。这通常是由于页面上存在其他元素或弹出窗口遮…

ROS学习之基础包创建的详细流程:包括rosnode, rostopic, rosrun,roslaunch等使用

0 引言 本文旨在学习ROS基础包的从零开始创建&#xff0c;包括如何创建一个发布消息节点&#xff0c;一个接收消息节点&#xff0c;还有如何使用roslaunch同时启动多个节点&#xff0c;如何编译ROS工程包等操作。 默认已在Ubuntu系统中安装ROS机器人系统&#xff0c;比如Ubun…

AOP--拦截器

AOP应用--拦截器Spring拦截器拦截器执行流程前缀的添加统一异常处理统一数据返回格式返回String类型 AOP应用–拦截器 AOP的作用&#xff1a;统一功能处理&#xff1b;我们将以三个内容作为学习的掌握点&#xff1b;而这三点也是我们非常迫切需要的 1&#xff1a;用户登录权限…

Windows系统分区大小

Microsoft Reserved&#xff08;MSR&#xff09;——保留分区——16MB左右 EFI System Partition&#xff08;ESP&#xff09;——系统分区——100MB左右 Recovery Partition&#xff08;自起名字REP&#xff09;——恢复分区——450MB左右 其他分区——剩余

对rabbitmq进行压测

添加rabbitmq依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactI…

C# PaddleOCR标注工具

基于以下开源项目改造的 https://gitee.com/BaoJianQiang/FastOCRLabel 效果 Demo下载

CMake使用gRPC(Protobuf) 的c++ demo

gRPC的命令参数里&#xff0c; 1. 如果要用pacakge&#xff0c;需要--proto_path的参数&#xff0c; 例如helloworld.proto的绝对路径是 /home/user/grpc_demo_ws/grpc_demo/hello_world/proto/helloworld.proto 在helloworld.proto里面的pacakge是 package grpc_demo.hello_w…

同步(通信原理)

同步原理&#xff1a; 在通信系统中&#xff0c;同步是指发送端和接收端在时间上保持一致&#xff0c;使得接收端能够正确地解析和还原发送端发送的信号。同步的原理可以根据具体的通信系统和协议来区分&#xff0c;下面是几种常见的同步原理&#xff1a; 1. 时钟同步&#x…

uniapp 配置chooseLocation微信小程序腾讯地图选点

uniapp 配置chooseLocation微信小程序腾讯地图选点 场景 在uniapp中使用地图选点 搜索功能&#xff0c;回显功能&#xff0c;移动选点功能 使用到的API是uni.chooseLocation 详细看一下都有哪些属性 latitude &#xff1a;目标地纬度 Number longitude &#xff1a;目标地经度…

论文阅读: (CVPR2023 SDT )基于书写者风格和字符风格解耦的手写文字生成及源码对应

目录 引言SDT整体结构介绍代码与论文对应搭建模型部分数据集部分 总结 引言 许久不认真看论文了&#xff0c;这不赶紧捡起来。这也是自己看的第一篇用到Transformer结构的CV论文。之所以选择这篇文章来看&#xff0c;是考虑到之前做过手写字体生成的项目。这个工作可以用来合成…

浅析基于物联网技术的校园能耗智慧监控平台的设计及应用

摘 要&#xff1a;为打造低碳绿色校园&#xff0c;营造良好的学习环境&#xff0c;针对目前校园建筑能耗大&#xff0c;特别是空调节能困难等问题&#xff0c;特采用物联网技术构建校园建筑能耗智慧监控平台。通过设计空调监控子系统&#xff0c;搭建空调监控模型实现了空调等智…

在 Jetpack Compose 中使用 Snackbar

Jetpack Compose 是 Android 的现代 UI 工具库&#xff0c;提供了丰富的组件和功能来构建漂亮、交互丰富的用户界面。在本文中&#xff0c;我们将学习如何在 Jetpack Compose 中使用 Snackbar 组件来显示临时消息或操作反馈。 什么是 Snackbar&#xff1f; Snackbar 是一种用于…

基于Layui实现管理页面

基于Layui实现的后台管理页面&#xff08;仅前端&#xff09; 注&#xff1a;这是博主在帮朋友实现的一个简单的系统前端框架&#xff08;无后端&#xff09;&#xff0c;跟大家分享出来&#xff0c;可以直接将对应菜单跟html文件链接起来&#xff0c;页面使用标签页方式存在&…

面试了一个前阿里P7,Java八股文与架构核心知识简直背得炉火纯青

前几天&#xff0c;跟个老朋友吃饭&#xff0c;他最近想跳槽去大厂&#xff0c;觉得压力很大&#xff0c;问我能不能分享些所谓的经验套路。 每次有这类请求&#xff0c;都觉得有些有趣&#xff0c;不知道你发现没有大家身边真的有很多人不知道怎么面试&#xff0c;也不知道怎…

赛效:如何将PDF文件免费转换成Word文档

1&#xff1a;在网页上打开wdashi&#xff0c;默认进入PDF转Word页面&#xff0c;点击中间的上传文件图标。 2&#xff1a;将PDF文件添加上去之后&#xff0c;点击右下角的“开始转换”。 3&#xff1a;稍等片刻转换成功后&#xff0c;点击绿色的“立即下载”按钮&#xff0c;将…

win10修改IP地址报错:出现一个意外情况,不能完成所有你在......

问题描述 在修改网卡适配器的时候出现一下报错&#xff1a;出现一个意外情况&#xff0c;不能完成你在设置中所要求的更改 问题原因 该问题是由于我之前卸载VMware导致注册表出现问题。 解决方法 解决办法为:修复注册表(下载一个CCleaner下载试用版即可&#xff08;https…

2. 查询至少连续三天下单的用户

文章目录 题目需求思路一实现一思路二实现二 题目需求 查询订单信息表(order_info)中 最少连续3天 下单的用户id&#xff0c;期望结果如下&#xff1a; user_id101 订单信息表&#xff1a;order_info order_id(订单id)user_id(用户id)create_date (下单日期)total_amount(订…