Внедрите простой шаблон 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, и автоустановка не происходит. Есть ли способ добавить аннотации, чтобы сделать эту работу правильно?
Спасибо
Ответы
Ответ 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 в качестве ключа и содержит все существующие реализации ваших сервисов.
если у вас есть другая логика для создания объектов, эта дополнительная логика может быть перемещена в другое место и работать в сочетании с этими фабриками (которые получают объекты по имени).