Spring IOC 控制反转(注解版)

news2024/11/22 19:02:24

Spring IOC 控制反转

文章目录

  • Spring IOC 控制反转
    • 一、前言
      • 什么是控制反转(IOC)
      • 什么是依赖注入(DI)
    • 二、介绍 IOC
      • 2.1 传统思想代码
      • 2.2 解决方案
      • 2.3 IOC思想代码
      • 2.4 IOC 使用(@Autowired依赖注入)
      • 2.5 IOC 优势
    • 三、IOC详解
      • 3.1 从Spring容器中获取对象:Spring上下文
      • 3.2 Bean的存储
        • 3.2.1 为什么要这么多类注解?
      • 3.3 方法注解@Bean
        • 3.3.1 @Bean方法注解一定要配合类注解使用
        • 3.3.2 @Bean方法定义多个对象
        • 3.3.3 重命名@Bean
      • 3.4 扫描路径
    • 四、DI依赖注入
      • 4.1 属性注入
      • 4.2 构造方法注入
      • 4.3 Setter注入
    • 五、@Autowired存在问题
      • 5.1 常见面试题:@Autowired与@Resource的区别
    • 六、附加总结

一、前言

Spring 框架中的核心概念之一就是控制反转(Inversion of Control,IoC)

IOC就是一种思想,而依赖注入(Dependency Injection, DI) 是控制反转的一种实现方式。

Spring本身是一个容器,存的是对象。对象这个词,在 Spring的范围内,称之为 Bean。

什么是控制反转(IOC)

控制反转(Inversion of Control,IoC)是一种设计原则,它将对象的创建和依赖关系的管理从程序代码中解耦出来,交由框架或容器进行处理。传统的编程方式中,应用程序代码主动创建和管理对象,而通过IoC,框架或容器负责对象的创建和管理,应用程序代码只需要声明依赖关系。转换对象控制权,让Spring帮我们管理或创建 bean

什么是依赖注入(DI)

依赖注入(Dependency Injection,DI)是一种设计模式,它将对象所依赖的其他对象的创建和管理职责从对象自身剥离出来,通过外部容器(如Spring IoC容器)将所需的依赖对象注入到目标对象中,从而实现对象之间的解耦和提高代码的可维护性和可测试性。

二、介绍 IOC

下面将通过案例来分析什么是IOC

需求:造一辆车

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1 传统思想代码

public class NewCarExample {
    public static void main(String[] args) {
        Car car = new Car(20);
        car.run();
    }

    /**
     * 汽车对象
     */
    static class Car {
        private Framework framework;

        public Car(int size) {
            framework = new Framework(size);
            System.out.println("Car init....");
        }
        public void run(){
            System.out.println("Car run...");
        }
    }

    /**
    *车身类
    */
    static class Framework {
        private Bottom bottom;
        
        public Framework(int size) {
            bottom = new Bottom(size);
            System.out.println("Framework init...");
        }
    }

    /**
     * 底盘类
     */
    static class Bottom {
        private Tire tire;
        
        public Bottom(int size) {
            this.tire = new Tire(size);
            System.out.println("Bottom init...");
        }
    }

    /**
     * 轮胎类
     */
    static class Tire {
        // 尺寸
        private int size;

        public Tire(int size){
            this.size = size;
            System.out.println("轮胎尺寸:" + size);
        }
    }
}

从上述代码中可以看到,以上程序的问题是代码耦合性过高,导致修改底层代码后,需要调整整体的代码。

2.2 解决方案

利用IOC思想,控制反转。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

具体操作是将原来由我们自己创建的下极类,改为传递的方式(也就是注入的方式)。

因为我们不需要在当前类中创建下极类了,所以下极类及时发生变化,当前类本身也无需修改任何代码,这样就完成了解耦合。

2.3 IOC思想代码

public class IocCarExample {
    public static void main(String[] args) {
        Tire tire = new Tire(20);
        Bottom bottom = new Bottom(tire);
        Framework framework = new Framework(bottom);
        Car car = new Car(framework);
        car.run();
    }

    static class Car {
        private Framework framework;

        public Car(Framework framework) {
            this.framework = framework;
            System.out.println("Car init....");
        }
        public void run() {
            System.out.println("Car run...");
        }
    }

