创建型设计模式之工厂模式

概述

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。

大部分设计模式要解决的都是代码的可扩展性问题。

对于灵活多变的业务,需要用到设计模式,提升扩展性和可维护性,让代码能适应更多的变化;

设计模式的核心就是,封装变化,隔离可变性

设计模式解决的问题:

  • 创建型设计模式主要解决“对象的创建”问题,创建和使用代码解耦;
  • 结构型设计模式主要解决“类或对象的组合或组装”问题,将不同功能代码解耦;
  • 行为型设计模式主要解决的就是“类或对象之间的交互”问题。将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。

创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。

  • 单例模式用来创建全局唯一的对象。
  • 工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
  • 建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
  • 原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。

设计模式关注重点: 了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。

经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。

常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。

不常用的有:原型模式。

工厂模式:

工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。

简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。

在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。

重点还是搞清楚应用场景:什么时候该用工厂模式?相对于直接 new 来创建对象,用工厂模式来创建究竟有什么好处呢?

  • 大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat、Calender。
  • 工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等,这个我们根据具体的场景和习惯来命名就好。

定义:

工厂模式创建的是不同子类的对象;

简单工厂:

如果创建对象简单,直接new对象

  • 可以直接使用if判断new对象;

    一般不用:这种方式每次调用工厂方法都要new一个对象,浪费内存和对象创建的时间;并且添加一个分支逻辑就需要修改工厂方法,违背开闭原则;

    在下面的代码实现中,我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser。实际上,如果 parser 可以复用,为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。

    public class RuleConfigParserFactory {
      public static IRuleConfigParser createParser(String configFormat) {
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
          parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
          parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
          parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
          parser = new PropertiesRuleConfigParser();
        }
        return parser;
      }
    
  • 也可以使用MAP缓存new的对象,复用对象且替代 if 分支判断

使用Map缓存对象

使用Map缓存对象, 复用策略对象,节省内存和对象创建的时间

提供策略注册接口,新增策略无需修改策略工厂类

public class RuleConfigParserFactory {
  private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();

  static {
    cachedParsers.put("json", new JsonRuleConfigParser());
    cachedParsers.put("xml", new XmlRuleConfigParser());
    cachedParsers.put("yaml", new YamlRuleConfigParser());
    cachedParsers.put("properties", new PropertiesRuleConfigParser());
  }

   // 提供注册策略接口,外部只需要调用此接口接口新增策略,而无需修改策略工厂内部代码
    //可以利用反射自动注册,下面案例说明
    public static void registerTaxStrategy(String configFormat, RuleConfigParser ruleConfigParser) {
        cachedParsers.put(taxType, taxStrategy);
    }

  public static IRuleConfigParser createParser(String configFormat) {
    if (configFormat == null || configFormat.isEmpty()) {
      return null;//返回null还是IllegalArgumentException全凭你自己说了算
    }
    return cachedParsers.get(configFormat.toLowerCase());
  }
}

工厂方法(Factory Method):

适用于生产一种类型的对象

解决的问题:工厂方法利用多态将不同对象的创建细节封装工厂类里,提高了代码的扩展性,比简单工厂更加符合开闭原则;

  1. 当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂;
  2. 使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,这种方式适用于直接new对象;

利用多态将不同对象的创建细节封装工厂类里,提高了代码的扩展性,更加符合开闭原则;新增一种对象的时候,只需要增加对应的工厂类;

还需要为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

工厂方法的缺点:增加了类的个数,牺牲了代码的可读性。

public interface IRuleConfigParserFactory {
  IRuleConfigParser createParser();
}

public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new JsonRuleConfigParser();
  }
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
  @Override
  public IRuleConfigParser createParser() {
    return new XmlRuleConfigParser();
  }
}
}

我们可以为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。

其中,RuleConfigParserFactoryMap 类是创建工厂对象的工厂类,getParserFactory() 返回的是缓存好的单例工厂对象。

当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,注册到工厂的工厂;

