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

JavaEE--SpringIoC

目录

一、IoC概述

1. 传统程序开发

2. 问题分析

3. 解决方案

4. IoC程序开发

5. IoC优势

二、IoC详解

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 扫描路径


一、IoC概述

控制反转(Inversion of Control,IoC)是一种设计原则,用于解耦组件之间的依赖关系。传统编程中,调用者主动创建和管理依赖对象;而IoC将控制权交给外部容器或框架,由容器负责创建和注入依赖对象。

接下来我们通过案例来了解一下什么是IoC。

需求:造一辆车

1. 传统程序开发

我们的设计思路是这样的:

先设计轮子(Tire),然后根据轮子的大小设计底盘(Bottom),接着根据底盘设计车身(Framework),最后根据车身设计好整个汽车(Car)。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。

最终程序的实现代码如下:

public class NewCarExample {public static void main(String[] args) {Car car = new Car();car.run();}/*** 汽车对象*/static class Car {private Framework framework;public Car() {framework = new Framework();System.out.println("Car init...");}public void run() {System.out.println("Car run...");}}/*** 车身类*/static class Framework {private Bottom bottom;public Framework() {bottom = new Bottom();System.out.println("Framework init...");}}/*** 底盘类*/static class Bottom {private Tire tire;public Bottom() {this.tire = new Tire();System.out.println("Bottom init...");}}/*** 轮胎类*/static class Tire {// 尺寸private int size;public Tire() {this.size = 17;System.out.println("轮胎尺寸: " + size);}}
}

2. 问题分析

这样的设计看起来没问题,但是可维护性却很低。

如果接下来需求有了变更:随着对车的需求量越来越大,个性化需求也会越来越多,我们需要加工多种尺寸的轮胎。

那这个时候就需要对上面的程序进行修改,修改后的代码如下所示:

修改之后,其他调用程序也会报错,我们需要继续修改:

完整代码如下:

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);}}
}

从以上代码可以看出:当最底层代码改动之后,整个调用链上的所有代码都需要修改(程序的耦合度非常高)。

3. 解决方案

我们尝试换一种思路,我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘,底盘依赖车身,车身依赖汽车。

如何来实现呢:
我们可以尝试不在每个类中自己创建下级类,如果自己创建下级类就会出现当下级类发生改变操作,自己也要跟着修改。此时,我们只需要将原来由自己创建的下级类,改为传递的方式(也就是注入的方式),因为我们不需要在当前类中创建下级类了,所以下级类即使发生变化(创建或减少参数),当前类本身也无需修改任何代码,这样就完成了程序的解耦。

4. 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);}}
}

代码经过以上调整,无论底层类如何变化,整个调用链是不用做任何改变的,这样就完成了代码之间的解耦,从而实现了更加灵活、通用的程序设计了。

5. IoC优势

  • 降低耦合度:组件不直接依赖具体实现。
  • 增强可测试性:便于模拟依赖进行单元测试。
  • 提高可维护性:依赖关系集中管理,修改时影响小。
  • 灵活性:运行时动态替换依赖实现。

Spring就是一种IoC容器,帮助我们来做了这些资源管理。

二、IoC详解

1. Bean的存储

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

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

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration
  2. 方法注解:@Bean
1.1 @Controller(控制器存储)

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

import org.springframework.stereotype.Controller;@Controller
public class HelloController {public void print() {System.out.println("Hello control");}
}

从Spring容器中获取这个对象:

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

ApplicationContext:Spring上下文

因为对象都交给Spring管理了,所以获取对象要从Spring中获取,那么就得下得到Spring的上下文。

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

若把@Controller删掉,观察运行结果:

报错信息显示:找不到bean。

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

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.beans.factory;import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;public interface BeanFactory {String FACTORY_BEAN_PREFIX = "&";char FACTORY_BEAN_PREFIX_CHAR = '&';//1Object getBean(String name) throws BeansException;//2<T> T getBean(String name, Class<T> requiredType) throws BeansException;//3Object getBean(String name, Object... args) throws BeansException;//4<T> T getBean(Class<T> requiredType) throws BeansException;//5<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;//6<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);//7<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);boolean containsBean(String name);boolean isSingleton(String name) throws NoSuchBeanDefinitionException;boolean isPrototype(String name) throws NoSuchBeanDefinitionException;boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;@NullableClass<?> getType(String name) throws NoSuchBeanDefinitionException;@NullableClass<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;String[] getAliases(String name);
}

常用的是上述1,2,4种,这三种方式获取到的bean是一样的。其中1,2种都涉及到根据名称来获取对象,Bean的名称可以参考官方文档。

程序开发人员不需要为bean指定名称(Beanld),如果没有显式的提供名称(Beanld),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 SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);HelloController bean = context.getBean(HelloController.class);bean.print();HelloController bean2 = (HelloController) context.getBean("helloController");bean2.print();HelloController bean3 = context.getBean("helloController", HelloController.class);bean3.print();System.out.println(bean);System.out.println(bean2);System.out.println(bean3);}
}

三种方法都成功从Spring中获取到Controller对象,并执行Controller的print方法。并且地址一样,说明是同一个对象。

ApplicationContext VS BeanFactory

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

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

import org.springframework.stereotype.Service;@Service
public class UserService {public void print() {System.out.println("Hello service");}
}

读取bean的代码:

@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();}
}

1.3 @Repository(仓库存储)

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

