全栈之路-杂篇-探究spring IOC的核心机制
spring IOC的核心机制就是实例化与注入,那么其实从前从来没有想过为什么在spring注入的时候遇到多个接口的实现bean的情况下,到底spring会注入哪个bean的实例呢?当然之前的项目中也没有遇到过这种情况,现在好好的分析学习一下,spring IOC的核心机制,通过一个简单的例子进行分析
一、模式注解
1、@Component注解(基础的注解)
@Component注解作用就是将 组件/类/bean 加入到IOC容器中的,当一个类加上@Component注解之后,就会被spring加入到IOC容器中,
(1)加入容器中代码示例
1 @Component 2 public class Diana { 3 4 public void q() { 5 System.out.println("Diana Q"); 6 } 7 8 public void w() { 9 System.out.println("Diana W"); 10 } 11 12 public void e() { 13 System.out.println("Diana E"); 14 } 15 16 public void r() { 17 System.out.println("Diana R"); 18 } 19 }
(2)注入代码示例
注入的时候,只需要加上@Autowired就行了,这样的话,这个类就可以使用了
1 @Autowired 2 private Diana diana;
2、@Service、@Controller、@Repository注解
注意:启动文件和Controller之间是有一个桥接点的,将Controller中的访问路由地址注册到IOC容器中的,通过IOC进行类的实例化的
这三个注解是以@Component注解为基础的,本质上没有什么区别,这三个注解主要是用来标明一个类的作用,例如@Service表示该类是一个服务,并且也是有@Component注解的功能,就是加入到IOC容器中,@Controller表示该类是一个控制器,并且有@Component注解的功能,@Repository注解则是标明该类是做数据库访问的类,同时具有@Component注解的作用,但是如果没有明确的目的的话,一般使用@Component注解
3、@Configuration注解
(1)关于这个注解的话,感觉有些别扭,先看一下如何使用的,示例代码:
接口实现类代码:
1 public class Camille implements ISkill { 2 3 private String skillName = "Camille R"; 4 5 public Camille() { 6 System.out.println("Camille constructor..."); 7 } 8 9 @Override 10 public void q() { 11 System.out.println("Camille Q"); 12 } 13 14 @Override 15 public void w() { 16 System.out.println("Camille W"); 17 } 18 19 @Override 20 public void e() { 21 System.out.println("Camille E"); 22 } 23 24 @Override 25 public void r() { 26 System.out.println(this.skillName); 27 } 28 }
注解的使用(这个需要新建一个配置类):
1 @Configuration 2 public class HeroConfiguration { 3 4 @Bean 5 public ISkill camille (){ 6 return new Camille(); 7 } 8 }
这样的话,这个接口的实现类就可以添加到spring容器中去了,注入之后,就可以进行使用了!
(2)@Configuration详解
@Configuration注解其实就是用来替换之前spring中的XML配置中的beans标签和bean标签的,也可以说是一种简化,其实可以看依稀之前的配置文件的代码:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 6 7 <bean id="caille" class="com.lin.missyou.sample.hero.Camille"> 8 <property name="name" value="Camille"></property> 9 </bean> 10 11 <beans>
## 探究spring为什么偏爱配置
开闭原则(Open Closed Principle),简称就是OCP,我们总会提及到的就是开发中的变化,我理解的就是变量,这些变化是不可避免的,我们能做的只是隔离这些变化,恰好,配置文件就是起到隔离这些变化的解决办法
## 但是,为什么要隔离到配置文件中呢?
---配置文件具有集中性
---清晰(没有业务逻辑)
## spring中配置分类
---常规配置 key-value键值对的形式
---xml配置 类/对象为组织单位的配置
用@Configuration和@Bean注解来学习一下spring中最基础的应用,用配置文件的方式来实现OCP这种开闭原则,代码如下:
#创建接口
1 public interface IConnect { 2 void connect(); 3 }
#创建接口的实现类
1 public class MySQL implements IConnect { 2 3 private String ip; 4 private Integer port; 5 6 public MySQL() { 7 8 } 9 10 public MySQL(String ip, Integer port) { 11 this.ip = ip; 12 this.port = port; 13 } 14 15 @Override 16 public void connect() { 17 System.out.println(this.ip+":"+this.port); 18 } 19 20 public void setIp(String ip) { 21 this.ip = ip; 22 } 23 24 public void setPort(Integer port) { 25 this.port = port; 26 } 27 }
#创建配置类
1 @Configuration 2 public class DatabaseConfiguration { 3 4 @Value("${mysql.ip}") 5 private String ip; 6 7 @Value("${mysql.port}") 8 private Integer port; 9 10 @Bean 11 public IConnect mysql(){ 12 return new MySQL(this.ip,this.port); 13 } 14 }
#在.properties文件中声明配置:
mysql.ip=127.0.0.1
mysql.port=3306
#看一下接口的调用:
1 @Autowired 2 private IConnect iConnect; 3 4 @GetMapping("/test1") 5 public void test1() { 6 iConnect.connect(); 7 }
这种事spring中很常用的一种将属性值放入到配置文件中进行读取的模式,其实@Configuration其实是提供了一种编程模式,采用的是配置的方式进行编程,这样的代码很容易符合OCP原则的
二、附加知识点
1、探究IOC对象 实例化 注入时机的问题
默认的情况是在springboot启动的时候,IOC容器已经开始对象的实例化并且将实例化的对象注入到代码片段中,但是我们可以使用@Lazy注解进行延迟实例化对象,使用时示例:
1 @Component 2 @Lazy 3 public class Diana implements ISkill { 4 5 public Diana(){ 6 System.out.println("Diana constructor..."); 7 } 8 9 public void q() { 10 System.out.println("Diana Q"); 11 } 12 13 public void w() { 14 System.out.println("Diana W"); 15 } 16 17 public void e() { 18 System.out.println("Diana E"); 19 } 20 21 public void r() { 22 System.out.println("Diana R"); 23 } 24 }
注意这里存在着一个问题,当需要注入该对象的类是默认的立即实例化对象的,那么这个@Lazy注解的延迟加载作用是不存在的,这个类也会被立即实例化,只有在注入方同样也加上@Lazy注解之后才能实现延迟加载
1 @RestController 2 @Lazy 3 @RequestMapping(value = "/v1/banner") 4 public class BannerController { 5 6 @Autowired 7 private Diana diana; 8 }
2、注入方式
注入方式主要有三种:构造方法注入、属性注入、setter注入,我们常用的是比较简便的属性注入的方式,因为写起来比较简单
(1)属性注入
1 @Autowired 2 private Diana diana;
(2)构造方法注入
1 private Diana diana; 2 3 @Autowired 4 public BannerController(Diana diana){ 5 this.diana = diana; 6 }
构造方法上的@Autowired注解是可以不用添加的,当然你加上的话,也是没有任何问题的,加上与不加上对注入的结果没有影响,都是能够注入成功的
(3)Setter注入
1 private Diana diana; 2 3 @Autowired 4 public void setDiana(Diana diana) { 5 this.diana = diana; 6 }
3、探究依赖接口的注入方式
先把问题讲述一下,当我们在做项目的时候往往会在service层创建接口,然后创建接口的实现类,但是我们遇到的一般是一个接口有一个实现类的情况,那么当一个接口有多个实现类的时候,我们注入的时候是实现哪一个实现类呢?我们如何利用spring进行控制接口的实现类呢?这个就是我们这里需要面对和解决的问题。为了更好的解释这个问题,用代码进行详细的说明一下:
(1)问题的代码说明
接口代码:
1 public interface ISkill { 2 void q(); 3 void w(); 4 void e(); 5 void r(); 6 }
接口的实现类(1):
1 @Component 2 public class Diana implements ISkill { 3 4 public Diana(){ 5 System.out.println("Diana constructor..."); 6 } 7 8 @Override 9 public void q() { 10 System.out.println("Diana Q"); 11 } 12 13 @Override 14 public void w() { 15 System.out.println("Diana W"); 16 } 17 18 @Override 19 public void e() { 20 System.out.println("Diana E"); 21 } 22 23 @Override 24 public void r() { 25 System.out.println("Diana R"); 26 } 27 }
接口实现类(2):
1 @Component 2 public class Irelia implements ISkill { 3 4 public Irelia(){ 5 System.out.println("Irelia constructor..."); 6 } 7 8 @Override 9 public void q() { 10 System.out.println("Irelia Q"); 11 } 12 13 @Override 14 public void w() { 15 System.out.println("Irelia W"); 16 } 17 18 @Override 19 public void e() { 20 System.out.println("Irelia E"); 21 } 22 23 @Override 24 public void r() { 25 System.out.println("Irelia R"); 26 } 27 }
思考:这个下面注入的接口,这个接口是有两个实现类的,并且这两个实现类都已经加入到spring IOC容器中了,我们在使用的时候,到底是哪个实现类呢?这个该如何控制呢?这就是我们要探究的问题!
1 @Autowired 2 private ISkill diana; 3 4 @GetMapping("/test") 5 public String test() { 6 // 思考?这样的话 到底是用哪个接口的实现类呢? 7 diana.r(); 8 return "Hello world 你好世界!!!"; 9 }
(2)解决方案的探究
变量的名字对这个注入的实现类是有影响的?!这个是什么原因呢?这里需要知道@Autowired的注入方式:
说这个之前,说明一下spring注入过程(如何寻找接口的实现类进行注入操作,分情况说明一下):
## 首先,找不到任何一个bean的情况下,spring会报错提示
## 其次,如果找到一个实现bean的话,直接注入
## 接下来,找到多个,两个及两个以上,并不一定会报错,会按照字段的名字推断选择哪个bean
## @Autowired被动注入方式,这个就是bytype和byname两种注入方式
---bytype 按照类型注入(默认的注入方式)
当接口的实现类注入的时候,spring会在容器中去寻找实现了接口的实现类,当发现只有一个的时候,就会使用这唯一的一个实现类,这个就是按照类型的注入,但是,当容器中存在这个接口的两个实现类的时候,那么spring是不知道要给你注入哪个实现类的,这个时候你如果还坚持使用按照类型注入的方式,spring是会给你报错的。代码说明一下:(注意标红的那个名字,不是diana或者irelia,而是随便的一个名字,此时如果IOC容器中是存在两个次接口的实现类的话,启动就会报错的,如果只有一个实现类,就会注入那唯一的一个)
1 @Autowired 2 private ISkill iSkill; 3 4 @GetMapping("/test") 5 public String test() { 6 iSkill.r(); 7 return "Hello world 你好世界!!!"; 8 }
---byname 按照名字注入
## @Autowired主动注入的方式
@Qualifier(value = "指定的注入bean字段名"),这种就可以指定需要注入的实现bean,可以看一下代码的实现(下面注入的就是Irelia实现类):
1 @Autowired 2 @Qualifier(value = "irelia") 3 private ISkill diana;
4、总结面向对象中变化的应对方案
(1)制定一个interface,然后用多个类实现同一个interface(设计模式中策略模式),这个策略模式的变化方案有以下几种的:
## byname 切换bean name
## @Qualifier注解 指定bean
## 有选择的只注入一个bean,注释掉某个bean上的@Component注解
## 使用@Primary注解,来进行实现某一个bean的优先注入
## 使用条件注解来进行条件的限制来实现
(2)一个类、属性 解决变化(此种方法不够灵活,扩展性较差)
内容出处:七月老师《从Java后端到全栈》视频课程
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步