public class RuleConfigSource {
  public RuleConfig load(String ruleConfigFileType) {
 
    IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileType);
    if (parserFactory == null) {
      throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFile);
    }
    IRuleConfigParser parser = parserFactory.createParser();

    String configText = "";
    //从ruleConfigFileType文件中读取配置文本到configText中
    RuleConfig ruleConfig = parser.parse(configText);
    return ruleConfig;
  }
}

//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
public class RuleConfigParserFactoryMap { //工厂的工厂
  private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();

  static {
    cachedFactories.put("json", new JsonRuleConfigParserFactory());
    cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
    cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
  }

    // 提供注册策略接口,外部只需要调用此接口接口新增策略,而无需修改策略工厂内部代码
    public static void registerTaxStrategy(String configFormat, RuleConfigParser ruleConfigParser) {
        cachedParsers.put(taxType, taxStrategy);
    }

  public static IRuleConfigParserFactory getParserFactory(String type) {
    if (type == null || type.isEmpty()) {
      return null;
    }
    IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());
    return parserFactory;
  }
}

抽象工厂(Abstract Factory)

工厂方法的一个弊端是,如果工厂不只是生产一种类型的对象,或者说对象不是根据一种特征分类,那工厂方法的类将会非常多,难以维护;

抽象工厂就是让一个工厂可以生产不同类型的对象。

抽象工厂就是针对这种非常特殊的场景而诞生的。我们可以让一个工厂负责创建多个不同类型的对象(IRuleConfigParser、ISystemConfigParser 等),而不是只创建一种 parser 对象。这样就可以有效地减少工厂类的个数。

public interface IConfigParserFactory {
  IRuleConfigParser createRuleParser();
  ISystemConfigParser createSystemParser();
  //此处可以扩展新的parser类型,比如IBizConfigParser
}

public class JsonConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new JsonRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new JsonSystemConfigParser();
  }
}

public class XmlConfigParserFactory implements IConfigParserFactory {
  @Override
  public IRuleConfigParser createRuleParser() {
    return new XmlRuleConfigParser();
  }

  @Override
  public ISystemConfigParser createSystemParser() {
    return new XmlSystemConfigParser();
  }
}

// 省略YamlConfigParserFactory和PropertiesConfigParserFactory代码

前两种工厂模式的应用场景进行总结:

当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。 创建逻辑比较复杂有下面两种情况:

  • 第一种情况:类似规则配置解析的例子,代码中存在 if-else 分支判断,动态地根据不同的类型创建不同的对象。针对这种情况,我们就考虑使用工厂模式,将这一大坨 if-else 创建对象的代码抽离出来,放到工厂类中。(map缓存也可以消灭if-else 分支判断)
  • 还有一种情况,尽管我们不需要根据不同的类型创建不同的对象,但是,单个对象本身的创建过程比较复杂,比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

对于第一种情况,当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。

当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的简单工厂类,我推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。同理,对于第二种情况,因为单个对象本身的创建逻辑就比较复杂,所以,我建议使用工厂方法模式。

除了刚刚提到的这几种情况之外,如果创建对象的逻辑并不复杂,那我们就直接通过 new 来创建对象就可以了,不需要使用工厂模式。

工厂模式的作用有以下四点,这也是判断要不要使用工厂模式的最本质的参考标准:

  • 封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 代码复用:创建代码抽离到独立的工厂类之后可以复用。
  • 隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
  • 控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁。

工厂模式是一种非常常用的设计模式,在很多开源项目、工具类中到处可见,比如 Java 中的 Calendar、DateFormat 类。

Spring框架中的BeanFactory, 屏蔽创建bean时的复杂操作。

Dependency Injection框架

当创建对象是一个“大工程”的时候,我们一般会选择使用工厂模式,来封装对象复杂的创建过程,将对象的创建和使用分离,让代码更加清晰。

工厂模式和 DI 容器有何区别?

实际上,DI 容器底层最基本的设计思路就是基于工厂模式的。DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为“容器”。

