Spring — это инструмент с открытым исходным кодом, который многие веб-разработчики Java считают незаменимым для любого нетривиального веб-приложения. Фреймворк предлагает множество элегантных решений для общих задач разработки программного обеспечения. По словам авторов на
springsource.org [1]:
Spring Framework предоставляет комплексную модель программирования и конфигурации для современных корпоративных приложений на основе Java — на любой платформе развертывания. Ключевым элементом Spring является инфраструктурная поддержка на уровне приложений: Spring фокусируется на «подключении» корпоративных приложений, чтобы группы могли сосредоточиться на бизнес-логике на уровне приложений без ненужных связей с конкретными средами развертывания.
Одна из наиболее часто используемых функций Spring — Injection of Concerns (IOC). Внедрение проблем позволяет повторно использовать код котельной пластины, позволяя «вводить» код клиента в общую логику. Это похоже на (или некоторые сказали бы то же самое как) Инверсия Контроля (IOC) или Инъекция Зависимости (см. Koirala, Shivprasad, Инъекция Зависимости (DI) против Инверсии Контроля (IOC) [2]). Впрыскивание, однако, не обходится без затрат, и во многих случаях эти затраты не приносят пользы. Инъекции не следует регулярно использовать в тех случаях, когда не может быть четко продемонстрирована польза. Это означает, что в большинстве случаев разработчики бизнес-логики не должны использовать инъекции. Мы продемонстрируем это на простом примере. Этот пример является простым приложением, но принципы, продемонстрированные здесь, также применимы ко всем приложениям,особенно крупномасштабные приложения, включая крупномасштабные веб-приложения.
Весь проект для этого примера, включая весь исходный код, доступен по адресу:
https://svn.code.sf.net/p/springexample/code/tags/release_1_0_0/.
В этом примере мы начнем с нескольких служб, которые предоставляют информация о фруктах. Эти сервисы будут использоваться в простом плоском примере старого Java-объекта (POJO) и в примере Spring-wired. Используемые нами услуги предоставят информацию о стоимости, цене, местонахождении и доступных типах для данного фрукта. Эти сервисы будут использоваться классом действий, который предоставляет объект значения данных (DVO), который представляет данные, доступные для выбранного фрукта.
Исходный код службы стоимости фруктов выглядит следующим образом:
public class FruitCostService { public int getCost(Fruit fruit) { int rtn = 0; switch (fruit) { case APPLE: rtn = 20; break; case MANGO: rtn = 40; break; case MELON: rtn = 10; break; case BANANA: rtn = 25; break; default: throw new RuntimeException(); } return rtn; } }
Исходный код сервиса цен на фрукты выглядит следующим образом:
public class FruitPriceService { public int getCost(Fruit fruit) { int rtn = 0; switch (fruit) { case APPLE: rtn = 22; break; case MANGO: rtn = 44; break; case MELON: rtn = 11; break; case BANANA: rtn = 28; break; default: throw new RuntimeException(); } return rtn; } }
Исходный код для службы определения местоположения выглядит следующим образом:
public class FruitLocationService { public Location getLocation(Fruit fruit) { Location rtn = null; switch (fruit) { case APPLE: rtn = Location.BIN_1; break; case MANGO: rtn = Location.BIN_2; break; case MELON: rtn = Location.BIN_3; break; case BANANA: rtn = Location.BIN_4; break; } return rtn; } }
Исходный код для службы типа фрукт выглядит следующим образом (некоторая бизнес-логика была исключена для краткости):
public class FruitTypeService { public List<String> getTypes(Fruit fruit) { List<String> rtn = null; switch (fruit) { case APPLE: rtn = getApples(); break; case MANGO: rtn = getMangos(); break; case MELON: rtn = getMelons(); break; case BANANA: rtn = getBananas(); break; default: rtn = getUnknown(); break; } return rtn; } }
Для нашего примера мы будем извлекать данные из сервисов и представлять эти данные в одном объекте значения данных (DVO), как показано ниже.
public class FruitDvo { private Fruit fruit; private Location location; private int cost; private int price; private List<String> types; public Fruit getFruit() { return fruit; } public void setFruit(Fruit fruit) { this.fruit = fruit; } public Location getLocation() { return location; } public void setLocation(Location location) { this.location = location; } public int getCost() { return cost; } public void setCost(int cost) { this.cost = cost; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public List<String> getTypes() { return types; } public void setTypes(List<String> types) { this.types = types; } }
Из классов обслуживания, показанных выше, очень легко создать класс действия, который получает данные для данного типа фруктов, используя простое кодирование Java, как показано ниже.
public class FruitAction { public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(new FruitCostService().getCost(fruit)); rtn.setLocation(new FruitLocationService().getLocation(fruit)); rtn.setPrice(new FruitPriceService().getCost(fruit)); rtn.setTypes(new FruitTypeService().getTypes(fruit)); return rtn; } }
Есть несколько способов, которыми этот пример может быть Spring-wired. Для этого примера мы сделаем простую XML-разводку для сервисов и подключим сервисы к классу действия через конструктор.
Для этого мы сначала изменим класс действия, чтобы службы могли быть введены. Мы могли бы также сделать переменные-члены-сервисы класса и выполнить инъекцию через эти переменные-члены либо в XML, либо с помощью аннотаций.
public class FruitAction { // // instance variables // private FruitCostServiceDefinition fruitCostService; private FruitLocationServiceDefinition fruitLocationService; private FruitPriceServiceDefinition fruitPriceService; private FruitTypeServiceDefinition fruitTypeService; // // constructor // public FruitAction( FruitCostServiceDefinition fruitCostService, FruitLocationServiceDefinition fruitLocationService, FruitPriceServiceDefinition fruitPriceService, FruitTypeServiceDefinition fruitTypeService) { super(); this.fruitCostService = fruitCostService; this.fruitLocationService = fruitLocationService; this.fruitPriceService = fruitPriceService; this.fruitTypeService = fruitTypeService; } /** * Method to get data for fruit. */ public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(fruitCostService.getCost(fruit)); rtn.setLocation(fruitLocationService.getLocation(fruit)); rtn.setPrice(fruitPriceService.getPrice(fruit)); rtn.setTypes(fruitTypeService.getTypes(fruit)); return rtn; } }
Сервисы и класс действий теперь могут быть подключены к Spring в XML согласно следующему:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- original implementation --> <bean id="fruitCostService" class="org.yaorma.simplespringexample.service.fruit.FruitCostService" /> <bean id="fruitLocationService" class="org.yaorma.simplespringexample.service.fruit.FruitLocationService" /> <bean id="fruitPriceService" class="org.yaorma.simplespringexample.service.fruit.FruitPriceService" /> <bean id="fruitTypeService" class="org.yaorma.simplespringexample.service.fruit.FruitTypeService" /> <bean id="fruitAction" class="org.yaorma.simplespringexample.action.fruit.spring.FruitAction"> <constructor-arg name="fruitCostService" ref="fruitCostService" /> <constructor-arg name="fruitLocationService" ref="fruitLocationService" /> <constructor-arg name="fruitPriceService" ref="fruitPriceService" /> <constructor-arg name="fruitTypeService" ref="fruitTypeService" /> </bean> </beans>
Есть две основные проблемы, возникающие при добавлении проводки Spring в этот пример. Первое — это увеличение количества кода, необходимого для выполнения той же задачи. В нашем примере POJO весь код, необходимый для выполнения задачи получения информации, которую мы хотим получить с помощью класса действий, завершается в этом классе. Добавление Spring-проводки вводит несколько строк XML.
Это может показаться несущественным в этом небольшом примере. Этот пример, однако, использует только один класс действий. Разработанные нами производственные веб-приложения часто содержат сотни классов действий и сотни сервисов. Добавление даже нескольких строк кода для каждого сервиса и для каждого действия приводит к созданию тысяч строк XML, которые всегда трудно поддерживать.
Вторая проблема, возникающая в связи с введением Spring Spring в этом примере, — это нарушение инкапсуляции, которое мы вводим. В классе действия примера POJO вся работа, ресурсы и зависимости, необходимые для выполнения задачи получения данных для данного фрукта, инкапсулированы в этом классе.
Когда мы представляем связывание Spring для этого класса, становится необходимым предоставить ресурсы, используемые этим классом, общедоступному интерфейсу этого класса, либо создав переменные-члены и общедоступные сеттеры для этих ресурсов, либо (как мы сделали в этом примере), потребовав клиента код для предоставления этих ресурсов в конструкторе.
Одной из распространенных причин использования Spring Wiring является повторное использование и, в частности, возможность легко или просто заменить одну реализацию другой.
Например, наша существующая SpringAired FruitAction может быть повторно использована и перемонтирована для использования альтернативной реализации каждой из служб. Этот тип замещения особенно полезен в ситуациях, когда необходимо заменить весь слой или систему приложения, и его следует учитывать и планировать на случай, когда вероятность такого типа замещения предвидится или ожидается. Например, это может быть рассмотрено в проекте, где изначально использовалась одна реализация базы данных (например, MySQL), и более надежная реализация базы данных ожидалась по мере завершения проекта (например, Oracle) или когда одна среда могла быть заменена другой. (например, Cayenne, vs. Hibernate) и т. д.
Для этого примера мы создали альтернативные реализации классов обслуживания в пакете org.yaorma.simplespringexample.service.fruit2 . Эти сервисы могут быть внедрены в существующий класс FruitAction, добавив следующее в XML, который определяет нашу проводку Spring, как показано ниже.
<!-- second implementation --> <bean id="specialFruitCostService" class="org.yaorma.simplespringexample.service.fruit.FruitCostService" /> <bean id="specialFruitLocationService" class="org.yaorma.simplespringexample.service.fruit.FruitLocationService" /> <bean id="specialFruitPriceService" class="org.yaorma.simplespringexample.service.fruit.FruitPriceService" /> <bean id="specialFruitTypeService" class="org.yaorma.simplespringexample.service.fruit.FruitTypeService" /> <bean id="specialFruitAction" class="org.yaorma.simplespringexample.action.fruit.spring.FruitAction"> <constructor-arg name="fruitCostService" ref="specialFruitCostService" /> <constructor-arg name="fruitLocationService" ref="specialFruitLocationService" /> <constructor-arg name="fruitPriceService" ref="specialFruitPriceService" /> <constructor-arg name="fruitTypeService" ref="specialFruitTypeService" /> </bean>
Наш переписанный класс теперь можно вызывать из клиента Java, как показано ниже.
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); FruitAction action = (FruitAction) context.getBean("specialFruitAction"); test(action); }
Таким образом, мы можем повторно использовать наш существующий класс FruitAction, не изменять какой-либо код Java (кроме создания дополнительных служб) и, таким образом, полностью изменять службы, используемые классом FruitAction. Единственными изменениями, необходимыми для достижения этой цели, было обновление XML, который определяет проводку Spring. Это часто упоминается как одно из больших преимуществ введения проблем. Прежде чем принять это, давайте посмотрим, что требуется для выполнения того же требования, без использования Spring.
Чтобы создать класс FruitAction, который использует наши новые сервисы без использования Injection of Concerns, мы можем просто создать новый класс действий, который использует новые сервисы.
public FruitDvo getFruitInfo(Fruit fruit) { FruitDvo rtn = new FruitDvo(); rtn.setFruit(fruit); rtn.setCost(new SpecialFruitCostService().getCost(fruit)); rtn.setLocation(new SpecialFruitLocationService().getLocation(fruit)); rtn.setPrice(new SpecialFruitPriceService().getPrice(fruit)); rtn.setTypes(new SpecialFruitTypeService().getTypes(fruit)); return rtn; }
Таким образом, мы можем достичь всего лишь нескольких строк кода Java (по одной строке для каждой из служб, которые мы хотим заменить) того, что требовало примерно одинакового количества проводки XML для достижения того же результата с помощью Spring Injection of Concerns.
В заключение, если разработчик не может рационализировать введение проводки Spring в класс, нецелесообразно использовать проводку Spring. Рационализация использования пружинной проводки должна быть конкретной и наглядной. Рационализации, такие как повторное использование или возможность изменения реализаций, недостаточно, если не будут определены более конкретные преимущества.
Этот вывод согласуется, но не идентичен утверждению Абеля Аврама о том, что добавление Spring снижает качество приложений JEE [3] (на основе работы Джея Саппиди [4]).
Саппиди заявляет: «Приложения Java не должны основываться на личных предпочтениях, а должны тщательно оцениваться и рационализироваться с учетом объективных фактов и требований бизнеса». Этот анализ часто выполняется при принятии общих решений: «Нужно ли нам использовать Spring, Struts, Hibernate, Cayenne и т. Д. Для этого проекта?» но этот тип анализа также должен проводиться ежедневно на этапе реализации процесса разработки программного обеспечения: «Нужно ли использовать Spring инъекцию в этой ситуации? Увеличивает ли использование Spring инъекций для достижения этой цели качество кода? Могу ли я конкретно привести конкретный пример того, как код улучшается за счет использования Spring в этомситуация?» Во всех случаях, когда у разработчика нет конкретного и конкретного ответа на этот последний вопрос, Springjection не должен использоваться.
Ссылки:
[1] http://www.springsource.org/spring-framework
[2] Койрала, Shivprasad, Dependency Injection (DI) против инверсии управления (МОК) http://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO
[3] Аврам, Абель, CAST: добавление Spring снижает качество приложений JEE , http://www.infoq.com/news/2013/02/CAST-JEE-Spring-Quality
[4] Sappidi, Jay, Java-приложения и кофе: вариации бесконечны , http://www.castsoftware.com/news-events/event/appmarq-java-applications