    static class Framework {
        private Bottom bottom;

        public Framework(Bottom bottom) {
            this.bottom = bottom;
            System.out.println("Framework init...");
        }
    }

    static class Bottom {
        private Tire tire;

        public Bottom(Tire tire) {
            this.tire = tire;
            System.out.println("Bottom init...");
        }
    }

    static class Tire {
        private int size;

        public Tire(int size) {
            this.size = size;
            System.out.println("轮胎尺寸:" + size);
        }
    }
}

2.4 IOC 使用(@Autowired依赖注入)

Spring 作为一个IOC容器帮我们管理对象,其主要功能就是

存:存的是对象bean,可以使用 @Component或者其他注解(下文中会讲到)

取:告诉 Spring ,从容器中取出这个对象,赋值给当前对象的属性。也就是依赖注入 使用注解 @Autowired

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.5 IOC 优势

传统开发中,对象创建的顺序是:Car -> Framework -> Bottom -> Tire

改进之后解耦代码的创建顺序是:Tire -> Bottom -> Framework -> Car

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们发现了一个规律,通用程序的实现代码,类的创建顺序是反的,传统代码是 Car 控制并创建了Framework,Framework 创建并创建了 Bottom,依次往下,而改进之后的控制权发生的反转,不再是使用方对象创建并控制依赖对象了 ,而是把依赖对象注入将当前对象中,依赖对象的控制权不再由当前类控制了。

这样的话,即使依赖类发生任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是 IoC 的实现思想。

上述改进后的程序main中的代码就是IOC容器需要存储的数据。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从上面也可以看出来, IoC容器具备以下优点:

资源不由使用资源的双方管理,而由不使用资源的第三方管理。

  • 资源的集中管理:IOC会帮我们管理一些资源(对象等),需要的时候,直接去IOC中取即可。
  • 在创建实例的时候不需要了解其中的细节,降低了使用资源双方的依赖程度,降低耦合度。

三、IOC详解

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

3.1 从Spring容器中获取对象:Spring上下文

在学习如何存储对象之前,先来看如何从Spring容器中获取对象?

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//String上下文
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        //获取到这个类的对象
        context.getBean(Class<T> aClass);
        //根据bean名称获取bean
        context.getBean(String s);
    }
}

获取Bean的三种常用方法外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

通过Bean名称来获取Bean,如果没有显式的提供名称(BeanId),Spring容器将为该bean生成唯一的名称。

Bean的命名约定:查看官方文档

其大致意思是,bean名称以小写字母开头,然后使用驼峰式大小写

比如:

类名:UserController,Bean的名称为:userController;

类名:AccountManager,Bean的名称为:accountManage;

也有特殊情况:

比如 :

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

类名:AController,Bean的名称为:AController;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
        //上下文
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        //通过类名获取bean
        UserController usercontroller1 = context.getBean(UserContorller.class);
        //通过Bean名获取bean
        UserController usercontroller2 = context.getBean(userController);
        
        system.out.println(usercontroller1);
        system.out.println(usercontroller2);
    }
}

3.2 Bean的存储

在Spring中,要把某个对象交给IOC容器管理,需要在类上添加注解,下文中就会讲到Spring框架为服务web应用程序,提供了丰富的注解。

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

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration
  2. 方法注解:@Bean

观察下面类注解的源代码,都是@component的衍生类,因此@Component的作用范围更广。外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

@Controller控制存储器

@Service服务存储

@Repository仓库存储

@Component组件存储

@Configuration配置存储

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

最直接的一个原因就是,可以让程序员看到类注解之后,就能直接了解当前类的用途。

  • @Controller:控制层, 接收请求, 对请求进行处理, 并进行响应.
  • @Servie:业务逻辑层, 处理具体的业务逻辑.
  • @Repository:数据访问层,也称为持久层. 负责数据访问操作
  • @Configuration:配置层. 处理项目中的一些配置信息

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.3 方法注解@Bean

五大注解只能加在类上,并且只能加在自己的代码上,如果我引入了一个第三方jar包,也希望交给Spring管理,是没有办法加五大注解。比如说:数据库操作,定义多个数据源

