使用 Spring 3 注解实现一个简单的工厂模式
Posted
技术标签:
【中文标题】使用 Spring 3 注解实现一个简单的工厂模式【英文标题】:Implement a simple factory pattern with Spring 3 annotations 【发布时间】:2011-09-17 11:05:39 【问题描述】:我想知道如何使用 Spring 3 注释实现简单工厂模式。我在文档中看到您可以创建调用工厂类并运行工厂方法的 bean。我想知道这是否可能仅使用注释。
我有一个当前调用的控制器
MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();
MyService 是一个接口,其中包含一个名为 checkStatus() 的方法。
我的工厂类如下所示:
@Component
public class MyServiceFactory
public static MyService getMyService(String service)
MyService myService;
service = service.toLowerCase();
if (service.equals("one"))
myService = new MyServiceOne();
else if (service.equals("two"))
myService = new MyServiceTwo();
else if (service.equals("three"))
myService = new MyServiceThree();
else
myService = new MyServiceDefault();
return myService;
MyServiceOne 类如下所示:
@Autowired
private LocationService locationService;
public boolean checkStatus()
//do stuff
当我运行此代码时,locationService 变量始终为空。我相信这是因为我自己在工厂内创建对象并且没有进行自动装配。有没有办法添加注释以使其正常工作?
谢谢
【问题讨论】:
来晚了,但我认为此信息可能对其他人有所帮助。您可以在 Spring 中使用 ServiceLocatorFactoryBean of。处理工厂模式真的很巧妙。 你找到正确答案了吗?这里有很多错误的答案 【参考方案1】:如果没有明确的路径来确定在构建时应该使用哪一个,Spring 不会自动装配 bean。由于工厂没有改变,您可以在那里自动连接您的 LocationService 并将其传递给您的不同服务。如果您的类有多个依赖项,例如,这可能会有点麻烦。服务、回购等。
如果您不打算对“MyService”类有很多依赖项,您可以这样做:
@Component
public class MyServiceFactory()
@Autowired
LocationService locationService;
public static MyService getMyService(String service)
service = service.toLowerCase();
switch(service)
case "one":
return new MyServiceOne(locationService);
case "two":
return new MyServiceTwo(locationService);
case "three":
return new MyServiceThree(locationService);
default:
return new MyServiceDefault(locationService);
你的 MyServiceOne 类:
@Service
public class MyServiceOne implements MyService
public LocationService locationService;
public MyServiceOne(LocationService service)
locationService = service;
@Override
public checkStatus()
// your code
我的服务接口
interface MyService
boolean checkStatus();
【讨论】:
【参考方案2】:这是创建新实例的上述答案的变体。
如果Service
仅依赖于Spring
托管bean。
public interface MyService
//Code
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService
//Code
public MyServiceOne(Dependency dep)
...
@Component("Two")
@Scope("prototype")
public class MyServiceTwo implements MyService
//Code
public class Factory
Map<String,MyService> services;
ApplicationContext context;
Dependency dep;
public Factory(Map<String, MyService> components, ApplicationContext context, Dependency dep)
...
MyService service(String type)
return context.getBean(services.get(type).getClass());
@Configuration
public class Config
@Bean
Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep)
return new Factory(map,context,dep);
如果您想在工厂方法中包含并非全部由 Spring
管理的自定义参数,您可以尝试以下草图之一
-
通过添加空的构造函数确保 Bean 在首次发现时可以被实例化
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService
//Code
public MyServiceOne()
...
public MyServiceOne(Dependency dep)
...
public MyServiceOne(Dependency dep, Integer myFactoryValue)
...
-
或者您在要发现的配置中手动创建它们
\\ no longer available in autoscan
public class MyServiceOne implements MyService
//Code
public MyServiceOne()
...
public MyServiceOne(Dependency dep, Integer myFactoryValue)
...
@Configuration
public class Config
@Bean("One")
@Scope("prototype")
Service serviceOne()
// used only for dynamic discovery
return new ServiceOne();
...
@Bean
Factory languageFactory(Map<String,Service> map, ApplicationContext context, Dependency dep)
return new Factory(map,context,dep);
这两种解决方案都允许您像这样定义工厂方法
public class Factory
....
MyService service(String type, Integer someParameter)
// you provide the parameters for the constructor
return context.getBean(services.get(type).getClass(),dep,someParameter);
【讨论】:
【参考方案3】:我最近处理了类似的需求,我想使用工厂模式,但我对 if else 逻辑不满意,它在未来会继续增长并违反单一责任原则。
第一步,创建一个接口并拥有一个 getType() 方法,在给定的上下文中它将返回“one”、“two”等,否则它可以是任何东西。 这是上面大多数人建议的常见解决方案。
public interface MyService
String getType();
void checkStatus();
一些实现:
@Component
public class MyServiceOne implements MyService
@Override
public String getType()
return "one";
@Override
public void checkStatus()
// Your code
@Component
public class MyServiceTwo implements MyService
@Override
public String getType()
return "two";
@Override
public void checkStatus()
// Your code
@Component
public class MyServiceThree implements MyService
@Override
public String getType()
return "three";
@Override
public void checkStatus()
// Your code
工厂本身如下:
@Service
public class MyServiceFactory
@Autowired
private List<MyService> services;
public static MyService getService(final String type)
return services
.stream().filter(service -> type.equals(service.getType()))
.findFirst()
.orElseThrow(throw new RuntimeException("Unknown service type: " + type));
此解决方案不需要额外的 Map 来根据类型存储实例的键值。此解决方案无需任何进一步的代码更改即可扩展,因为工厂具有 List 自动布线,因此 MyService 的任何未来实现都将很容易工作。因此也保证了单一职责原则。
我在使用 Java 8 时使用了流()和谓词,因为早期版本简单的 for 循环就可以完成这项工作。
【讨论】:
【参考方案4】:遵循DruidKuma 和jumping_monkey 的回答
您还可以包含可选的,让您的代码更漂亮、更简洁:
public static MyService getService(String type)
return Optional.ofNullable(myServiceCache.get(type))
.orElseThrow(() -> new RuntimeException("Unknown service type: " + type));
【讨论】:
【参考方案5】:关注DruidKuma的回答
使用自动装配的构造函数对他的工厂进行少量重构:
@Service
public class MyServiceFactory
private static final Map<String, MyService> myServiceCache = new HashMap<>();
@Autowired
private MyServiceFactory(List<MyService> services)
for(MyService service : services)
myServiceCache.put(service.getType(), service);
public static MyService getService(String type)
MyService service = myServiceCache.get(type);
if(service == null) throw new RuntimeException("Unknown service type: " + type);
return service;
【讨论】:
Nice Pavel,锦上添花,考虑将构造函数的可访问性更改为私有,因为类不需要从外部构造/访问,Spring 仍然可以做到通过反射自动接线。我测试了它,它可以工作。 @jumping_monkey 'List@Autowired
的美妙之处,它很神奇。所有实现“MyService”的类都将自动连接到服务List
。试试看。
在这种情况下测试呢?如果构造函数是私有的,你将如何进行单元测试?【参考方案6】:
你可以手动让 Spring 自动装配它。
让您的工厂实现 ApplicationContextAware。然后在您的工厂中提供以下实现:
@Override
public void setApplicationContext(final ApplicationContext applicationContext)
this.applicationContext = applicationContext;
然后在创建 bean 后执行以下操作:
YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.
这将完美地自动连接您的 LocationService。
【讨论】:
【参考方案7】:基于 Pavel Černý here 的解决方案 我们可以对这种模式进行通用类型化的实现。 为此,我们需要引入 NamedService 接口:
public interface NamedService
String name();
并添加抽象类:
public abstract class AbstractFactory<T extends NamedService>
private final Map<String, T> map;
protected AbstractFactory(List<T> list)
this.map = list
.stream()
.collect(Collectors.toMap(NamedService::name, Function.identity()));
/**
* Factory method for getting an appropriate implementation of a service
* @param name name of service impl.
* @return concrete service impl.
*/
public T getInstance(@NonNull final String name)
T t = map.get(name);
if(t == null)
throw new RuntimeException("Unknown service name: " + name);
return t;
然后我们创建一个具体对象的具体工厂,例如 MyService:
public interface MyService extends NamedService
String name();
void doJob();
@Component
public class MyServiceFactory extends AbstractFactory<MyService>
@Autowired
protected MyServiceFactory(List<MyService> list)
super(list);
where 列出编译时 MyService 接口的实现列表。
如果您在应用程序中有多个按名称生成对象的类似工厂(如果按名称生成对象当然足以满足您的业务逻辑),则此方法可以正常工作。 这里的 map 可以很好地使用 String 作为键,并保存您服务的所有现有实现。
如果您有不同的逻辑来生成对象,这个额外的逻辑可以移动到另一个地方,并与这些工厂(通过名称获取对象)结合使用。
【讨论】:
【参考方案8】:试试这个:
public interface MyService
//Code
@Component("One")
public class MyServiceOne implements MyService
//Code
@Component("Two")
public class MyServiceTwo implements MyService
//Code
【讨论】:
【参考方案9】:您还可以声明性地定义一个 ServiceLocatorFactoryBean 类型的 bean,它将充当工厂类。 Spring 3 支持它。
一个 FactoryBean 实现,它采用一个接口,该接口必须具有一个或多个带有签名的方法(通常是 MyService getService() 或 MyService getService(String id)),并创建一个实现该接口的动态代理
这里是an example of implementing the Factory pattern using Spring
One more clearly example
【讨论】:
ServiceLocatorFactoryBean 的另一个例子也可以在这里找到:jcombat.com/spring/…【参考方案10】:您可以通过将所有服务类作为参数传递来实例化“AnnotationConfigApplicationContext”。
@Component
public class MyServiceFactory
private ApplicationContext applicationContext;
public MyServiceFactory()
applicationContext = new AnnotationConfigApplicationContext(
MyServiceOne.class,
MyServiceTwo.class,
MyServiceThree.class,
MyServiceDefault.class,
LocationService.class
);
/* I have added LocationService.class because this component is also autowired */
public MyService getMyService(String service)
if ("one".equalsIgnoreCase(service))
return applicationContext.getBean(MyServiceOne.class);
if ("two".equalsIgnoreCase(service))
return applicationContext.getBean(MyServiceTwo.class);
if ("three".equalsIgnoreCase(service))
return applicationContext.getBean(MyServiceThree.class);
return applicationContext.getBean(MyServiceDefault.class);
【讨论】:
如何在静态函数中引用 applicationContext ...?它给了我一个错误,因为它也不是静态的 哦,是的,你显然是对的。一个简单的错误。我刚刚从方法“getMyService”中删除了“静态” 如果我在 diff 实现类中有带 diff 参数的参数化构造函数,如何遵循上述技术?【参考方案11】:为什么不将接口FactoryBean添加到MyServiceFactory(告诉Spring它是一个工厂),然后添加一个寄存器(字符串服务,MyService实例),然后让每个服务调用:
@Autowired
MyServiceFactory serviceFactory;
@PostConstruct
public void postConstruct()
serviceFactory.register(myName, this);
这样,您可以在必要时将每个服务提供者分成模块,Spring 会自动选取任何已部署和可用的服务提供者。
【讨论】:
我喜欢这个解决方案,只是为了扩展一点,而不是使用抽象 MyService 接口,它将自动装配工厂并执行 PostConstruct。所以你的服务是干净的。 只是一个警告,我在使用@Transactional 注释注释的方法为spring服务类实现类似代码时遇到了一个问题。基本上,当您使用 `this` 关键字注册一个类并且该类被代理时,传递给您的服务工厂的类不会被代理。见spring.io/blog/2012/05/23/…【参考方案12】:以下内容对我有用:
接口由逻辑方法和附加标识方法组成:
public interface MyService
String getType();
void checkStatus();
一些实现:
@Component
public class MyServiceOne implements MyService
@Override
public String getType()
return "one";
@Override
public void checkStatus()
// Your code
@Component
public class MyServiceTwo implements MyService
@Override
public String getType()
return "two";
@Override
public void checkStatus()
// Your code
@Component
public class MyServiceThree implements MyService
@Override
public String getType()
return "three";
@Override
public void checkStatus()
// Your code
工厂本身如下:
@Service
public class MyServiceFactory
@Autowired
private List<MyService> services;
private static final Map<String, MyService> myServiceCache = new HashMap<>();
@PostConstruct
public void initMyServiceCache()
for(MyService service : services)
myServiceCache.put(service.getType(), service);
public static MyService getService(String type)
MyService service = myServiceCache.get(type);
if(service == null) throw new RuntimeException("Unknown service type: " + type);
return service;
我发现这样的实现更简单、更简洁且更具可扩展性。添加新的 MyService 就像创建另一个实现相同接口的 spring bean 一样简单,而无需在其他地方进行任何更改。
【讨论】:
“private List我想你使用 org.springframework.beans.factory.config.ServiceLocatorFactoryBean。 这将大大简化您的代码。 除了 MyServiceAdapter 之外,您只能使用 MyService getMyService 方法和别名来创建接口 MyServiceAdapter 来注册您的类
代码
bean id="printStrategyFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="YourInterface" value="factory.MyServiceAdapter" />
</bean>
<alias name="myServiceOne" alias="one" />
<alias name="myServiceTwo" alias="two" />
【讨论】:
【参考方案14】:你是对的,通过手动创建对象你不会让 Spring 执行自动装配。考虑通过 Spring 管理您的服务:
@Component
public class MyServiceFactory
@Autowired
private MyServiceOne myServiceOne;
@Autowired
private MyServiceTwo myServiceTwo;
@Autowired
private MyServiceThree myServiceThree;
@Autowired
private MyServiceDefault myServiceDefault;
public static MyService getMyService(String service)
service = service.toLowerCase();
if (service.equals("one"))
return myServiceOne;
else if (service.equals("two"))
return myServiceTwo;
else if (service.equals("three"))
return myServiceThree;
else
return myServiceDefault;
但我认为整体设计相当糟糕。拥有一个通用的MyService
实现并将one
/two
/three
字符串作为额外参数传递给checkStatus()
不是更好吗?你想达到什么目标?
@Component
public class MyServiceAdapter implements MyService
@Autowired
private MyServiceOne myServiceOne;
@Autowired
private MyServiceTwo myServiceTwo;
@Autowired
private MyServiceThree myServiceThree;
@Autowired
private MyServiceDefault myServiceDefault;
public boolean checkStatus(String service)
service = service.toLowerCase();
if (service.equals("one"))
return myServiceOne.checkStatus();
else if (service.equals("two"))
return myServiceTwo.checkStatus();
else if (service.equals("three"))
return myServiceThree.checkStatus();
else
return myServiceDefault.checkStatus();
这仍然设计不佳,因为添加新的 MyService
实现还需要修改 MyServiceAdapter
(违反 SRP)。但这实际上是一个很好的起点(提示:地图和策略模式)。
【讨论】:
如果你打算这样做,为什么不去掉中间人,直接将不同的服务类型自动装配到调用工厂的 bean 中? 好问题。也许是因为我们想将客户端与不同的实现分离?看看我在此期间所做的编辑。 您的建议使代码正常工作。我最初将代码放在一个类中,但我认为稍后添加实现 MyService 接口的服务会更容易,我只需要更改工厂类中的代码。 第一个选项不起作用,因为 getMyService() 是静态的。因此 myServiceOne、myServiceTwo 也必须是静态的。 什么?如果我的服务有 10 种方法?您是否要在“适配器”中实现所有这些(这是一个误导性名称)以上是关于使用 Spring 3 注解实现一个简单的工厂模式的主要内容,如果未能解决你的问题,请参考以下文章