3

我们如何告诉 Spring ServiceLocatorFactoryBean 提供服务的默认实例?我有这样的场景。

package strategy;

import model.Document;

public interface IPrintStrategy {
public void print(Document document);
}

和 2 种策略类

package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A4Landscape")
public class PrintA4LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A4 landscape document");
 }

}


package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("A5Landscape")
public class PrintA5LandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("Doing stuff to print an A5 landscape document");
 }

}

策略工厂接口如下

package strategy;

public interface PrintStrategyFactory {

 IPrintStrategy getStrategy(String strategyName);

}

和Spring配置如下

<beans xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 <context:component-scan base-package="strategy">

  <bean class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean" id="printStrategyFactory">
  <property name="serviceLocatorInterface" value="strategy.PrintStrategyFactory">
 </property></bean>

 <alias alias="A4P" name="A4Portrait">
 <alias alias="A4L" name="A4Landscape">
</alias></alias></context:component-scan></beans>

和我的测试班

import model.Document;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import strategy.PrintStrategyFactory;


@ContextConfiguration(locations = {"classpath:/spring-config.xml"})
public class SpringFactoryPatternTest extends AbstractTestNGSpringContextTests{

 @Autowired
 private PrintStrategyFactory printStrategyFactory;

 @Test
 public void printStrategyFactoryTest(){
  Document doc = new Document();

  printStrategyFactory.getStrategy("A4L").print(doc);
  printStrategyFactory.getStrategy("A5L").print(doc);

  printStrategyFactory.getStrategy("Something").print(doc);

 }
}

当我像上次通话一样向工厂传递一些文本时会发生什么

  printStrategyFactory.getStrategy("Something").print(doc);

有没有办法配置 ServiceLocatorFactoryBean 来发回我的打印策略的默认实例,比如下面的类的实例。

package strategy;

import model.Document;

import org.springframework.stereotype.Component;

@Component("invalid")
public class InvalidLandscapeStrategy implements IPrintStrategy{

 @Override
 public void print(Document document) {
  System.out.println("INVALID DOCUMENT STRATEGY");
 }

}
4

1 回答 1

0

我没有找到真正简洁的方法,但这里有 3 个不太简洁的选项。

1:我遇到同样问题时使用的选项是使用setServiceLocatorExceptionClass方法并设置自己的异常类,然后将其捕获并默认。

// Checked exception for service locator factory
public class PrintStrategyException extends Exception
{
    public PrintStrategyException(String message)
    {
        super(message);
    }

    public PrintStrategyException(String message, Throwable cause)
    {
        super(message, cause);
    }

    public PrintStrategyException(Throwable cause)
    {
        super(cause);
    }
}

我使用了 JAVA API,但将其转换为您的 XML 配置:

<bean class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean" id="printStrategyFactory">
    <property name="serviceLocatorInterface" value="strategy.PrintStrategyFactory"/>
    <property name="setServiceLocatorExceptionClass" value="strategy.PrintStrategyException"/>
</bean>

您可能在这里需要一个 RuntimeException,但由于我故意使用已检查的异常,我可以(并且必须)捕获它:

try {
    strategy = printStrategyFactory.getStrategy("A4L");
}
catch (PrintStrategyException e) {
    // strategy was not found, use the default "invalid"
    strategy = printStrategyFactory.getStrategy("invalid");
}
strategy.print(doc);

2:与选项 1 相同,但在找不到 bean 时捕获标准 Spring 异常之一,而不是自己创建。

请注意,我的 try-catch 代码在您的测试类中,因此这种方法实际上取决于创建一个包装类来获取您的策略,您可以像在测试中所做的那样自动装配工厂。(使您的测试类成为生产类)。这样做的需要 - 将您的调用以将工厂实例化到一个地方以便您可以处理异常 - 是使这种方法不太理想的原因。它似乎只是将您的原始问题包装在更好的包装中,但实际上,您可以在任何地方捕获该异常(您不必我一样使用通用方法)并通过检查它(与运行时)并添加它对于工厂方法签名,您将其作为 API 的一部分,以便所有调用者都需要处理它。

3:最后一个非常老套的方法是利用基于属性的方法setServiceMappings并覆盖 java.util.Properties 类:

// I'm sticking with the Annotation approach here, but you can transcribe it into XML
@Bean("strategyFactory")
public FactoryBean serviceLocatorFactoryBean() {
    ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
    factoryBean.setServiceLocatorInterface(PrintStrategyFactory.class);
    Properties myProperties = new MyProperties();
    myProperties.setProperty("A4P", "A4Portrait");
    myProperties.setProperty("A4L", "A4Landscape");
    factoryBean.setServiceMappings(myProperties);
    return factoryBean;
}

public static class MyProperties extends Properties
{
    @Override
    public String getProperty(String key)
    {
        String value = super.getProperty(key);

        // Strategy name is not known, default to "invalid" strategy
        return value == null ? "invalid" : value;
    }
}

这是 hacky,因为它依赖于 Spring 类将在 Properties 对象上调用 getProperty() 的知识。而且,确实 java.util.Properties 不是final。如果 Spring 想要支持这个解决方案,他们会使用像Map而不是Properties的接口。

但是,虽然调用 getProperty() 在合约中并不明确,但它是强烈暗示的,并且不可避免地会调用 getProperty()。这个解决方案将无一例外地适用于任何地方。

所以它有效。不过,我仍然更喜欢选项 1,因为它更整洁。我正在使用我自己的检查异常。

于 2020-04-27T14:01:20.350 回答