@Bean方法一定要配合类注解使用

使用@Bean注解时,bean的名称是方法名。

3.3.1 @Bean方法注解一定要配合类注解使用

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

@Component
public class BeanConfig {
    @Bean
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}
3.3.2 @Bean方法定义多个对象

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

比如说,多数据源的场景,类是同一个,但是配置不同,指向的数据源也不同。

@Component
public class BeanConfig {
    @Bean
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2(){
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}

此时,如果通过类型获取对象的话,Spring就会给我们报错,因为有两个对象,Spring不知道取哪个。接下来根据名称来获取bean对象。

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
        //根据bean名称, 从Spring上下文中获取对象
        // User user1 = (User) context.getBean("user1");
        User user2 = (User) context.getBean("user2");
        System.out.println(user1);
        System.out.println(user2);
    }
}
3.3.3 重命名@Bean
@Bean(name = {"u1","user1"})
public User user1(){
    User user = new User();
    user.setName("zhangsan");
    user.setAge(18);
    return user;
}

3.4 扫描路径

SpringBoot 特点就是约定大于配置。其中之一的体现就是扫描路径。

默认扫描路径:启动类所在的目录及其子孙目录

如果更改启动类所在目录,而未进行路径的标注就会出现报错。

通过@ComponentScan()这个注解可以指定扫描路径。

@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		//获取Spring上下文对象
		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
				//从Spring上下文中获取对象
		User u1 = (User) context.getBean("u1");
		//使用对象
		System.out.println(u1);
	}
}

四、DI依赖注入

上面我们讲解了控制反转IoC的细节,接下来呢,我们学习依赖注入DI的细节。依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。在上面程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作。

关于依赖注入,Spring也给我们提供了三种方式:

  • 属性注入
  • 构造方法注入
  • Setter注入

4.1 属性注入

属性注入是使用@Autowired实现的。

下面是将Service类注入到Controller类中。

//Service类
import org.springframework.stereotype.Service;
@Service
public class UserService {
    public void sayHi() {
        System.out.println("Hi,UserService");
    }
}
@Controller
public class UserController {
    //注入方法1: 属性注入
    @Autowired
    private UserService userService;

    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
    }
}

4.2 构造方法注入

构造方法注入是在类的构造方法中实现注入,代码如下:

@Controller
public class UserController2 {
    //注入方法2: 构造方法
    private UserService userService;

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

    public void sayHi(){
        System.out.println("hi,UserController2...");
        userService.sayHi();
    }
}

4.3 Setter注入

Setter注入和属性的Setter方法实现类似,只不过在设置 set 方法的时候需要加上@Autowired注解,代码如下:

@Controller
public class UserController3 {
    //注入方法3: Setter方法注入
    private UserService us;

    @Autowired
    public void setUS(UserService us) {
        this.us = us;
    }

    public void sayHi(){
        System.out.println("hi,UserController3...");
        userService.sayHi();
    }
}

五、@Autowired存在问题

当一个类存在多个bean时,使用@Autowored会存在问题

@Component
public class BeanConfig {

    @Bean()
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}
@Controller
public class UserController {

        @Autowired
        private UserService11 userService;
    
        //此时注入的user,Spring不知道是user1还是user2
        @Autowired
        private User user;

        public void sayHi() {
                System.out.println("hi,UserController...");
                userService.sayHi();
                System.out.println(user);
        }
}

运行程序就会报错外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

报错原因是非唯一的Bean对象。

Spring提供了以下几种解决方案:

  • Primary
  • Qualifier
  • Resource

使用@Primary注解: 当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现.

@Component
public class BeanConfig {

    //此时Spring默认的就是user1()
    @Primary
    @Bean()
    public User user1() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

    @Bean
    public User user2() {
        User user = new User();
        user.setName("lisi");
        user.setAge(19);
        return user;
    }
}

使用@Qualifier注解:指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。

@Controller
public class UserController 
    
    @Autowired
    private UserService11 userService;

    @Qualifier("user2") //指定bean名称
    @Autowired
    private User user;

    public void sayHi() {
            System.out.println("hi,UserController...");
            userService.sayHi();
            System.out.println(user);
    }
}

