全栈之路-杂篇-探究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后端到全栈》视频课程

七月老师课程链接:https://class.imooc.com/sale/javafullstack

posted @ 2020-02-11 20:59  ssc在路上  阅读(325)  评论(0编辑  收藏  举报