Spring使用注解进行对象装配(DI)

news2024/9/29 11:32:31

文章目录

  • 一. 什么是对象装配
  • 二. 三种注入方式
    • 1. 属性注入
    • 2. 构造方法注入
    • 3. Setter注入
  • 三. 三种注入方式的优缺点
  • 四. 综合练习

通过五大类注解可以更便捷的将对象存储到 Spring 中,同样也可以使用注解将已经储存的对象取出来,直接赋值到注解所在类的一个属性中,这一个过程也叫做对象的装配或者叫对象的注入,即 DI。

一. 什么是对象装配

获取 Bean 对象也叫做对象装配,就是把对象取出来放到某个类中,有时候也叫对象注入。
对象装配(对象注入)的实现方法以下 3 种:

  1. 属性注入 ,就是将对象注入到某个类的一个属性当中。
  2. 构造方法注入 ,就是通过构造方法来将对象注入到类中。
  3. Setter 注入 ,通过 SetXXX 系列方法将对象注入到类中。

常见有关对象注入的注解有两个,一个是@Autowired,另外一个是@Resource

🍂它们两者的区别如下:

  1. 出身不同:@Autowired 是由Spring提供的,而 @Resource 是JDK提供的。
  2. 查找顺序不同:从容器中获取对象时 @Autowired 先根据类型再根据名称查询,而 @Resource 先根据名称再根据类型查询。
  3. 使⽤时设置的参数不同:@Resource 支持更多的参数设置(有 7 个),如nametype等,而@Autowired只支持设置required一个参数,用来设置注入 Bean 的时候该 Bean 是否必须存在(true/false)。 imgimg
  4. 依赖注入支持不同:@Autowired 支持属性注入,构造方法注入和 Setter 注入,而 @Resource 只支持属性注入和 Settter 注入,但是不支持构造方法注入。
  5. 对 IDEA 的兼容性支持不同:使用 @Autowired 在 IDEA 旗舰版下可能会有误报(设置required即可);而 @Resource 不存在误报的问题。

二. 三种注入方式

1. 属性注入

属性注入只需要在需要注入对象的属性上加上 @Autowired 或者 @Resource 注解就可以了,这里以 @Autowired 为例。

首先来看第一种情况,待注入的同类对象只有一个,此时我们直接使用 @Autowired 注解就好,不必设置参数,例如我们在UserController类里面注入UserService对象。

下面UserService的结构,先使用 @Service 将 Bean 存放到 Spring 中:

package com.tr.demo.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
    public void sayHi() {
        System.out.println("Hello, UserService~");
    }
}

属性注入:

package com.tr.demo.controller;

import com.tr.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    //属性注入
    @Autowired
    private UserService userService;

    public void sayHi() {
        userService.sayHi();
    }
}

此时我们就可以在启动类中,使用上下文对象来获取到UserController对象,通过执行UserController对象的sayHi方法来进而调用到注入的UserService对象中的sayHi方法了,此时的UserService对象就不是我们自己new出来的了。

import com.tr.demo.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        UserController usercontroller =  context.getBean("userController", UserController.class);

        usercontroller.sayHi();
    }
}

运行结果:
img
上面说的是同类对象只有一个的情况,而如果存在多个同类对象,我们就得通过参数来告知容器我们要注入哪一个对象,不告知就会报错。

比如我们将多个User对象添加到容器中,如下:

package com.tr.demo.model;
// User 结构
public class User {
    private int id;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.tr.demo.model;

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

@Component
public class UserBeans {
    // 使用方法注解添加多个 User 对象到容器中
    @Bean("user1")
    public User user1(){
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }

    @Bean("user2")
    public User user2(){
        User user = new User();
        user.setId(2);
        user.setName("李四");
        return user;
    }