使用@Resource注解:是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

@Controller
public class UserController 
    
    @Autowired
    private UserService11 userService;

    @Resource(name = "user2")
    private User user;

    public void sayHi() {
            System.out.println("hi,UserController...");
            userService.sayHi();
            System.out.println(user);
    }
}

5.1 常见面试题:@Autowired与@Resource的区别

  • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解

  • @Autowired 默认是按照类型注入,而@Resource是按照名称注入. 相比于@Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。

六、附加总结

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

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

相关文章

一五三、MAC 安装MongoDB可视化工具连接

若没有安装brew包管理工具&#xff0c;在命令行输入安装命令 /bin/bash -c “$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)”上面步骤安装完成后&#xff0c;开始安装MongoDB&#xff0c;输入安装命令&#xff1a; brew tap mongodb/brewbrew u…

LogicFlow 学习笔记——8. LogicFlow 基础 事件 Event

事件 Event 当我们使用鼠标或其他方式与画布交互时&#xff0c;会触发对应的事件。通过监听这些事件&#xff0c;可以获取其在触发时所产生的数据&#xff0c;根据这些数据来实现需要的功能。详细可监听事件见事件API。 监听事件 lf实例上提供on方法支持监听事件。 lf.on(&…

本地数据如何正确的导入SOLIDWORKS PDM系统

SOLIDWORKS 产品数据管理 (PDM) 解决方案可帮助您控制设计数据&#xff0c;并且从本质上改进您的团队就产品开发进行管理和协作的方式。使用 SOLIDWORKS PDM Professional&#xff0c;您的团队能够&#xff1a;1. 安全地存储和索引设计数据以实现快速检索&#xff1b;2. 打消关…

一键掌控,4G红外插座引领智能生活新潮流!

随着科技的进步&#xff0c;市场上出现大量带语音、手机APP可控制的智能插座产品&#xff0c;由此可看出客户对产品的功能要求也越来越高&#xff0c;追求舒适的体验感&#xff0c;特别是对操控性的要求越来越高。但是目前大部分红外遥控插座均为WiFi插座类型&#xff0c;WiFi红…

osi七层网络模型安全加固

应用层加固 应用层的攻击&#xff1a; 1、针对应用层协议的攻击&#xff1a;HTTP攻击、DNS攻击、电子邮件攻击等&#xff0c;利用应用层协议的漏洞&#xff0c;构造恶意数据包&#xff0c;是目标服务器执行恶意代码或暴露敏感信息 HTTP攻击&#xff1a;XSS、CSRF、HTTP头注入…

MySQL之高级特性(五)

高级特性 查询缓存 如何配置和维护查询缓存 一旦理解查询缓存工作的原理&#xff0c;配置起来就很容了。它也只有少数的参数可供配置。如下所示: 1.query_cache_type 是否打开查询缓存。可以设置成OFF、ON或DEMAND。DEMAND表示只有在查询语句中明确写明SQL_CACHE的语句才放…

Excel 常用技巧(五)

Microsoft Excel 是微软为 Windows、macOS、Android 和 iOS 开发的电子表格软件&#xff0c;可以用来制作电子表格、完成许多复杂的数据运算&#xff0c;进行数据的分析和预测&#xff0c;并且具有强大的制作图表的功能。由于 Excel 具有十分友好的人机界面和强大的计算功能&am…

解锁呼叫中心运营的无限潜能 - 信必优数字化解决方案

在当今瞬息万变的商业环境中,呼叫中心已不仅仅是一个简单的客户服务渠道。它是企业与客户建立联系、提供卓越体验的关键纽带。然而,传统的呼叫中心运营模式已无法满足日益增长的客户期望和业务需求。这就需要一种全新的解决方案来推动呼叫中心的数字化转型,释放其无限潜能。 信…

QListWidget 插入 item,item显示自定义界面

代码示意&#xff1a; class ItemWidget_action_cfg_w(QWidget):... # 如下方法是在指定item下插入新的item def __do_add_item(self, item):# 获取当前item rowrow self.__list_w.indexFromItem(item).row()# 注意这里没有父类&#xff0c;解释见后面说明new_item QList…

