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

Spring IoCDI_2_使用篇

通过前一篇文章,我们已经知道了Spring IoC和DI的基本概念,这篇文章我们将系统地学习Spring-IoC和DI的操作。

前面我们提到的IoC控制反转,就是将对象的控制权交给Spring的IoC容器,由IoC容器创建及管理对象。也就是bean(Spring管理的对象统称为“bean”)的存储。

IoC详解

Bean的存储

将对象存储在Spring中共有两种注解类型可以实现:

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

2、方法注解:@Bean

@Controller(控制器存储)

如图,在启动类中创建一个子目录Controller:

类中代码如下:

@Controller
public class HelloController {public void sayHi(){System.out.println("hello Controller……");}
}

如何知道当前对象已经存在Spring容器中了呢?

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

点进Spring的启动类中,添加上如下代码:

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

观察运行结果,发现成功获取到了Controller对象,并执行了sayHi()方法:把@Controller删去重新执行一遍代码:

将错误信息翻译一下:Spring容器中没有找到这个Bean。

获取Bean的其他方式

上述代码是根据类型来查找对象,如果Spring容器中,同一类型存在多个bean的话,怎么来获取呢?

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

观察getBean方法的源码:

常用的是上述1,2,4种,这三种方式,获取到的bean是一样的(单例模式)。

其中1,2种都涉及到根据名称来获取对象。bean的名称是什么呢? 

刚才我们讲了bean是spring框架在运行时管理的对象,Spring会给管理的对象起一个名字,就是bean的名称,bean的名称在Spring中是唯一的,类似于学生的学号或者人的身份证号。

Bean的命名约定

默认情况下,首字母小写的驼峰表示(如:我们刚才写的HelloController,Bean的名称为:helloController)。特殊情况下,如果前两个字母均为大写,Bean的名称就是原来的类名(如:UController,Bean的名称是UController)。 


根据这个命名规则,我们来获取Bean:

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);//根据Bean的名称获取BeanHelloController helloController = (HelloController) run.getBean("helloController");//根据Bean的名称和类型获取BeanHelloController bean = run.getBean("helloController", HelloController.class);//根据Bean的类型获取BeanHelloController bean1 = run.getBean(HelloController.class);System.out.println(helloController);System.out.println(bean1);System.out.println(bean);

运行结果:

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

常见面试题:ApplicationContext VS BeanFactory 

1、关系上,他们是父子关系,BeanFactory的功能ApplicationContext都有。除此之外,ApplicationContext还具备环境、资源管理等功能。

2、性能上

Beanfactory是懒加载(什么时候使用什么时候加载),ApplicationContext是提前加载(一开始就把全部对象加载完了)。

@Service(服务存储)

Service类代码:

@Service
public class UserService {public void sayHi(){System.out.println("hello service");}
}

获取Bean的代码 

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserService bean = run.getBean(UserService.class);bean.sayHi();

运行结果: 

 @Repository(仓库存储)

Repository类:

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

获取Bean的代码: 

//获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserRepository bean = run.getBean(UserRepository.class);bean.sayHi();

运行结果:

@Component(组件存储)

Component类:

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

获取Bean: 

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserComponent bean = run.getBean(UserComponent.class);bean.sayHi();

运行结果:

 @Configuration(配置存储)

Configuration类:

@Configuration
public class UserConfig {public void sayHi(){System.out.println("hello configuration");}
}

获取Bean :

 //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserConfig bean = run.getBean(UserConfig.class);bean.sayHi();

运行结果:

为什么要有那么多的类注解?

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

@Controller:控制层,接收请求,对请求进行处理,并进行响应。

@Service:业务逻辑层,处理具体的业务逻辑。

@Reposity:数据访问层,也称为持久层,负责数据访问操作。

@Configuration:配置层,处理项目中的一些配置信息。

调用流程如下:

查看这些注解的源码: 可以发现它们里面都有一个相同的@Component(元注解)注解,说明它们都是@Component的“子类”。

“@Controller”,@Service和@Repository用于更具体的用例(分别在控制层,业务逻辑层,持久化层)。

方法注解@Bean

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

1、使用外部包里的类,没办法添加类注解。

2、一个类需要多个对象比如多个数据源。

这种情况我们就需要使用方法注解@Bean。

我们先来看看方法注解如何使用:

先创建一个外部包类UserInfo:

@Data
public class UserInfo {private String name;private Integer age;private Integer id;public UserInfo(){}public UserInfo(String name) {this.name = name;}
}

 使用@Bean注解:

public class BeanConfig {@Beanpublic UserInfo userInfo(){return new UserInfo("zhangsan");}
}

获取Bean:

   ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserInfo bean = run.getBean(UserInfo.class);System.out.println(bean)

将代码写入后发现执行结果如下 :

找不到Bean,这是为什么呢?

在Spring框架的涉及中,方法注解@Bean要配合类注解才能将对象正常存储到Spring容器中。

修改代码如下: 

