当前位置:   article > 正文

Spring IoC实现及原理

Spring IoC实现及原理


控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法

一、IoC

1、Spring Ioc 的由来

首先,我们要清楚Spring IoC 是一种设计思想,假如一个系统有大量的组件,都需要我们手动去管理其生命周期和维持它们之间的关系,对于程序员来说,是非常不好管理的,这不仅大大增加了系统的复杂度,同时还会使它们的依赖具有很强的耦合性,那么为了更好的管理组件,由此引入了Spring IoC的思想,利用Spring容器去管理大量组件。其是Spring IoC 就相当于一个中间层,用来负责解耦的容器,负责创建,管理,销毁bean的过程。那么IoC容器主要有的好处:

  • 一是容器管理所有的对象,我们不需要去手动创建对象;
  • 二是容器管理所有的依赖项,在创建实例的时候不需要了解其中的细节。

2、IoC思想

IoC(Inversion of Control,控制反转),将对象的控制权交给 IOC 容器,是由容器(Spring)创建,管理,销毁bean的过程,是Spring的核心。在日常的开发过程中,我们一般是通过new来创建对象的;当我们使用IoC控制反转时,创建对象是由Spring容器来替我们new出对象实例。目的是为了降低耦合度。

3、IoC的实现

3.1 、实现原理

利用(*反射+工厂*)技术,根据配置文件中给出的类名生成相应的对象。

3.2、 实现过程

