Spring系列Spring工厂模式及依赖注入

Posted 一宿君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring系列Spring工厂模式及依赖注入相关的知识,希望对你有一定的参考价值。

2 Spring中的工厂模式

工厂模式的思想正好契合Spring IoC的设计思想:某一接口的具体具体实现类的选择权从调用类中移除,转而交给第三方决定,即借由Spring的Bean配置来实现控制,这同样也是工厂模式的思想。

工厂模式提供了一种绝佳的创建对象的方法。在工厂模式中我们并不会直接使用new来创建一个对象,而是使用一个共同的接口类来指定其实现类,这就大大降低了系统的耦合性——我们无需改变每个调用此接口的类,而直接改变实现此接口的类即可完成软件的更新迭代。

2.1 BeanFactory和ApplicationContext有什么区别?

BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

  • BeanFactory是Spring框架的基础设施,面向的是Spring本身,也就是用于创建Spring扩展的其他内容,如Spring Security、Spring JDBC等。
  • ApplicationContext这个工厂是面向开发者的,也就是应用上下文——配置文件等,开发者能够使用这个工厂实现自己的功能。

依赖关系

  • BeanFactory:是Spring里面最底层的接口,包含了Bean的定义,读取Bean配置文档,管理Bean的加载、实例化,控制Bean的生命周期,维护Bean之间的依赖关系。
  • ApplicationContext:接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外还提供了更完整的框架功能:
    • 继承MessageSource,因此支持国际化;
    • 统一的资源文件访问方式;
    • 提供在监听器中注册Bean的事件;
    • 同时加载多个配置文件;
    • 载入多个(有继承关系)的上下文,使得每一个上下文都专注于一个特定的层次,比如应用的Web层。

加载方式

  • BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean())才对该Bean进行加载实例化。这样我们就不能发现一些Spring存在的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean()方法才会抛出异常。
  • ApplicationContext,它是在容器中启动时,一次性创建了所有的Bean。这样在容器启动时,我们就可以发现Spring中存在的配置问题,这样有利于检查所依赖属性是否注入,AppIIcationContext启动后预载入所有的单实例Bean,通过预载入单实例Bean,确保当你需要的时候,你就不用再等到,因为他们已经创建好了。
  • 相对于基本的BeanFactory,ApplicationContext唯一不足的是占用内存空间。当应用程序配置Bean实例较多时,程序启动较慢。
    创建方式
  • BeanFactory通常以编程的方式被创建;
  • ApplicationContext还能以声明的方式创建,如使用ContextLoader。

注册方式

  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryProcessor的使用;
  • 但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext是自动注册。

2.2 ApplicationContext通常的实现类是什么?

  • FileSystemXmlApplicationContext:此容器从一个 XML文件中加载Beans的定义,XML Bean配置文件的全路径名(绝对路径)必须提供给它的构造函数。
  • ClassPathXmlApplicationContext:此容器也从一个XML文件中加载Beans的定义,这里你需要正确设置classpath(相对路径)因为这个容器将在classpath里找Bean配置。(推荐)
  • WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个Web应用的所有Bean。

2.3 什么是Spring的依赖注入?

  • 控制反转IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:依赖注入和依赖查询。
  • 依赖注入:相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入(Dependency Injection),即组价之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中各个关联的组件之中。组件不做定位查询,只是提供普通的Java方法让容器去决定依赖关系。

2.4 依赖注入的基本原则

  • 应用组件不应该负责查找资源或者其他依赖的协作对象。
  • 配置对象的工作应该由IoC容器负责,查找资源的代码应该从应用组件的代码中抽取出来,交给IoC容器管理。
  • 容器全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter方法)或者构造器传递给需要的对象。

2.5 依赖注入的实现方式有哪些?

依赖注入是当前比较流行的IoC实现方式,依赖注入分为接口注入(Interface Injection),Setter方法注入(Setter Injection),构造器注入(Constructor Injection)三种方式。其中接口注入由于在灵活性和易用性比较差,现在从Spring4已被废除。

  • Setter方法注入通过调用无参构造器或无参static工厂方法实例化Bean之后,调用该Bean的setter方法,即实现了基于setter的依赖注入。
  • 构造器依赖注入通过容器触发一个类的构造器来实现,该类有一系列参数,每个参数代表一个对其他类的依赖。

2.6 构造器依赖注入和Setter方法注入的区别

构造函数注入setter方法注入
没有部分注入有部分注入
不会覆盖setter属性会覆盖setter属性
任意修改都会创建一个新实例任意修改不会创建一个新实例
适用于设置很多属性适用于设置少量属性