一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。

DI 容器的核心功能有哪些?

一个简单的 DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。

我们将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。

配置解析:

下面是一个典型的 Spring 容器的配置文件。Spring 容器读取这个配置文件,解析出要创建的两个对象:rateLimiter 和 redisCounter,并且得到两者的依赖关系:rateLimiter 依赖 redisCounter。

public class RateLimiter {
  private RedisCounter redisCounter;
  public RateLimiter(RedisCounter redisCounter) {
    this.redisCounter = redisCounter;
  }
  //...
}

public class RedisCounter {
  private String ipAddress;
  private int port;
  public RedisCounter(String ipAddress, int port) {
    this.ipAddress = ipAddress;
    this.port = port;
  }
  //...
}

配置文件beans.xml:
<beans>
   <bean id="rateLimiter" class="com.xzg.RateLimiter">
      <constructor-arg ref="redisCounter"/>
   </bean>
 
   <bean id="redisCounter" class="com.xzg.redisCounter">
     <constructor-arg type="String" value="127.0.0.1">
     <constructor-arg type="int" value=1234>
   </bean>
</beans>

对象创建:

我们只需要将所有类对象的创建都放到一个工厂类中完成就可以了,比如 BeansFactory。

DI 容器的具体实现:用“反射”这种机制,它能在程序运行的过程中,动态地加载类、创建对象,不需要事先在代码中写死要创建哪些对象。所以,不管是创建一个对象还是十个对象,BeansFactory 工厂类代码都是一样的。

对象的生命周期管理:

简单工厂模式有两种实现方式:

  • 一种是每次都返回新创建的对象,
  • 另一种是每次都返回同一个事先创建好的对象,也就是所谓的单例对象。

在 Spring 框架中,我们可以通过配置 scope 属性,来区分这两种不同类型的对象。scope=prototype 表示返回新创建的对象,scope=singleton 表示返回单例对象。

除此之外,我们还可以配置对象是否支持懒加载。如果 lazy-init=true,对象在真正被使用到的时候(比如:BeansFactory.getBean(“userService”))才被被创建;如果 lazy-init=false,对象在应用启动的时候就事先创建好。

不仅如此,我们还可以配置对象的 init-method 和 destroy-method 方法,比如 init-method=loadProperties(),destroy-method=updateConfigFile()。DI 容器在创建好对象之后,会 主动调用 init-method 属性指定的方法来初始化对象。 在对象被最终销毁之前,DI 容器会主动调用 destroy-method 属性指定的方法来做一些清理工作,比如释放数据库连接池、关闭文件。

如何实现一个简单的 DI 容器?

用 Java 语言来实现一个简单的 DI 容器,核心逻辑只需要包括这样两个部分:配置文件解析、根据配置文件通过“反射”语法来创建对象。

1. 最小原型设计

我们只实现一个 DI 容器的最小原型。像 Spring 框架这样的 DI 容器,它支持的配置格式非常灵活和复杂。为了简化代码实现,在最小原型中,我们只支持下面配置文件中涉及的配置语法。

配置文件beans.xml
<beans>
   <bean id="rateLimiter" class="com.xzg.RateLimiter">
      <constructor-arg ref="redisCounter"/>
   </bean>
 
   <bean id="redisCounter" class="com.xzg.redisCounter" scope="singleton" lazy-init="true">
     <constructor-arg type="String" value="127.0.0.1">
     <constructor-arg type="int" value=1234>
   </bean>
</bean

最小原型的使用方式跟 Spring 框架非常类似,示例代码如下所示:

public class Demo {
  public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            "beans.xml");
    RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter");
    rateLimiter.test();
    //...
  }
}

2. 提供执行入口

面向对象设计的最后一步是:组装类并提供执行入口。在这里,执行入口就是一组暴露给外部使用的接口和类。

