zoukankan      html  css  js  c++  java
  • 全栈之路-杂篇-探究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

  • 相关阅读:
    Javaweb中的监听器
    Maven
    Ajax
    jQuery
    Spring入门
    spring boot实战——微信点餐系统02:接口信息,分页工具,gson, 注解信息,表单验证,字段返回(时间处理,null处理)
    Nginx
    Spring Data JPA
    spring boot实战——微信点餐系统01:开始代码
    spring boot实战——微信点餐系统00:项目设计,环境搭建
  • 原文地址:https://www.cnblogs.com/ssh-html/p/12229686.html
Copyright © 2011-2022 走看看