@Configuration
public class BeanConfig {@Beanpublic UserInfo userInfo(){return new UserInfo("zhangsan");}
}

定义多个对象

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

修改@Bean类如下:

@Configuration
public class BeanConfig {@Beanpublic UserInfo userInfo(){return new UserInfo("zhangsan");}@Beanpublic UserInfo userInfo1(){return new UserInfo("lisi");}@Beanpublic UserInfo userInfo2(){return new UserInfo("wangwu");}
}

如果我们此时通过类型获取对象,获取的是哪个对象呢? 

//获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserInfo bean = run.getBean(UserInfo.class);System.out.println(bean);

运行结果:报错信息显示:期望只有一个匹配,但是根据这个类型找到了3个Bean,userInfo,userInfo1,userInfo2。

接下来我们可以通过Bean的名称来获取Bean,我们刚才讲了类注解的Bean的名称,那方法注解Bean的名称又是什么呢?

其实就是——方法名。

代码如下:

         //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);UserInfo bean = (UserInfo) run.getBean("userInfo");System.out.println(bean);UserInfo bean1 = (UserInfo) run.getBean("userInfo1");System.out.println(bean1);UserInfo bean2 = (UserInfo) run.getBean("userInfo2");System.out.println(bean2);

运行结果:

Bean的名称总结 

1、五大注解:

类名:首字母小写

        如果类名前两位为大写,bean的名称为类名本身

2、@Bean

bean 名称:方法名

扫描路径

使用前面学习的注解声明的bean,一定会生效吗?

不一定(原因:bean想要生效,还需要被Spring扫描)

将启动类放到Controller目录下: 写上如下代码:

   //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi();UserService bean = run.getBean(UserService.class);bean.sayHi();

运行结果:可以看到HelloController的bean对象能够正常获取并使用,而UserService的bean对象却显示没有找到。

这是为什么呢?

Spring的默认扫描路径是当前目录和当前目录下的子目录。我们可以通过@ComponentScan来配置扫描路径。

修改代码如下:

@ComponentScan("com.example.springioc")
@SpringBootApplication
public class SpringIoCApplication {public static void main(String[] args) {//获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi();UserService bean = run.getBean(UserService.class);bean.sayHi();}
}

运行结果:

推荐做法:

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

DI详解

上面我们讲解了控制反转IoC的细节,接下来,我们学习依赖注入DI的细节。

依赖注入是一个过程,是指IoC容器在创建Bean时,去提供运行时所依赖的资源,而资源指的就是对象。

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

1、属性注入

2、构造方法注入

3、Setter注入

属性注入

属性注入需要使用@Autowired这个注解。

例如:在HelloController类中注入UserSeverice对象。

HelloController类代码如下:

@Controller
public class HelloController {@Autowiredprivate UserService userService;public void sayHi(){userService.sayHi();System.out.println("hello Controller……");}
}

 获取Bean并调用sayHi方法:

//获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi()

执行结果:

去掉 @Autowired,重新执行:

出现空指针异常。

构造方法注入 

HelloController代码:


@Controller
public class HelloController {private UserService userService;public HelloController(UserService userService) {this.userService = userService;}public void sayHi(){userService.sayHi();System.out.println("hello Controller……");}
}

获取使用Bean代码 :

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi()

执行结果: 那如果我给HelloController再加上一个无参构造方法呢?

代码修改如下:

@Controller
public class HelloController {private UserService userService;public HelloController() {}public HelloController(UserService userService) {this.userService = userService;}public void sayHi(){userService.sayHi();System.out.println("hello Controller……");}
}

可以看到报了一个空指针异常,这是因为Spring会默认使用无参的构造方法,这就导致了userService是一个空对象 。

解决办法:

为构造方法添加@Autowired注解:

@Controller
public class HelloController {private UserService userService;public HelloController() {}
@Autowiredpublic HelloController(UserService userService) {this.userService = userService;}public void sayHi(){userService.sayHi();System.out.println("hello Controller……");}
}

那么如果我注入两个对象,并且加入带一个参数的构造方法和带两个参数的构造方法(不加注解的情况)呢?

代码如下:

@Controller
public class HelloController {private UserService userService;private UserRepository userRepository;public HelloController(UserService userService) {this.userService = userService;}public HelloController(UserService userService, UserRepository userRepository) {this.userService = userService;this.userRepository = userRepository;}public void sayHi(){userRepository.sayHi();userService.sayHi();System.out.println("hello Controller……");}
}