C++类对象模型、类对象的存储方式、this指针、this指针的引出、this指针的特性、C语言和C++实现Stack的对比等的介绍。

文章目录 前言一、C类对象模型1. 类对象的存储方式2. 结构体内存对齐规则 二、this指针1. this指针的引出2. this指针的特性3. C语言和C实现Stack的对比 总结 前言 C类对象模型、类对象的存储方式、this指针、this指针的引出、this指针的特性、C语言和C实现Stack的对比等的介绍…

如何在招聘中开始使用AI?

在人工智能时代&#xff0c;招聘团队面临着“用更少的钱做更多的事情”的压力&#xff1a;用更少的钱和更少的团队。根据一项调查&#xff0c;58%的受访者认为&#xff0c;“提高我们助教团队的效率&#xff0c;降低成本”是明年招聘职位的首要任务。在招聘中使用人工智能是提高…

Excel 如何复制单元格而不换行

1. 打开excle, sheet1右键单击>查看代码>插入>模块 输入代码 Sub CopyText() Updated by NirmalDim xAutoWrapper As ObjectSet xAutoWrapper New DataObject or GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")xAutoWrapper.SetText ActiveC…

东理咨询交流论坛系统

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术、B/S架构 工具&#xff1a;MyEclipse 系统展示 首页 管理员功能…

如何利用TikTok矩阵源码实现自动定时发布和高效多账号管理

在如今社交媒体的盛行下&#xff0c;TikTok已成为全球范围内最受欢迎的短视频平台之一。对于那些希望提高效率的内容创作者而言&#xff0c;手动发布和管理多个TikTok账号可能会是一项繁琐且耗时的任务。幸运的是&#xff0c;通过利用TikTok矩阵源码&#xff0c;我们可以实现自…

docker hub仓库被禁用,镜像加速器站点替换

整理 站点整理之前用的daemon.json,现更改镜像加速地址替换自己的docker加速器daemon.json前面加https:// 站点整理 之前用的daemon.json,现更改镜像加速地址 vim /etc/docker/daemon.json{"registry-mirrors": ["https://4xgbe4ey.mirror.aliyuncs.com",…

利用原生HTML + CSS + JS实现歌词滚动

对于很多音乐APP&#xff0c;都有这么一个功能&#xff0c;就是根据歌曲的进度来控制对应的歌词滚动&#xff0c;如下图所示&#xff1a; 大概这样的效果&#xff0c;我此次是使用原生的HTMLCSSJS来实现的&#xff0c;以下是具体的实现过程。 1. 数据获取与处理 对于数据来源&…

揭秘低代码平台:解锁表尾统计方案

前言 在现代Web应用中&#xff0c;数据表格是常见的界面元素之一&#xff0c;用于展示和管理大量的数据。而vxe-table作为Vue.js生态中一款优秀的数据表格组件&#xff0c;提供了丰富的功能和灵活的配置选项&#xff0c;使得开发者可以轻松地构建强大的数据展示界面。 然而&…

普林斯顿大学教授终于把算法整理成图解笔记

普林斯顿大学教授终于把算法整理成图解笔记了&#xff01;&#xff01;&#xff01; 这些年虽然学到的编程知识越来越多&#xff0c;但是我对算法却始终没搞明白&#xff0c;直到偶然间看到这份笔记&#xff0c;我才认识到这些概念是多么简单。 对于很多刚入门的小伙伴来说&am…

充电学习—1、psy框架梳理

一、linux充电驱动代码框架&#xff1a; APP 层 该部分属于电量上报的最后的环节。其主要工作是&#xff1a;监听系统广播并对 UI 作出相应更新&#xff0c;包括电池电量百分比&#xff0c;充电状态&#xff0c;低电提醒&#xff0c;led 指示灯&#xff0c;异常提醒等FrameWork…

字符串专题详解

目录 字符串hash进阶 KMP算法 next数组 KMP算法 KMP算法优化 字符串hash进阶 字符串hash是指将一个字符串S映射为一个整数&#xff0c;使得该整数可以尽可能唯一地代表字符串S。那么在一定程度上&#xff0c;如果两个字符串转换成的整数相等&#xff0c;就可以认为这两个…