通过刚刚的最小原型使用示例代码,我们可以看出,执行入口主要包含两部分:ApplicationContext 和 ClassPathXmlApplicationContext。其中,ApplicationContext 是接口,ClassPathXmlApplicationContext 是接口的实现类。两个类具体实现如下所示:

public interface ApplicationContext {
  Object getBean(String beanId);
}

public class ClassPathXmlApplicationContext implements ApplicationContext {
  private BeansFactory beansFactory;
  private BeanConfigParser beanConfigParser;

  public ClassPathXmlApplicationContext(String configLocation) {
    this.beansFactory = new BeansFactory();
    this.beanConfigParser = new XmlBeanConfigParser();
    loadBeanDefinitions(configLocation);
  }

  private void loadBeanDefinitions(String configLocation) {
    InputStream in = null;
    try {
      in = this.getClass().getResourceAsStream("/" + configLocation);
      if (in == null) {
        throw new RuntimeException("Can not find config file: " + configLocation);
      }
      List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
      beansFactory.addBeanDefinitions(beanDefinitions);
    } finally {
      if (in != null) {
        try {
          in.close();
        } catch (IOException e) {
          // TODO: log error
        }
      }
    }
  }

  @Override
  public Object getBean(String beanId) {
    return beansFactory.getBean(beanId);
  }
}

从上面的代码中,我们可以看出,ClassPathXmlApplicationContext 负责组装 BeansFactory 和 BeanConfigParser 两个类,串联执行流程:从 classpath 中加载 XML 格式的配置文件,通过 BeanConfigParser 解析为统一的 BeanDefinition 格式,然后,BeansFactory 根据 BeanDefinition 来创建对象。

3. 配置文件解析

配置文件解析主要包含 BeanConfigParser 接口和 XmlBeanConfigParser 实现类,负责将配置文件解析为 BeanDefinition 结构,以便 BeansFactory 根据这个结构来创建对象。

配置文件的解析比较繁琐,只给出两个类的大致设计思路,并未给出具体的实现代码。具体的代码框架如下所示:

public interface BeanConfigParser {
  List<BeanDefinition> parse(InputStream inputStream);
  List<BeanDefinition> parse(String configContent);
}

public class XmlBeanConfigParser implements BeanConfigParser {

  @Override
  public List<BeanDefinition> parse(InputStream inputStream) {
    String content = null;
    // TODO:...
    return parse(content);
  }

  @Override
  public List<BeanDefinition> parse(String configContent) {
    List<BeanDefinition> beanDefinitions = new ArrayList<>();
    // TODO:...
    return beanDefinitions;
  }

}

public class BeanDefinition {
  private String id;
  private String className;
  private List<ConstructorArg> constructorArgs = new ArrayList<>();
  private Scope scope = Scope.SINGLETON;
  private boolean lazyInit = false;
  // 省略必要的getter/setter/constructors
 
  public boolean isSingleton() {
    return scope.equals(Scope.SINGLETON);
  }


  public static enum Scope {
    SINGLETON,
    PROTOTYPE
  }
  
  public static class ConstructorArg {
    private boolean isRef;
    private Class type;
    private Object arg;
    // 省略必要的getter/setter/constructors
  }
}

4. 核心工厂类设计

最后,我们来看,BeansFactory 是如何设计和实现的。这也是我们这个 DI 容器最核心的一个类了。它负责根据从配置文件解析得到的 BeanDefinition 来创建对象。

如果对象的 scope 属性是 singleton,那对象创建之后会缓存在 singletonObjects 这样一个 map 中,下次再请求此对象的时候,直接从 map 中取出返回,不需要重新创建。如果对象的 scope 属性是 prototype,那每次请求对象,BeansFactory 都会创建一个新的对象返回。

实际上,BeansFactory 创建对象用到的主要技术点就是 Java 中的反射语法:一种动态加载类和创建对象的机制。