2.7 下面我们通过实例来演示为什么使用Spring工厂模式和依赖注入

  • Human接口,定义eat()和sleep()两个方法
    package com.spring.dao;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:22:50
     */
    public interface Human {
        /**
         * 吃
         */
        void eat();
    
        /**
         * 睡
         */
        void sleep();
    }
    
  • 因为人有很多种类,我们暂时以中国人和美国人为例。
  • Chinese实现类
    package com.spring.dao.impl;
    
    import com.spring.dao.Human;
    import com.spring.dao.Language;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:24:00
     */
    public class Chinese implements Human {
    
        @Override
        public void eat() {
            System.out.println("中国人吃吃吃吃吃");
        }
    
        @Override
        public void sleep() {
            System.out.println("中国人睡睡睡睡睡");
        }
    }
    
  • American实现类
    package com.spring.dao.impl;
    
    import com.spring.dao.Human;
    import com.spring.dao.Language;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:24:18
     */
    public class American implements Human {
    
        @Override
        public void eat() {
            System.out.println("美国人吃吃吃吃吃");
        }
    
        @Override
        public void sleep() {
            System.out.println("美国人睡睡睡睡睡");
        }
    }
    
  • Test1(传统方式)
    package com.spring.test;
    
    import com.spring.dao.Human;
    import com.spring.dao.impl.American;
    import com.spring.dao.impl.Chinese;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:25:22
     */
    public class Test1 {
        public static void main(String[] args) {
            /**
             * 传统方式:直接创建
             * 缺点:
             * 高耦合、可扩展性差、不够灵活
             */
            Human human1 = new Chinese();
            human1.eat();
            human1.sleep();
    
            Human human2 = new American();
            human2.eat();
            human2.sleep();
        }
    }
    
  • 上述可以看出,传统的创建方式缺点高耦合,且可扩展性差、不够灵活,如果有一百中人种类,就要new一百个对象,这就很不合适了。所以我么引入一个人类工厂类,可根据输入的参数判断属于哪一种类人。
  • HumanFactoryBean工厂类
    package com.spring.staticProxy;
    
    import com.spring.dao.Human;
    import com.spring.dao.impl.American;
    import com.spring.dao.impl.Chinese;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:31:26
     */
    public class HumanFactoryBean{
        /**
         * 代理工厂类
         * @param name
         * @return
         */
        public Human getHuman(String name){
            if("ch".equals(name)){
                return new Chinese();//返回中国人实例
            }else if("am".equals(name)){
                return new American();//返回美国人实例
            }else {
                throw new IllegalArgumentException("参数语法错误!!!");
            }
        }
    }
    
  • Test2测试类
    package com.spring.test;
    
    import com.spring.dao.Human;
    import com.spring.staticProxy.HumanFactoryBean;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:33:23
     */
    public class Test2 {
        public static void main(String[] args) {
            /**
             * 使用工厂模式管理实例创建
             * 相比传统直接创建方式的优点:
             * 解耦合、可扩展性高、灵活性高
             */
            //创建工厂类对象
            HumanFactoryBean humanFactory = new HumanFactoryBean();
            
            Human human1 = humanFactory.getHuman("ch");//返回中国人实例对象
            human1.eat();
            human1.sleep();
    
            Human human2 = humanFactory.getHuman("am");//返回美国人实例对象
            human2.eat();
            human2.sleep();
        }
    }
    
  • 综上可以看出,使用工厂模式管理实例会大大降低代码耦合度,并且提高了代码的可扩展性和灵活性,这就是我们要体现的工厂模式,这时候Spring为我们提供了更为简便的管理实例化对象的方法,那就是利用Bean工厂创建对象,方便管理且高效。
  • applicationContext.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
           
        <!--引入Chnise-->
        <bean id="ch" class="com.spring.dao.impl.Chinese"/>
        <!--引入American-->
        <bean id="am" class="com.spring.dao.impl.American"/>
    
    </beans>
    
  • 这时候我们就用到了上述所讲的Spring的两大核心接口,Beanfactory和ApplicationContext两个Spring容器,用来管理Bean实例对象。
  • Test3
    package com.spring.test;
    
    import com.spring.dao.Human;
    import javafx.application.Application;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.core.io.FileSystemResource;
    
    import java.io.File;
    import java.lang.reflect.Field;
    import java.util.Base64;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:38:59
     */
    public class Test3 {
        public static void main(String[] args) {
            /**
             * 采用Spring的bean工厂方式创建实例
             */
            BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("src/applicationContext.xml"));
            Human human1 = (Human) beanFactory.getBean("ch");
            human1.eat();
            human1.sleep();
            Human human2 = (Human)beanFactory.getBean("am");
            human2.eat();
            human2.sleep();
    
            /**
             * 采用应用程序上下文方式创建bean实例
             */
            //绝对路径(不推荐使用)
            //ApplicationContext applicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
            //相对路径
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Human human3 = (Human) applicationContext.getBean("ch");
            human3.eat();
            human3.sleep();
            Human human4 = (Human)applicationContext.getBean("am");
            human4.eat();
            human4.sleep();
         
        }
    }
    
    在这里插入图片描述