    @Bean("user3")
    public User user3(){
        User user = new User();
        user.setId(3);
        user.setName("王五");
        return user;
    }
}

而在UserController2类中需要注入User对象,此时我们运行程序就会报错:

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController2 {
    @Autowired
    private User user;

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

我们试着以同样的方法来调用sayHi方法:

img
运行结果:

@Autowired 依赖注入流程首先是先根据类型从容器中获取对象,如果只能获取到一个,那么就直接将此对象注入到当前属性上;如果能获取到多个对象,此时会使用 BeanName 进行匹配,而我们添加到 Spring 中的对象是没有一个叫user的,所以程序就报错了。
img

此时就需要我们来告知容器我们需要哪一个具体的 Bean,要获得目标对象主要有下面三种方法:

  • 1️⃣方法1:将属性的变量名设置为你需要的那个BeanName就可以了,后面的构造方法与 Setter 注入同理,将形参名设置成与BeanName相同即可。imgimg
  • 2️⃣方法2:@Autowired 注解与 @Qualifier 注解配合使用,设置 @Qualifier 的value参数为BeanName即可,要注意 @Qualifier 注解不能修饰方法,只能修饰变量。
    imgimg
  • 3️⃣方法3:将 @Autowired 注解替换成 @Resource 注解的,并将它name参数值设置为BeanName即可。 imgimg

2. 构造方法注入

在构造方法加上 @Autowired 注解就可,要注意 @Resource 注解是不支持构造方法注入的,我们就直接演示如何获取取多个同类对象中的其中一个了,还是用上面添加到容器中的多个 User 对象。

方法1:将构造方法形参名设置为user1

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController3 {
    private User user;
    @Autowired
    public UserController3(User user1) {
        this.user = user1;
    }

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

启动类就不贴代码了,一样的,运行结果如下:
img

方法2:@Autowired 搭配 @Qualifier

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController4 {
    private User user;
    @Autowired
    public UserController4(@Qualifier(value = "user2") User user) {
        this.user = user;
    }

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

运行结果:
img
对了,如果一个类中只有一个构造方法,@Autowired 是可以省略的,演示一下:

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.stereotype.Controller;

@Controller
public class UserController5 {
    private User user;

    public UserController5(User user3) {
        this.user = user3;
    }

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

此时仍然是可以成功注入对象。
img
如果有多个构造方法,要注意此时是不能省略 @Autowired 的,会导致会注入对象失败。

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.stereotype.Controller;

@Controller
public class UserController6 {
    private User user;

    public UserController6(User user1) {
        this.user = user1;
    }
    
    public UserController6() {
        System.out.println("调用无参构造");
    }

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

此时可以看到注入对象失败了,输出的结果是null

img
当然此时加上 @Autowired 注解就能正常注入了,就不做展示了。

3. Setter注入

Setter 注入就是在 setXXX 系列方法上加上 @Resource 或者 @Autowired 进行注入,和构造方法注入大同小异,简单演示一下。

package com.tr.demo.controller;

import com.tr.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;

@Controller
public class UserController7 {
    private User user;

    @Autowired
    public void setUser(@Qualifier(value = "user2") User user) {
        this.user = user;
    }

    public void sayHi() {
        System.out.println("Hello, " + user);
    }
}

启动类和运行结果:

img

这里这里第一行输入的是因为启动程序会将上面写的UserController6也添加到容器中,UserController6的无参构造方法是我们自定义的。

三. 三种注入方式的优缺点

在早期的 Spring 版本中,官方推荐使用的 Setter 注入,最开始说的原因就是不符合单一设计原则吧,而现在比较新的 Spring 版本(Sring 4.x 之后)中,官方最使用推荐的又是构造方法注入了,说法是因为它的通用性最好。

🎯属性注入

优点:

  1. 使用起来简单方便

缺点:

  1. 无法注入到一个final修饰的变量,因为 final 修饰的变量只有两种赋值方式,一是直接赋值,二是通过构造方法进行赋值,而属性注入这两种方式都不能满足。img
  2. 通用性问题,属性注入只能在 IoC 容器中使用,在非 IoC 容器中是不可⽤的。
  3. 更容易违背单一设计原则,简单理解就是注入方式越简单,滥用的概率越大,就比如在数据持久层有一个针对用户操作的类,本来这个类就只是注入用户相关操作的依赖就行了,但由于属性注入使用起来成本不高,程序猿就多注了一些依赖去完成了一些和用户操作无关的内容,这就违背了单一设计原则了。

🎯Setter 注入

优点:

  1. 通常情况下,setXXX 系列的方法中只会设置一个属性,就更符合单一设计原则。

缺点:

  1. 同样的,也不能注入到一个 final 修饰的变量中。img
  2. 注入的对象是可能被修改的,因为 setXXX 系列的方法随时都有可能被调用导致注入的 Bean 就被修改了。

🎯构造方法注入

优点:

  1. 可以注入到一个被 final 修饰的变量。img
  2. 注入对象不会被修改,因为构造方法只会在对象创建时执行一次,不存在注入对象被随时修改的情况。
  3. 可以保证注入对象的完全初始化,因为构造方法是在对象创建之前执行的。
  4. 通用性最好,因为不管你怎么写 Java 代码,创建实例对象时都要执行构造方法吧。

缺点:

  1. 相较于属性注入,写法更加复杂,如果有多个注⼊会显得⽐较臃肿,但出现这种情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了。
  2. 使用构造注入,无法解决循环依赖的问题。

四. 综合练习

在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀ 个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可。

首先要清楚的是在 main 方法中是不能使用依赖注入的,因为类的静态部分是在 Spring 注入之前的加载的,仔细想一下,在类加载时就要使用一个还没注入的对象这是不现实的。

所以我们要在 main 中执行的是将扫描路径中的类添加到 Spring 中,对象的注入要在 mian 方法所在类的外部去实现。

img

package com.tr.demo.model;

public class User {
    private int id;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.tr.demo.repository;

import com.tr.demo.model.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository {

    public User getUser(){
        // 伪代码
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }

}

package com.tr.demo.service;

import com.tr.demo.model.User;
import com.tr.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUser(){
        return userRepository.getUser();
    }

}

package com.tr.demo.contoller;

import com.tr.demo.model.User;
import com.tr.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired
    private UserService userService;

    public User getUser(){
        return userService.getUser();
    }

}

package com.tr.demo;

import com.tr.demo.contoller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 启动类
 */
public class App {

    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        UserController userController =
                context.getBean("userController", UserController.class);
        System.out.println(userController.getUser());
    }
}

运行结果:

img

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

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

相关文章

守护进程——后台服务进程

文章目录 什么是终端进程组会话关系相关函数守护进程创建步骤应用 什么是终端 echo $$:可以查看当前进程的进程号 进程组 会话》进程组》首进程 会话 关系 >:重定向 |:管道 wc -l:查找 &:在后台去运行 SID:会…

小学期笔记——天天酷跑3

画笔的载体是图层 图层的载体是窗体 效果: ------------------- 效果: ---------------------- 实现一个接口可以理解成添加一个能力 接口可以理解为能力的集合 对于abstract(判断:没有方法体),尽量使用…

linux系统上安装kail

1.虚拟机安装 加入kail镜像 kail系统的安装 2.更新kail的源 注释原本的源,加入阿里云的源 #阿里云 #deb http://mirrors.aliyun.com/kali kali-rolling main non-free contrib #deb-src http://mirrors.aliyun.com/kali kali-rolling main non-free contrib 参考&…

【计算机网络】11、网桥(bridge)、集线器(hub)、交换机(switch)、路由器(router)、网关(gateway)

文章目录 一、网桥(bridge)二、集线器(hub)三、交换机(switch)四、路由器(router)五、网关(gateway) 对于hub,一个包过来后,直接将包转发到其他口。 对于桥&…

【C++ 程序设计】实战:C++ 变量实践练习题

目录 01. 变量:定义 02. 变量:初始化 03. 变量:参数传递 04. 变量:格式说明符 ① 占位符 “%d” 改为格式说明符 “%llu” ② 占位符 “%d” 改为格式说明符 “%f” 或 “%e” 05. 变量:字节数统计 06. 变量&a…

[containerd] 在Windows上使用IDEA远程调试containerd, ctr, containerd-shim

文章目录 1. containerd安装2. 源码编译3. 验证编译的二进制文件是否含有调试需要的信息3.1. objdump工具验证3.2. file工具验证3.3. dlv工具验证 4. debug 1. containerd安装 [Ubuntu 22.04] 安装containerd 2. 源码编译 主要步骤如下: 1、从github下载containe…

MyBatis-Plus 查询PostgreSQL数据库jsonb类型保持原格式

文章目录 前言数据库问题背景后端返回实体对象前端 实现后端返回List<Map<String, Object>>前端 前言 在这篇文章&#xff0c;我们保存了数据库的jsonb类型&#xff1a;MyBatis-Plus 实现PostgreSQL数据库jsonb类型的保存与查询 这篇文章介绍了模糊查询json/json…

前端调用合约如何避免出现transaction fail

前言&#xff1a; 作为开发&#xff0c;你一定经历过调用合约的时候发现 gas fee 超出限制&#xff0c;但是不知道报了什么错。这个时候一般都是触发了require错误合约校验。对于用户来说他不理解为什么一笔交易会花费如此大的gas&#xff0c;那我们作为开发如何尽量避免这种情…

Power BI-网关设置与云端报表定时刷新(一)

网关的工作原理 网关是将本地数据传输至云端的桥梁&#xff0c;不仅Power BI能使用&#xff0c;其他微软软件也能够使用。 我们发布在云上的报表&#xff0c;发布后是静态的&#xff0c;不会自动刷新。需要通过网关设置定时刷新。 安装与设置 1.登录到Powerbi 在线服务–设置…

kaggle新赛:RSNA 2023 腹部创伤检测大赛赛题解析(CV)

赛题名称&#xff1a;RSNA 2023 Abdominal Trauma Detection 赛题链接&#xff1a; https://www.kaggle.com/competitions/rsna-2023-abdominal-trauma-detection 赛题背景 腹部钝力创伤是最常见的创伤性损伤类型之一&#xff0c;最常见的原因是机动车事故。腹部创伤可能导致…

大促之前全链路压测原理解析

1. 全链路压测的意义 上图为 2012 年淘宝核心业务应用关系的拓扑图&#xff0c;还不包含了其他的非核心业务应用&#xff0c;所谓的核心业务就是和交易相关的&#xff0c;和钱相关的业务&#xff0c;这张图大家可能看不清楚&#xff0c;看不清楚才是正常的&#xff0c;因为当时…

BGP实验

第一步&#xff1a;配置IP 第二步&#xff1a;写ospf 在R2&#xff0c;R3&#xff0c;R4&#xff0c;R5&#xff0c;R6&#xff0c;R7上分别配置ospf 如&#xff1a;R2 [R2]ospf 1 router-id 2.2.2.2 [R2-ospf-1]area 0 [R2-ospf-1-area-0.0.0.0]network 172.16.0.0 0.0.255…

wxwidgets Ribbon构建多个page与按钮响应

新建一个控制台应用程序&#xff0c;添加好头文件的依赖与lib库文件的依赖&#xff0c;修改属性&#xff1a; 将进入ribbon界面的文件与主界面的类分开&#xff1a; 1、RibbonSample.cpp #include "stdafx.h" #include "MyFrame.h" class MyApp : public…

谷粒商城第六天-商品服务之分类管理下的获取三级分类树形列表

目录 一、总述 1.1 前端思路 1.2 后端思路 二、前端部分 2.1 在网页中建好目录及菜单 2.1.1 建好商品目录 2.1.2 建好分类管理菜单 ​编辑 2.2 编写组件 2.2.1 先完成组件文件的创建 2.2.2 编写组件 2.2.2.1 显示三级分类树形列表 三、后端部分 3.1 编写商品分类…

二十章:基于弱监督语义分割的亲和注意力图神经网络

0.摘要 弱监督语义分割因其较低的人工标注成本而受到广泛关注。本文旨在解决基于边界框标注的语义分割问题&#xff0c;即使用边界框注释作为监督来训练准确的语义分割模型。为此&#xff0c;我们提出了亲和力注意力图神经网络&#xff08;A2GNN&#xff09;。按照先前的做法&a…

NO1.使用命令行创建Maven工程

①在工作空间目录下打开命令窗口 ②使用命令行生成Maven工程 mvn archetype:generate 运行 MVN 原型&#xff1a;生成命令,下面根据提示操作 选择一个数字或应用过滤器&#xff08;格式&#xff1a;[groupId&#xff1a;]artifactId&#xff0c;区分大小写包含&#xff09;&a…

阿里云服务器全方位介绍_优势_使用_租用费用详解

阿里云服务器全方位介绍包括云服务器ECS优势、云服务器租用价格、云服务器使用场景及限制说明&#xff0c;阿里云服务器网分享云服务器ECS介绍、个人和企业免费试用、云服务器活动、云服务器ECS规格、优势、功能及应用场景详细你说明&#xff1a; 目录 什么是云服务器ECS&…

C++基础知识 (命名空间、输入输出、函数的缺省参数、函数重载)

⭐️ 第一个 c 代码 &#x1f320; 例1&#xff1a; #include <iostream> using namespace std;int main() {cout << "hello world" << endl;return 0; }#include <iostream> 标准输入输出std 是 c 标准库的命名空间&#xff0c;将标准库的…

Open3D-ML自动驾驶点云目标检测与分割入门

当开始新的研究时&#xff0c;我的方法通常是测试不同的相关事物&#xff0c;直到有足够的经验让我开始将这些点联系起来。 在开始构建用于 3D 对象检测的自定义模型之前&#xff0c;我购买了一台 LiDAR 并处理了一些数据。 下一个明显的步骤是在我为自己的数据贴标签之前找出研…

6、Nginx实现反向代理

Nginx 反向代理是一种常见的应用场景&#xff0c;它允许 Nginx 作为中间服务器接收客户端的请求&#xff0c;并代理转发这些请求到后端的真实服务器。这种配置使得客户端只需要与 Nginx 交互&#xff0c;而后端服务器对客户端是透明的。 ngx_http_proxy_module&#xff1a; 将客…