JVM 在启动的时候会根据代码自动地加载类、创建对象。至于都要加载哪些类、创建哪些对象,这些都是在代码中写死的,或者说提前写好的。但是,如果某个对象的创建并不是写死在代码中,而是放到配置文件中,我们需要在程序运行期间,动态地根据配置文件来加载类、创建对象,那这部分工作就没法让 JVM 帮我们自动完成了,我们需要利用 Java 提供的反射语法自己去编写代码。

搞清楚了反射的原理,BeansFactory 的代码就不难看懂了。具体代码实现如下所示:

public class BeansFactory {
  private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
  private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();

  public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {
    for (BeanDefinition beanDefinition : beanDefinitionList) {
      this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);
    }

    for (BeanDefinition beanDefinition : beanDefinitionList) {
      if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) {
        createBean(beanDefinition);
      }
    }
  }

  public Object getBean(String beanId) {
    BeanDefinition beanDefinition = beanDefinitions.get(beanId);
    if (beanDefinition == null) {
      throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);
    }
    return createBean(beanDefinition);
  }

  @VisibleForTesting
  protected Object createBean(BeanDefinition beanDefinition) {
    if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {
      return singletonObjects.get(beanDefinition.getId());
    }

    Object bean = null;
    try {
      Class beanClass = Class.forName(beanDefinition.getClassName());
      List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();
      if (args.isEmpty()) {
        bean = beanClass.newInstance();
      } else {
        Class[] argClasses = new Class[args.size()];
        Object[] argObjects = new Object[args.size()];
        for (int i = 0; i < args.size(); ++i) {
          BeanDefinition.ConstructorArg arg = args.get(i);
          if (!arg.getIsRef()) {
            argClasses[i] = arg.getType();
            argObjects[i] = arg.getArg();
          } else {
            BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());
            if (refBeanDefinition == null) {
              throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg());
            }
            argClasses[i] = Class.forName(refBeanDefinition.getClassName());
            argObjects[i] = createBean(refBeanDefinition);
          }
        }
        bean = beanClass.getConstructor(argClasses).newInstance(argObjects);
      }
    } catch (ClassNotFoundException | IllegalAccessException
            | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
      throw new BeanCreationFailureException("", e);
    }

    if (bean != null && beanDefinition.isSingleton()) {
      singletonObjects.putIfAbsent(beanDefinition.getId(), bean);
      return singletonObjects.get(beanDefinition.getId());
    }
    return bean;
  }
}

DI 容器在一些软件开发中已经成为了标配,比如 Spring IOC、Google Guice。但是,大部分人可能只是把它当作一个黑盒子来使用,并未真正去了解它的底层是如何实现的。当然,如果只是做一些简单的小项目,简单会用就足够了,但是,如果我们面对的是非常复杂的系统,当系统出现问题的时候,对底层原理的掌握程度,决定了我们排查问题的能力,直接影响到我们排查问题的效率。

一个简单的 DI 容器的实现原理,其核心逻辑主要包括:配置文件解析,以及根据配置文件通过“反射”语法来创建对象。其中,创建对象的过程就应用到了我们在学的工厂模式。对象创建、组装、管理完全有 DI 容器来负责,跟具体业务代码解耦,让程序员聚焦在业务代码的开发上。

问题:

BeansFactory 类中的 createBean() 函数是一个递归函数。当构造函数的参数是 ref 类型时,会递归地创建 ref 属性指向的对象。如果我们在配置文件中错误地配置了对象之间的依赖关系,导致存在循环依赖,那 BeansFactory 的 createBean() 函数是否会出现堆栈溢出?又该如何解决这个问题呢?

1、构造器循环依赖

构造器注入的循环依赖是无法解决的,只能抛出bean创建异常使容器无法启动

如何判断是循环依赖?

A类依赖于B类,而B类又依赖于A类,这就是循环依赖。循环依赖会抛出异常

2、setter方法循环依赖

①单例情况可以解决循环依赖,方法是提前暴露一个返回该单例的工厂方法,让依赖对象可以引用到
②多例不能解决循环依赖,因为多例不需要缓存
全部评论

相关推荐

点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务