Подтвердить что ты не робот

Внедрите простой шаблон factory с помощью Spring 3 аннотаций

Мне было интересно, как я могу реализовать простой шаблон factory с Spring 3 аннотациями. Я видел в документации, что вы можете создать beans, которые вызывают класс factory и запускают метод factory. Мне было интересно, возможно ли это только с помощью аннотаций.

У меня есть контроллер, который в настоящее время вызывает

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

MyService - это интерфейс с одним методом, называемым checkStatus().

Мой класс factory выглядит следующим образом:

@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 имеет значение alwasy null. Я верю, что это потому, что я сам создаю объекты внутри factory, и автоустановка не происходит. Есть ли способ добавить аннотации, чтобы сделать эту работу правильно?

Спасибо

4b9b3361

Ответ 1

Вы правы, создавая объект вручную, вы не позволяете 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). Но на самом деле это хорошая отправная точка (подсказка: карта и шаблон стратегии).

Ответ 2

Следующие работали для меня:

Интерфейс состоит из логических методов и дополнительного метода идентификации:

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

И сам factory выглядит следующим образом:

@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 реализации одного и того же интерфейса без каких-либо изменений в других местах.

Ответ 3

Почему бы не добавить интерфейс FactoryBean в MyServiceFactory (чтобы сообщить Spring, что он factory), добавьте регистр (служба String, экземпляр MyService), затем вызовите каждый из сервисов:

@Autowired
MyServiceFactory serviceFactory;

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

Таким образом, вы можете разделить каждого поставщика услуг на модули, если это необходимо, и Spring автоматически подберет всех развернутых и доступных поставщиков услуг.

Ответ 4

Вы можете вручную запросить Spring автообновить его.

Попросите factory реализовать ApplicationContextAware. Затем выполните следующую реализацию в factory:

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

Ответ 5

Вы также можете декларативно определить bean-компонент типа ServiceLocatorFactoryBean, который будет действовать как класс Factory. поддерживается spring 3.

Реализация FactoryBean, которая принимает интерфейс, который должен иметь один или несколько методов с подписями (обычно MyService getService() или MyService getService (String id)) и создает динамический прокси, который реализует этот интерфейс

Вот пример реализации шаблона Factory с использованием Spring

Еще более наглядный пример

Ответ 6

Следующий ответ ДруидКума

Литт-рефакторинг его завода с конструктором с автопроводкой:

@Service
public class MyServiceFactory {

    private static final Map<String, MyService> myServiceCache = new HashMap<>();

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

Ответ 7

Предположим, вы используете org.springframework.beans.factory.config.ServiceLocatorFactoryBean. Это значительно упростит ваш код. Кроме MyServiceAdapter и вы можете создавать интерфейс MyServiceAdapter с помощью метода MyService getMyService и с помощью псевдонимов для регистрации ваших классов

код

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

Ответ 8

Вы можете создать экземпляр "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);
    }
}

Ответ 9

Попробуй это:

public interface MyService {
 //Code
}

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

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

Ответ 10

На основании решения Павла Черны здесь мы можем сделать универсальную реализацию набранное этого шаблона. Для этого нам нужно ввести интерфейс 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);
    }
}

где Список списка реализаций интерфейса MyService во время компиляции.

Этот подход хорошо работает, если у вас есть несколько одинаковых фабрик в приложении, которые производят объекты по имени (если, конечно, бизнес-логики достаточно для создания объектов по имени). Здесь map хорошо работает с String в качестве ключа и содержит все существующие реализации ваших сервисов.

если у вас есть другая логика для создания объектов, эта дополнительная логика может быть перемещена в другое место и работать в сочетании с этими фабриками (которые получают объекты по имени).