我们上述简单的介绍了Spring的工厂模式,可以很方便的创建Bean实例,但是如果Bean实例中需要引用其他实例对象,我们就需要使用依赖注入。例如:人有种类之分,而语言同样有种类之分,这时候我们就需要引入语言实例对象Language,此处我们以中文和英文为例。

  • Language接口(定义语种方法kind)
    package com.spring.dao;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 16:12:47
     */
    public interface Language {
        /**
         * 语种
         * @return
         */
        public String kind();
    }
    
  • HanYu实现类(汉语)
    package com.spring.dao.impl;
    
    import com.spring.dao.Language;
    import jdk.jfr.Label;
    
    import javax.security.auth.callback.LanguageCallback;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 16:36:08
     */
    public class HanYu implements Language {
        @Override
        public String kind() {
            return "汉语";
        }
    }
    
  • English实现类(英语)
    package com.spring.dao.impl;
    
    import com.spring.dao.Language;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 16:37:18
     */
    public class English implements Language {
    
    
        @Override
        public String kind() {
            return "英语";
        }
    
    }
    
    

此时我们在Human接口中定义speak()方法,Chinese和American分别实现次方法。

  • Human接口
    package com.spring.dao;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:22:50
     */
    public interface Human {
        /**
         * 吃
         */
        void eat();
    
        /**
         * 睡
         */
        void sleep();
    
        /**
         * 说话
         */
        void speak();
    
    }
    
  • Chinese实现类(以构造方法实现注入引用)
    package com.spring.dao.impl;
    
    import com.spring.dao.Human;
    import com.spring.dao.Language;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:24:00
     */
    public class Chinese implements Human {
        private Language language;
    
        public Chinese() {
            this.language = language;
        }
    
        /**
         * 构造方法注入
         * @param language
         */
        public Chinese(Language language) {
            this.language = language;
        }
    
    
        @Override
        public void eat() {
            System.out.println("中国人吃吃吃吃吃");
        }
    
        @Override
        public void sleep() {
            System.out.println("中国人睡睡睡睡睡");
        }
    
        @Override
        public void speak() {
            System.out.println("我是中国人,我说的是" + language.kind());
        }
    }
    
  • American实现类(以setter方法实现)
    package com.spring.dao.impl;
    
    import com.spring.dao.Human;
    import com.spring.dao.Language;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:24:18
     */
    public class American implements Human {
    
        private Language language;
        /**
         * setter方法注入
         * @param language
         */
        public void setLanguage(Language language) {
            this.language = language;
        }
    
        @Override
        public void eat() {
            System.out.println("美国人吃吃吃吃吃");
        }
    
        @Override
        public void sleep() {
            System.out.println("美国人睡睡睡睡睡");
        }
    
        @Override
        public void speak() {
            System.out.println("我是美国人,我说的是" + language.kind());
        }
    
    }
    
  • Test3
    package com.spring.test;
    
    import com.spring.dao.Human;
    import javafx.application.Application;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanFactory;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    import org.springframework.core.io.FileSystemResource;
    
    import java.io.File;
    import java.lang.reflect.Field;
    import java.util.Base64;
    
    /**
     * @author 一宿君(CSDN : qq_52596258)
     * @date 2021-07-12 15:38:59
     */
    public class Test3 {
        public static void main(String[] args) {
            /**
             * 采用Spring的bean工厂方式创建实例
             */
            BeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource("src/applicationContext.xml"));
            Human human1 = (Human) beanFactory.getBean("ch2");
            human1.eat();
            human1.sleep();
            human1.speak();
            Human human2 = (Human)beanFactory.getBean("am2");
            human2.eat();
            human2.sleep();
            human2.speak();
    
            /**
             * 采用应用程序上下文方式创建bean实例
             */
            //绝对路径(不推荐使用)
            //ApplicationContext applicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
            //相对路径
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Human human3 = (Human) applicationContext.getBean("ch2");
            human3.eat(spring 配置bean的方法及依赖注入发方式

    Spring Ioc和依赖注入

    Java开发Spring之IOC详解第一篇(xml开发常用APIben标签DI依赖注入)

    在依赖注入环境(如 Spring Boot)中创建设计模式是不是无用?

    Spring-01 注解实现IOC

    Spring 依赖注入(DI)详解 [Spring][依赖注入的 6 种实现方式][setter注入][构造器注入][注解注入][自动装配注入][静态工厂注入][实例工厂注入]