​ Spring通过配置文件描述Bean及Bean之间的依赖关系(或者是注解@annotation),利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。(其中我们主要讲解的部分为注解形式,xml形式会以一个实例来表示。

3.2.1、xml方式

下面来看一个例子:

public class Hello {
    private String name;
    @Override
    public String toString() {
        return "Hello{" +
                "name='" + name + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("Hello,"+name);
    }
}
//测试类
public class Test {
    public static void main(String[] args) {
        //解析bean.xml文件,生成管理相应的Bean对象
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        //getBean:参数即为spring配置文件中的bean的id
        Hello hello = (Hello) context.getBean("hello");
        hello.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
<!--此文件命名为bean.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--id=变量名 class 相当于new 的类 property 相当于给对象中的属性赋值-->
    <bean id="hello" class="com.jm.helloWorld.Hello">
        <property name="name" value="jm"></property>
    </bean>
</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
3.2.2、注解方式

​ 首先我们需要创建组件(IOC)的注解,通过@Bean,在配置类集中创建第三方的bean。我们先来了解一下@Bean@Configuration注解:

  • @Bean:用于实例化表示方法,配置和初始化一个新的对象由Spring IoC容器管理。类似于bean.xml配置文件中的bean元素。

  • @Configuration:用来告诉容器Spring,这是一个配置类,相当于bean.xml文件。

    那么下面我们用一个实例来表示:

    //配置类
    @Configuration    //当配置ComponentScan 此注解也会被托管
    @ComponentScan(basePackages = "com.jm.ioc1.bean")  //当没有配置时 默认找此包或者此包下的子包
    public class AppConfig {
        //对于第三方引入的类
        @Bean
        public Teacher teacher(){
            return new Teacher();
        }
    }
    //外部类
    public class Teacher {
    
        private String name;
    
        public Teacher() {
            System.out.println("Teacher无参构造");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    //测试类
    public class Test {
        public static void main(String[] args) {
            //引入spring来托管
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
            Teacher teacher = (Teacher) applicationContext.getBean("teacher");   
            //如果Bean中没有赋值,那么默认以类的首字母小写取到其bean
            System.out.println(teacher);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    我们能在测试类中看到ApplicationContext以及AnnotationConfigApplicationContext,那么我们首先要知道BeanFactory。Spring容器最基本的接口就是BeanFactory,负责配置、创建、以及管理Bean。那么ApplicationContext就是由ApplicationContext派生而来。ApplicationContext因此也称之为Spring上下文。Spring容器负责管理Bean与Bean之间的依赖关系。而AnnotationConfigApplcationContext就是ApplicationContext的子类。这就是为什么IOC的实现是利用的工厂技术。

    ②利用FactoryBean完成( FactoryBean就相当于 @Bean )(在整合第三方框架时非常有用)

    1. 创建工厂Bean, 利用它来生成 Bean(可以更详细的来控制对象的特性)

      public class FruitFactoryBean implements FactoryBean<Orange> {
          @Override
          public Orange getObject() throws Exception {
              return new Orange();
          }
          @Override
          public Class<?> getObjectType() {
              return Orange.class;
          }
          @Override
          public boolean isSingleton() {
              return true;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    2. 托管工厂Bean

      public class AppConfig {
          @Bean
      	public FruitFactoryBean fruitFactoryBean(){
          	return new FruitFactoryBean();
      	}		
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    3. 获取Bean时请注意加 & 与不加的区别

      public class Test {
          public static void main(String[] args) {
              ApplicationContext ac=new AnnotationConfigApplicationContext( AppConfig.class );
              String [] beanNames=ac.getBeanDefinitionNames();
              for(String bn:beanNames){
                  System.out.println( bn );
              }
              Object obj=ac.getBean("fruitFactoryBean");
              System.out.println( obj );    //取 Orange
              Object obj2=ac.getBean("&fruitFactoryBean");
              System.out.println( obj2 );  //取工厂Bean
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

    ③通过包扫描@ComponentScan+IOC注解:来实现,这里适用于自己定义的类。在此之前我们也需要了解几个注解:

    1. 第一类为容器配置相关注解,在上面我们也提到过。

      • @Configuration
      • @ComponentScan
    2. 第二类为IOC相关注解

      • @Bean

      • @Component:此注解为通用注解,当我们的类不属于任何注解时,可以使用,也可以代替以下三个注解,那么我们为什么还要使用下面三个注解呢,小编在这里解释下。

      • @Repository:为dao层应用,此注解比@Component多实现了异常转换: SQLException转化成RuntimeException -> 事务 -> 在spring中事务的回滚( rollback) 只在出现了RuntimeException,这样减少了对业务层 侵入性。

      • @Service:为业务层

      • @Controller:为控制层

      //pojo类
      @Component  //由spring容器托管
      //@Repository  //Dao层
      //@Service //业务层
      //@Controller //控制层
      //@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  //多例 默认单例
      @Lazy    //只有在getBean时才创建
      public class Student {
          private Integer id;
          private String name;
      
          public Student() {
              System.out.println("Student无参构造");
          }
      
          @Override
          public String toString() {
              return "Student{" +
                      "id=" + id +
                      ", name='" + name + '\'' +
                      '}';
          }
      
          public Integer getId() {
              return id;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      //配置类
      @Configuration    //当配置ComponentScan 此注解也会被托管
      @ComponentScan(basePackages = "com.glw.ioc1.bean")  //当没有配置时 默认找此包或者此包下的子包
      public class AppConfig {
      }
      //测试类
      public class Test {
          public static void main(String[] args) {
              //引入spring来托管
              ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
              Student student = (Student) applicationContext.getBean("student");
              System.out.println(student);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53

​ 在此还有两个注解:

  • @Scope:用来控制此对象是单例还是多例,上面的例子中也提到了
  • @Lazy:懒加载,即只有在getBean之后才创建对象

二、 DI

1、DI含义

DI(Dependency Injection),依赖注入,当一个对象biz依赖另一个对象dao时,在程序运行期,由容器将依赖装配进去的过程,称为DI

2、注入依赖的方式

2.1、构造方法注入
public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2.2、set方法注入
public class Student {
    private Integer id;
    private String name;

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
2.3、属性方法注入
public class Student {
    private Integer id;
    private String name;
    
    public void findStudent(String name){
        //功能
    }

    public static void main(String[] args) {
        Student s = new Student();
        s.findStudent("jm");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
2.4、注解方式注入
  1. @Value,以下为连接数据库的案例

    //配置类
    @Configuration
    @ComponentScan
    @PropertySource("classpath:druid.properties")   //是由SpringIOC的属性读取对象
    public class AppConfig {
    
        private Logger logger = Logger.getLogger(AppConfig.class.getName());
    
        @Value("${jdbc.username}")   //DI:依赖注入  String类型的参数
        private String user;
        @Value("${jdbc.password}")   //DI:依赖注入  String类型的参数
        private String password;
        @Value("${jdbc.url}")   //DI:依赖注入  String类型的参数
        private String url;
        @Value("${jdbc.driverClassName}")   //DI:依赖注入  String类型的参数
        private String driverClassName;
    
    //    @Value("10")
        @Value("#{T(java.lang.Runtime).getRuntime().availableProcessors()*2}")    //Spring 表达式语言
        private int cpuCount;
    
        @Bean(initMethod = "init")
        public DruidDataSource ds( @Value("${jdbc.username}") String user){
    //        DataSource ds = new DruidDataSource();
            DruidDataSource dds = new DruidDataSource();
    
            dds.setUsername(user);
            dds.setPassword(password);
            dds.setUrl(url);
            dds.setDriverClassName(driverClassName);
    
            //当前主机的cpu数*2
            //1、
    //        int c = Runtime.getRuntime().availableProcessors() * 2;
            logger.info("配置druid连接池的大小:" + cpuCount);
            dds.setMaxActive(cpuCount);
            return dds;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    jdbc.username=****
    jdbc.password=****
    
    • 1
    • 2
    • 3
    • 4

    还有一点需要了解的是,@value中可以应用Spring的表达式语言。

  2. @Autowired: 按类型装配

    //接口类
    @Repository
    public interface StudentDao {
        void addStudent();
        void findStudent();
    }
    
    //实现类
    @Service
    @Scope
    public class StudentBizImpl {
    
        public StudentBizImpl() {
            System.out.println("构造");
        }
    //    @Inject
    //    @Named("studentDaoMongoImpl")
        @Autowired
        @Qualifier("studentDaoMongoImpl")  //当有多个相同类型的对象时,用来区分
    //    @Resource(name="studentDaoMongoImpl")
        private StudentDao studentDao;
    
        public boolean regStudent(){
            studentDao.findStudent();
            studentDao.addStudent();
            return true;
        }
    }
    
    @Repository
    @Primary
    @Repository
    @Primary
    public class StudentDaoMongoImpl implements StudentDao {
        @Override
        public void addStudent() {
            System.out.println("add");
        }
    
        @Override
        public void findStudent() {
            System.out.println("find");
        }
    }
    
    @Repository
    public class StudentDaoMyBatisImpl implements StudentDao{
        @Override
        public void addStudent() {
            System.out.println("add");
        }
    
        @Override
        public void findStudent() {
            System.out.println("find");
        }
        
        public void findStudent() {
            System.out.println("find");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    @Qualifier: 当有多个相同类型的对象时, 用@Qualifier来区分

    有多个相同的bean时,使用 @Primary指定优先

  3. @Inject:类似于 @Autowired注解,但@Inject注解由javax提供,要引入 javax.inject 包。

    @Named(“studentDaoJpaImpl”) 来区分多个相同类型的对象.

  4. JSR250的注解@Resouce:替换上面的 @Autowired和@Qualifier

3、 对一个类生命周期回调方法的注解

3.1、方案一

@Bean中指定init和destroy

public class Person {

    public Person(){
        System.out.println("构造方法");
    }
    public void init(){
        System.out.println("初始化方法");
    }
    public void destroy(){
        System.out.println("destroy()");
    }
}

@Configuration
public class AppConfig {
    public static void main(String[] args) {
        ApplicationContext ac=new AnnotationConfigApplicationContext( ConfigClass4.class );
        ( (AnnotationConfigApplicationContext)ac).close();
    }

    @Bean(initMethod = "init",destroyMethod = "destroy")
    public Person p(){
        return new Person();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
3.2、方案二

​ 使用JSR250规则定义的(java规范)两个注解来实现
​ @PostConstruct: 在Bean创建完成,且属于赋值完成后进行初始化,属于JDK规范的注解
​ @PreDestroy: 在bean将被移除之前进行通知, 在容器销毁之前进行清理工作

3.3、方案三
  1. 实现InitializingBean接口的afterPropertiesSet()方法,当beanFactory创建好对象,且把bean所有属性设置好之后,会调这个方法,相当于初始化方法
  2. 实现DisposableBean的destory()方法,当bean销毁时,会把单实例bean进行销毁。

三、Spring IOC的底层原理(马士兵课程描述)

在这里插入图片描述

控制反转:原来的对象是由使用者来进行控制,有了sping之后,可以把整个对象交给spring来管理

​ DI:依赖注入,把对应的属性的值注入到具体的对象中,@Autowired,populatieBean完成属性的注入

容器:存储对象,使用map结构来存储,在spring中一般存在三级缓存,singletonObject存放完整的bean对象,整个bean的生命周期从创建到使用到销毁的过程全都是由容器来管理(bean的生命周期)。

1、一般聊IOC容器的时候要设计到容器的创建过程(beanFactory,DefaultListableBeanFactory)

beanFactory:容器有一个最上层的父接口叫做beanFactory,里面只是一个接口,没有对应的子类实现,在实际调用过程中,最普遍的就是DefaultListableBeanFactory,包括在使用的时候,会优先创建当前bean工厂,优先向bean工厂中设置一些参数(BeanPostProcessor,Aware接口的子类)等等属性

2、加载解析bean对象,准备要创建的bean对象的定义对象beanDefinition,(xml或者注解的解析过程)

3、beanFactoryPostProcessor的处理,此处是扩展点,PlaceHolderConfigurSupport,ConfigurationClassPostProcessor

4、BeanPostProcessor的注册功能,方便后续对bean对象完成具体的扩展功能

5、通过反射的方式将BeanDefinition对象实例化具体的bean对象

6、bean对象的初始化过程(填充属性,调用Aware子类的方法,调用BeanPostProcessor前置处理方法,调用init-method方法,调用BeanPostProcessor的后置处理方法)

7、生成完整的bean对象,通过getBean方法可以直接获取

8、销毁过程

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/229664
推荐阅读
相关标签
  

闽ICP备14008679号