Implementar um padrão de fábrica simples com anotações de Primavera 3

Estava a pensar como poderia implementar o padrão de fábrica simples com anotações de Primavera 3. Eu vi na documentação que você pode criar feijões que chamam de classe de fábrica e executar um método de fábrica. Estava a pensar se isto seria possível usando apenas anotações.

tenho um controlador que actualmente chama

MyService myService = myServiceFactory.getMyService(test);
result = myService.checkStatus();

MyService é uma interface com um método chamado checkStatus().

A minha aula de fábrica parece ... isto:
@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;
    }
}
A minha classe é assim:
@Autowired
private LocationService locationService;

public boolean checkStatus() {
      //do stuff
}

Quando eu executar este código a variável locationService é sempre nula. Eu acredito que isso é porque eu estou criando os objetos dentro da fábrica e autowiring não está acontecendo. Existe uma maneira de adicionar anotações para fazer isso funcionar corretamente?

Obrigado.
Author: blong824, 2011-06-17

11 answers

Tem razão, ao criar um objecto manualmente, não está a deixar a mola efectuar a auto-fixação. Considere também a gestão dos seus serviços até à Primavera:

@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;
        }
    }
}
Mas eu consideraria o design geral um pouco pobre. Não seria melhor ter uma implementação geral e passar one/two/three string como parâmetro extra para checkStatus()? O que queres alcançar?
@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();
        }
    }
}

Isto é ainda mal concebido porque a adição de novos MyService implementação requer MyServiceAdapter modificação também (violação de SRP). Mas este é realmente um bom ponto de partida (dica: mapa e padrão de estratégia).

 43
Author: Tomasz Nurkiewicz, 2011-06-17 19:40:37

O seguinte funcionou para mim:

A interface consiste nos seus métodos lógicos mais o método de identidade adicional:

public interface MyService {
    String getType();
    void checkStatus();
}
Algumas implementações:
@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
    }
}

E a própria fábrica como se segue:

@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;
    }
}
Achei essa implementação mais fácil, mais limpa e muito mais extensível. Adicionar Novo MyService é tão fácil quanto criar outro spring bean implementando a mesma interface sem fazer quaisquer alterações em outros lugares.
 104
Author: DruidKuma, 2016-09-07 05:03:57

Após resposta de DruidKuma

Refactor da sua fábrica com o construtor Autocolante:

@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;
    }
}
 17
Author: Pavel Černý, 2020-06-24 04:11:50

Porque não adicionar a interface FactoryBean ao MyServiceFactory( para dizer à Spring que é uma fábrica), adicionar um registo (String service, MyService instance), então, ter cada um dos Serviços chamada:

@Autowired
MyServiceFactory serviceFactory;

@PostConstruct
public void postConstruct() {
    serviceFactory.register(myName, this);
}
Deste modo, poderá separar cada prestador de serviços em módulos, se necessário, e a Primavera recolherá automaticamente todos os prestadores de serviços disponíveis e implantados.
 10
Author: Jeff Wang, 2016-09-19 14:35:39

Pode pedir manualmente à Primavera para a Autowire.

Pede à tua fábrica que implemente o formulário de candidatura. Em seguida, forneça a seguinte implementação em sua fábrica:

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

E depois fazer o seguinte depois de criar o seu feijão:

YourBean bean = new YourBean();
applicationContext.getAutowireCapableBeanFactory().autowireBean(bean);
bean.init(); //If it has an init() method.
Isto vai autowire o seu serviço de localização perfeitamente bem.
 9
Author: rogermenezes, 2019-11-12 21:47:23

Você também pode definir declarativamente um feijão do tipo ServiceLocatorFactoryBean {[3] } que irá funcionar como uma classe de fábrica. foi apoiado pela Primavera 3.

Uma implementação de FactoryBean que toma uma interface que deve ter um ou mais métodos com as assinaturas (tipicamente, MyService getService () ou MyService getService (String id))) e cria um proxy dinâmico que implementa essa interface

Aqui está um exemplo de implementação do padrão de fábrica usando Primavera

Mais um exemplo claro

 7
Author: kyiu, 2018-01-30 14:01:02

Com base na solução de Pavel Černýaqui podemos fazer uma implementação universal tipada deste padrão. Para isso, precisamos introduzir interface NamedService:

    public interface NamedService {
       String name();
    }

E Adicionar classe abstracta:

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;
    }
}
Então criamos uma fábrica de concreto de objetos específicos como o 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);
    }
}

Onde listar a lista de implementações da interface MyService na hora de compilação.

Esta abordagem funciona bem se você tem várias fábricas similares através da aplicação que produzir objetos pelo nome (se produzir objetos por um nome é suficiente a lógica de negócios, é claro). Aqui o mapa funciona bem com String como uma chave, e detém todas as implementações existentes de seus serviços.

Se você tem uma lógica diferente para produzir objetos, esta lógica adicional pode ser movida para outro lugar e trabalhar em combinação com essas fábricas (que recebem objetos pelo nome).

 5
Author: Andreas Gelever, 2019-07-09 09:57:24
Acho que deves usar org.quadro de primavera.feijao.fabrica.configuracao.Operacionaldefactorybean. Simplificará muito o seu código. Excepto que o MyServiceAdapter u só pode criar uma interface MyServiceAdapter com o método MyService getMyService e com alies para registar as suas aulas

Código

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" />
 2
Author: Mikhail, 2015-06-09 15:07:39

Você poderia instanciar "Annotationconfigationcontext" passando todas as suas classes de serviço como parâmetros.

@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);
    }
}
 2
Author: OlivierTerrien, 2018-01-14 14:24:34

Tenta isto:

public interface MyService {
 //Code
}

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

@Component("Two")
public class MyServiceTwo implements MyService {
 //Code
}
 0
Author: Viren Dholakia, 2019-05-01 06:02:01

Seguindo a resposta de DruidKuma e jumping_monkey

Você também pode incluir opcional e tornar o seu código um pouco mais agradável e mais limpo:

 public static MyService getService(String type) {
        return Optional.ofNullable(myServiceCache.get(type))
                .orElseThrow(() -> new RuntimeException("Unknown service type: " + type));
 }
 0
Author: dextertron_, 2020-08-27 10:42:23