Spring Framework – многосторонний фреймворк, обладающий рядом возможностей, которые можно использовать как вместе, так и по отдельности:
- Inversion of Control
- упрощение доступа к базам данных
- управление транзакциями
- поддержка ORM – Object Relational Mapping для работы с базой данных на уровне объектов
- поддержка аспектно-ориентированного программирования (Spring AOP)
- … и много чего еще интересного!
Spring можно использовать не только в больших J2EE приложениях. Этот фреймворк подойдёт и для небольших проектов: как отдельно стоящих Java-приложений, так и веб-приложений, работающих в Servlet-контейнере, и даже апплетов. Я решил поделиться простым примером использования Spring в standalone приложении.
Первое, что я счёл необходимым использовать – Inversion of Control. IoC – это способ уменьшить зависимость отдельных компонентов системы друг от друга, при котором компонент “заявляет” зависимость от неких интерфейсов и сервисов, которые необходимы ему для работы, но понятия не имеет о том, каким образом эти интерфейсы и сервисы реализованы и откуда взять их реализацию. Простой пример “независимого” компонента, выдающего ленту новостей, можно записать так (с использованием JdbcTemplate – классического спобоба доступа к БД в Spring):
// NewsService.java package springtest; import java.util.List; public interface NewsService { /** Возвращает список заголовков новостей */ List<String> getHeaders(); }
// NewsServiceImpl.java package springtest; import java.util.List; import javax.annotation.Resource; import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; /** Реализация NewsService - загружает новости из базы данных */ public class NewsServiceImpl implements NewsService { private JdbcTemplate jt; /** @Resource - показывает, что этому объекту нужен DataSource для работы */ @Resource public void setDataSource(DataSource ds) { jt = new JdbcTemplate(ds); } public List<String> getHeaders() { List<String> headers = jt.queryForList("SELECT header FROM news", String.class); return headers; } }
Такой NewsServiceImpl уже может выполнять свою функцию при условии что некий “контейнер” передаст ему DataSource для доступа к базе. Таким контейнером может быть любой J2EE сервер приложений, но в случае простого приложения нет смысла использовать сервер. Все можно сделать гораздо проще: можно использовать Spring IoC.
Итак, наш NewsServiceImpl является полностью независимым и реюзабельным. Можно создать экземпляр NewsServiceImpl и работать с ним, как с обычным объектом (он и есть обычный объект), а можно поручить Spring обработать все зависимости, помеченные аннотацией @Resource. Тогда не придется вручную вызывать метод setDataSource. Сейчас покажем, как это сделать
.
Во-первых, надо проделать инициализацию BeanFactory, через который будут создаваться объекты. BeanFactory можно сконфигурировать так, что он будет обрабатывать аннотации вроде @Resource и предпринимать некоторые действия – для @Resource это будет поиск подходящего объекта и назначение его туда, где стоит @Resource.
Создать и инициализировать BeanFactory можно разными способами, но проще всего, наверное, создать XML файл конфигурации (назовём его beans.xml):
<?xml version="1.0" encoding="UTF-8"?> <!-- beans.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- Даём установку: интерпретировать аннотации --> <context:annotation-config /> <!-- Конфигурируем подключение к базе данных --> <bean id="mainDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&dumpQueriesOnException=true" /> <property name="username" value="root" /> <property name="password" value="1" /> </bean> <!-- Транзакции тоже будем конфигурировать аннотациями, очень удобно :) --> <tx:annotation-driven transaction-manager="txManager" mode="proxy" /> <!-- Менеджер транзакций: привязываем его к нашему DataSource --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="mainDataSource" /> </bean> </beans>
и, собственно, инициализация и пример вызова NewsService:
// Main.java package springtest; import java.util.List; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.*; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { /** Точка входа в программу */ public static void main(String[] args) { Main me = new Main(); me.init(); me.run(); } private ConfigurableApplicationContext cx; private void init(){ cx = new ClassPathXmlApplicationContext("beans.xml", Main.class); DefaultListableBeanFactory bf = (DefaultListableBeanFactory) cx.getBeanFactory(); // Добавляем в BeanFactory объявление сервиса, выдающего новости: bf.registerBeanDefinition("newsService", new AnnotatedGenericBeanDefinition(NewsServiceImpl.class)); } private void run() { // Получаем сервис новостей (он тут же будет инициализирован) NewsService news = cx.getBean("newsService", NewsService.class); // Пользуемся :) List<String> headers = news.getHeaders(); System.out.println("Headers: " + headers); } }
Что же получилось? Spring сам создаёт экземпляр NewsService, производя его инициализацию, в соответствии с аннотациями в самом NewsServiceImpl. Причём “клиент” этого сервиса (в данном случае, это метод run()) не имеет ни малейшего представления, какие компоненты нужны NewsService для работы. Вся забота о конфигурировании и связи NewsService с необходимыми ему сервисами/компонентами ложится на Spring Framework. Удобно, не так ли?
Если однажды в NewsService добавтся ещё одна или несколько зависимостей, например от какого-нибудь RssService, то это повлияет только на NewsServiceImpl: в нём появится лишь что-то вроде
@Resource private RssService rss;
или
@Resource public void setRssService(RssService rss) { ... }
а клиентский код, который пользуется NewsService может оставаться неизменным и даже не подозревать о том, что NewsService теперь зависит от ещё одного компонента.
По мере “роста” проекта наверняка кроме NewsService появятся ещё некие “сервисы” и компоненты, которые точно так же можно зарегистрировать в BeanFactory. Тут стоит отметить, что способ регистрации объявлений в BeanFactory можно выбрать любой: можно разместить объявление в XML-конфигурации (DataSource в предыдущем примере как раз так и объявлен), а можно поступить, как с NewsService – добавить его объявление “руками” в java-коде. Насколько одно или другое удобнее – решается в каждом отдельном случае, но на мой взгляд, для небольшого проекта справедливо следующее:
- “руками” зарегистрировать в BeanFactory можно те компоненты, которые не надо конфигурировать и которые обязаны всегда присутствовать в приложении. При этом объявления в java-коде позволяют отсеить часть возможных ошибок рано, еще на этапе компиляции. Кроме того, в этом случае отпадает необходимость правки внушительных XML, состоящих из одних статических объявлений, которые никогда не меняются.
- в XML удобно вынести объявления bean-ов, у которых могут поменяться настройки; в нашем примере это подключение к базе данных. Неразумно конфигурацию подключения к БД забивать “хардкодом”
. Ещё объявления бинов удобно хранить в XML, если используется test-driven разработка, тогда можно “на лету” заменить рабочий компонент заглушкой и тестировать проект.
А что делать, если требуется создать объект, который не описан в BeanFactory? Для этого в BeanFactory есть методы, позволяющие создать экземпляр указанного класса, после чего этот экземпляр будет “обработан” для разрешения его зависимостей, точно так же, как это делается с объявленными bean-ами. К примеру, создадим такой объект:
class TestClass { @Resource private NewsService news; public String getNews() { return news.getHeaders().toString(); } }
для этого достаточно написать:
TestClass test = (TestClass) cx.getBeanFactory().createBean( TestClass.class, AutowireCapableBeanFactory.AUTOWIRE_NO, false); System.out.println("Test class: " + test.getNews());
где cx – это созданный ранее ApplicationContext из примера выше. Метод createBean() создает нам экземпляр TestClass, уже настроенный в соответствии с указанными в нём аннотациями. В документации по Spring Framework можно найти описание методов, которые “связывают” уже созданные объекты.
Код создания экземпляра можно обернуть в более удобный для использования класс:
public interface ObjectFactory { /** * Создаёт экземпляр implClass, разрешает зависимости и возвращает экземпляр * интерфейса interfaceClass, который реализован в implClass */ <T> T createInstance(Class<? extends T> implClass, Class<T> interfaceClass); } public class SpringObjectFactory implements ObjectFactory { private final AutowireCapableBeanFactory factory; public SpringObjectFactory(AutowireCapableBeanFactory factory) { this.factory = factory; } @Override public <T> T createInstance(Class<? extends T> implClass, Class<T> interfaceClass) { Object bean = factory.createBean(implClass, AutowireCapableBeanFactory.AUTOWIRE_NO, false); // Возвращённый bean может быть на самом деле Proxy, если Spring // решит перехватить вызовы методов implClass // (это будет, если implClass использует декларативные транзакции, к примеру). // ПОЭТОМУ приведение типа к implClass может быть невозможно, но к // interfaceClass - запросто, если implClass реализует этот интерфейс. return interfaceClass.cast(bean); } }
После чего можно добавить ObjectFactory в BeanFactory:
bf.registerSingleton("factory", new SpringObjectFactory(bf));
Далее, можно пользоваться удобным способом создания полностью сконфигурированных JavaBean-ов:
@Resource public void setFactory(ObjectFactory factory) { NewsService news = factory.createInstance(NewsServiceImpl.class, NewsService.class); //.... пользуемся сервисом NewsService }
Приведённый пример иллюстрирует одну из возможностей использования Spring IoC в проекте, благодаря которой облегчается проектирование приложения и устраняются зависимости между компонентами (но остаются зависимости от интерфейсов!). Для построения “красивой” системы достаточно каждый сервис/компонент представить в виде интерфейса, а потребителям этих сервисов добавить необходимые аннотации @Resource; зарегистрировать реализации этих сервисов в BeanFactory, после чего наслаждаться автоматическим связыванием компонентов!
Буду рад, если эта статья поможет кому-то. Спасибо за внимание.













Comments
Leave a comment Trackback