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();
    }
}
<!--此文件命名为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>
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);
        }
    }
    
    

    我们能在测试类中看到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;
          }
      }
      
    2. 托管工厂Bean

      public class AppConfig {
          @Bean
      	public FruitFactoryBean fruitFactoryBean(){
          	return new FruitFactoryBean();
      	}		
      }
      
    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
          }
      }
      

    ③通过包扫描@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);
          }
      }
      

​ 在此还有两个注解:

  • @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;
    }
}
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;
    }
}
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");
    }
}
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;
        }
    }
    
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
    jdbc.username=****
    jdbc.password=****
    

    还有一点需要了解的是,@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");
        }
    }
    

    @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();
    }
}
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、销毁过程

  • 16
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值