import org.springframework.stereotype.Repository;@Repository
public class UserRepository {public void print() {System.out.println("Hello Repository");}
}

读取bean的代码:

@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();}
}

1.4 @Component(组件存储)

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

import org.springframework.stereotype.Component;@Component
public class UserComponent {public void print() {System.out.println("Hello component");}
}

读取bean的代码:

@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();}
}

1.5 @Configuration(配置存储)

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

import org.springframework.context.annotation.Configuration;@Configuration
public class UserConfig {public void print() {System.out.println("Hello configuration");}
}

读取bean的代码:

@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();}
}

2. 为什么需要这么多注解?

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

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

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

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

其实这些注解里面都有一个注解@Component,说明它们本身就是属于@Component  的"子类"。@Component是一个元注解,也就是说可以注解其他类注解,如@Controller,@Service,@Repository等。这些注解被称为@Component的衍生注解。

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

3. 方法注解@Bean

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

  1. 使用外部包里的类,没办法添加类注解
  2. 一个类,需要多个对象,比如多个数据源

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

下来看方法注解如何使用,创建一个Student类:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@NoArgsConstructor
@AllArgsConstructor
@Data
public class Student {private String name;private Integer age;
}
public class StudentComponent {@Beanpublic Student student() {return new Student("zhangsan", 18);}
}

尝试获取Bean对象中的student:

@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);Student student = context.getBean(Student.class);System.out.println(student);}
}

但是未能成功获取。

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

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

@Component
public class StudentComponent {@Beanpublic Student student() {return new Student("zhangsan", 18);}
}

再次运行:

3.2 定义多个对象

对于同一个类,如何定义多个对象呢?(比如多数据源的场景,类是同一个,但是配置不同,志向不同的数据源)

@Bean的使用:

@Component
public class StudentComponent {@Beanpublic Student student1() {return new Student("zhangsan", 18);}@Beanpublic Student student2 () {return new Student("lisi", 20);}
}

根据类型获取到的是哪个对象呢?

@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);Student student = context.getBean(Student.class);System.out.println(student);}
}

报错信息显示:期望只有一个匹配,结果发现了两个。

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

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

@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);//根据Bean名称,从Spring上下文获取对象Student student1 = (Student) context.getBean("student1");Student student2 = (Student) context.getBean("student2");System.out.println(student1);System.out.println(student2);}
}

3.3 重命名Bean

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

@Bean(name = {"s","stu"})
public Student student() {return new Student("zhangsan", 18);
}

此时我们使用s或stu就可以获取到Student对象了:

@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);Student student1 = (Student) context.getBean("s");Student student2 = (Student) context.getBean("stu");System.out.println(student1);System.out.println(student2);}
}

“name=”可以省略:

@Bean({"s","stu"})
public Student student() {return new Student("zhangsan", 18);
}

只有一个名称时,“{}”也可以省略:

@Bean("s")
public Student student() {return new Student("zhangsan", 18);
}
3.4 扫描路径

下面我们先修改启动类的代码:

@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);Student bean = (Student) context.getBean("s");System.out.println(bean);}
}

再修改项目工程的目录结构,测试bean对象是否生效:

使用五大注解声明的bean,要想生效,还需要配置扫描路径,让Spring扫描到这些注解,也就是通过@ComponentScan来配置扫描路径。

@ComponentScan("com.bite.springiocdemo")
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);Student bean = (Student) context.getBean("s");System.out.println(bean);}
}

在之前的代码中,@ComponentScan注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解@SpringBootApplication中了,默认扫描的范围是SpringBoot启动类所在包及其子包。在配置类上添加@ComponentScan注解,该注解默认会扫描该类所在的包下所有的配置类。


创作不易,给个三连支持一下吧

http://www.dtcms.com/a/494993.html

相关文章:

  • macOS版Sublime简记
  • 机器学习(1)- 机器学习简介
  • 系统架构设计师备考第44天——软件架构演化方式的分类和原则
  • 郑州网站建设公司排行超级工程网站建设上海中心大厦
  • 睢县做网站酒店怎样做网站好评
  • Azure OpenAI 压测和配额规划完整指南
  • Lua C API 中的 lua_rawseti 与 lua_rawgeti 介绍
  • 基于单片机的便携式温湿度检测烘干机设计
  • lua对象池管理工具剖析
  • 网站建设选择什么系统好福建省建设工程执业注册管理中心网站
  • 桐庐建设局网站域名解析入口
  • 数据库flask访问
  • 每日Reddit AI信息汇总 10.17
  • 高可用、高性能、高扩展集群核心区别详解以及高可用介绍
  • 【Linux网络】初识网络,网络的基础概念
  • 手机端网站动效类怎么做seo搜索优化 指数
  • 递归与迭代——力扣101.对称二叉树
  • 中扬立库 × 宁波卡帝亚:小家电之乡的仓储革命,破解制造仓储瓶颈
  • Linux《网络基础》
  • 网络层(IP)
  • 近红外相机在半导体制造领域的应用
  • 网站制作 深圳信科网络公司对比网站
  • 百度旗下所有app列表温州seo排名优化
  • 怎样做让百度收录网站域名设计方案翻译
  • 【whistle】whistle的安装和代理配置
  • 智能油脂润滑系统:降低维护成本与提升生产效率的双赢之道
  • CC17-加油站
  • 【办公类-120-01】20251016 UIBOT下载小说做成docx
  • RestTemplate发送Post请求报错:414 URI Too Long
  • 热力图:从逸出数据到宏观模式识别