 获取使用Bean:

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi()

执行结果: 

翻译一下这两个错误日志:构造方法有两个,我不知道应该使用哪个构造方法(没有默认构造方法)。

我们为两个参数的构造方法添加上@Autowired注解:

@Controller
public class HelloController {private UserService userService;private UserRepository userRepository;public HelloController(UserService userService) {this.userService = userService;}@Autowiredpublic HelloController(UserService userService, UserRepository userRepository) {this.userService = userService;this.userRepository = userRepository;}public void sayHi(){userRepository.sayHi();userService.sayHi();System.out.println("hello Controller……");}
}

代码成功执行:

总结:

构造函数注入

1、只有一个构造函数的情况,不需要加@Autowired

2、如果有多个构造函数,需要指定默认的构造函数(通过@Autowired指定,如果未指定,默认使用无参的构造函数

构造函数规范

1、如果添加构造函数,把无参构造函数显示添加

依赖注入:

添加构造函数时,使用@Autowired告知Spring要使用的构造函数。 

 Setter方法注入

Setter方法注入和属性注入一样需要添加@Autowired注解,否则会报空指针异常

@Controller
public class HelloController {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi(){userService.sayHi();System.out.println("hello Controller……");}
}

 获取使用Bean:

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi()

执行结果:

三种注入方式优缺点分析(八股文): 

@Autowired存在问题

当同一类型存在多个bean时,使用@Autowired会存在问题。

刚才的Beanconfig类中就存在多个bean:

@Configuration
public class BeanConfig {@Beanpublic UserInfo userInfo(){return new UserInfo("zhangsan");}@Beanpublic UserInfo userInfo1(){return new UserInfo("lisi");}@Beanpublic UserInfo userInfo2(){return new UserInfo("wangwu");}
}

同样将它注入HelloController类中: 

@Controller
public class HelloController {@Autowiredprivate UserInfo userInfo;public void sayHi(){System.out.println(userInfo);System.out.println("hello Controller……");}
}

获取Bean:

    //获取Spring上下文对象ConfigurableApplicationContext run = SpringApplication.run(SpringIoCApplication.class, args);HelloController bean1 = run.getBean(HelloController.class);bean1.sayHi()

可以看到,此时是能正常运行的: 但是倘若我给要注入的对象换个名字:

@Controller
public class HelloController {@Autowiredprivate UserInfo user;public void sayHi(){System.out.println(user);System.out.println("hello Controller……");}
}

执行结果: 

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

如何解决上述问题呢?Spring提供了三种解决方案:

  • @Primary
  • @Qualifier
  • @Resource

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

代码如下:

@Configuration
public class BeanConfig {@Primary//指定该Bean未默认Bean的实现@Beanpublic UserInfo userInfo(){return new UserInfo("zhangsan");}@Beanpublic UserInfo userInfo1(){return new UserInfo("lisi");}@Beanpublic UserInfo userInfo2(){return new UserInfo("wangwu");}
}

重新执行 :

使用@Qualifier注解:指定当前要注入的bean对象。在@Qualifier的value属性中,指定注入的bean的名称。(@Qualifiler注解不能单独使用,必须配合@Autowired使用)。

HelloController代码如下:

@Controller
public class HelloController {@Qualifier("userInfo1")@Autowiredprivate UserInfo user;public void sayHi(){System.out.println(user);System.out.println("hello Controller……");}
}

执行结果:

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

代码如下: 

@Controller
public class HelloController {@Resource(name = "userInfo2")private UserInfo user;public void sayHi(){System.out.println(user);System.out.println("hello Controller……");}
}

结果:

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

@Autowired是spring框架提供的注释,而@Resource是JDK提供的注释。

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

http://www.lryc.cn/news/591267.html

相关文章:

  • JAVA中的Map集合
  • Linux内存系统简介
  • AI关键词SEO最新实战全攻略提升排名
  • ubuntu--curl
  • Java学习-----消息队列
  • 3.2 函数参数与返回值
  • 通过轮询方式使用LoRa DTU有什么缺点?
  • Stone3D教程:免编码制作在线家居生活用品展示应用
  • Spring,Spring Boot 和 Spring MVC 的关系以及区别
  • WSL2 离线安装流程
  • 元宇宙与Web3的深度融合:构建沉浸式数字体验的愿景与挑战
  • 手写Promise.all
  • C#中的LINQ解析
  • Level-MC 5”雪原“
  • 探微“元宇宙”:概念内涵、形态发展与演变机理
  • MTK平台--如何查询手机连接的TX速率和带宽
  • 【PY32】使用轩微烧录器烧录PY32微控制器
  • 跨域通信inframe高级
  • Nginx/OpenResty HTTP 请求处理阶段与 Lua 实践全解20250717
  • Java中的字符串——String,StringBuilder,StringBuffer
  • 基于邻域统计分析的点云去噪方法
  • 【测试100问】没有接口文档的情况下,如何做接口测试?
  • TC500R立式加工中心主轴箱机械结构设计cad【11张】三维图+设计说明书
  • 【后端】.NET Core API框架搭建(7) --配置使用Redis
  • Android本地浏览PDF(Android PDF.js 简要学习手册)
  • React hooks——useReducer
  • 面试Redis篇-深入理解Redis缓存穿透
  • 基于YOLOv11的水面垃圾智能检测系统
  • halcon 模板匹配
  • 高精度加法模版介绍