使用 Spring3注释实现一个简单的工厂模式

我想知道如何用 Spring3注释实现简单的工厂模式。我在文档中看到,您可以创建调用工厂类并运行工厂方法的 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 变量始终为空。我相信这是因为我自己在工厂内创建对象,自动装配并没有发生。有没有一种方法可以添加注释来使这种方法正确工作?

谢谢

145294 次浏览

You are right, by creating object manually you are not letting Spring to perform autowiring. Consider managing your services by Spring as well:

@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;
}
}
}

But I would consider the overall design to be rather poor. Wouldn't it better to have one general MyService implementation and pass one/two/three string as extra parameter to checkStatus()? What do you want to achieve?

@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();
}
}
}

This is still poorly designed because adding new MyService implementation requires MyServiceAdapter modification as well (SRP violation). But this is actually a good starting point (hint: map and Strategy pattern).

Why not add the interface FactoryBean to MyServiceFactory (to tell Spring that it's a factory), add a register(String service, MyService instance) then, have each of the services call:

@Autowired
MyServiceFactory serviceFactory;


@PostConstruct
public void postConstruct() {
serviceFactory.register(myName, this);
}

This way, you can separate each service provider into modules if necessary, and Spring will automagically pick up any deployed and available service providers.

You can manually ask Spring to Autowire it.

Have your factory implement ApplicationContextAware. Then provide the following implementation in your factory:

@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

and then do the following after creating your bean:

YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.

This will autowire your LocationService perfectly fine.

You could also declaratively define a bean of type ServiceLocatorFactoryBean that will act as a Factory class. it supported by Spring 3.

A FactoryBean implementation that takes an interface which must have one or more methods with the signatures (typically, MyService getService() or MyService getService(String id)) and creates a dynamic proxy which implements that interface

Here's an example of implementing the Factory pattern using Spring

One more clearly example

I suppose you to use org.springframework.beans.factory.config.ServiceLocatorFactoryBean. It will much simplify your code. Except MyServiceAdapter u can only create interface MyServiceAdapter with method MyService getMyService and with alies to register your classes

Code

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" />

You could instantiate "AnnotationConfigApplicationContext" by passing all your service classes as parameters.

@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);
}
}

The following worked for me:

The interface consist of you logic methods plus additional identity method:

public interface MyService {
String getType();
void checkStatus();
}

Some implementations:

@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
}
}

And the factory itself as following:

@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;
}
}

I've found such implementation easier, cleaner and much more extensible. Adding new MyService is as easy as creating another spring bean implementing same interface without making any changes in other places.

Following answer of DruidKuma

Litte refactor of his factory with autowired constructor:

@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;
}
}

Try this:

public interface MyService {
//Code
}


@Component("One")
public class MyServiceOne implements MyService {
//Code
}


@Component("Two")
public class MyServiceTwo implements MyService {
//Code
}

Based on solution by Pavel Černý here we can make an universal typed implementation of this pattern. To to it, we need to introduce NamedService interface:

    public interface NamedService {
String name();
}

and add abstract class:

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;
}
}

Then we create a concrete factory of specific objects like 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 List the list of implementations of MyService interface at compile time.

This approach works fine if you have multiple similar factories across app that produce objects by name (if producing objects by a name suffice you business logic of course). Here map works good with String as a key, and holds all the existing implementations of your services.

if you have different logic for producing objects, this additional logic can be moved to some another place and work in combination with these factories (that get objects by name).

Following the answer from DruidKuma and jumping_monkey

You can also include optional and make your code a bit nicer and cleaner:

 public static MyService getService(String type) {
return Optional.ofNullable(myServiceCache.get(type))
.orElseThrow(() -> new RuntimeException("Unknown service type: " + type));
}

I have recently worked on similar requirement where in I wanted to use factory pattern but I was not okay with the if else logic which keeps on growing in future and violates the Single Responsibility Principle.

First step, create an interface and have a getType() method, in the given context it will return "one", "two" etc, otherwise it can be anything. This is the common solution most of the people suggested above.

public interface MyService {
String getType();
void checkStatus();
}

Some implementations:

@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
}
}

And the factory itself as following:

@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));
        

}
}

This solution doesn't require extra Map for storing key value of the instances against the type. This solution is extensible without any further code change, as the factory has List auto wiring, so any future implementation of MyService would be easily work. And hence the Single Responsibility Principle is also ensured.

I have used streams() and predicates as I was using Java 8, for earlier version simple for loop would do the work.

This is a variation of the above answers that creates fresh instances.

If the Service depends only on Spring managed beans.

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);
}
}






If you want to include custom parameters in the factory method that are not all managed by Spring you can try one of the following sketched below

  1. ensure a Bean can be instantiated when first discovered by adding empty constructors
@Component("One")
@Scope("prototype")
public class MyServiceOne implements MyService {
//Code
public MyServiceOne(){
...
}


public MyServiceOne(Dependency dep){
...
}


public MyServiceOne(Dependency dep, Integer myFactoryValue){
...
}
}


  1. Or you manually create them in the configuration to be discovered
\\ 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);
}
}


And both solutions allow you to define your factory method like this

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);
}
}

Spring won't Autowire beans if there isn't a clear path as to which one it should use at build time. Since the factory doesn't change, you can Autowire your LocationService there and pass it down to your different services. This can get a bit cumbersome if your classes have more than one dependency e.g. services, repos, etc.

If you don't intend on having a lot of dependencies for your "MyService" classes you can do this:

@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);
}
}
}

Your MyServiceOne class:

@Service
public class MyServiceOne implements MyService{


public LocationService locationService;


public MyServiceOne(LocationService service){
locationService = service;
}


@Override
public checkStatus(){
// your code
}
}

MyService interface

interface MyService{
boolean checkStatus();
}
public interface MyService {
public void save();
//Code
}


@Component("One")
public class MyServiceOne implements MyService {
//Code
public void save(){
System.out.println("one");
}
}


@Component("Two")
public class MyServiceTwo implements MyService {
//Code
public void save(){
System.out.println("two");
}
}


public class FatoryClass{
@Autowired
//@Qualifier("One") //This is your default child class use qualifier or just my sample
MyService One;
public MyService setMyservice(int typeId){
switch(typeId){
case 1:
One = new MyServiceTwo();
break;
default:
System.out.println("Default child instance");
}
return One;
}
}


@Service
public class serviceComponent{
@Autowired
FatoryClass facto;


public void setFactoryMethod(int typeId){
facto.setMyService(typeId);
facto.save();
}
}