Поиск по блогу

суббота, 23 октября 2010 г.

Попробуй Guice на вкус

В этом посте хотел бы вкратце описать возможности одной полезной Java библиотеки от Google. Естественно, только те из них, которые использовал непосредственно.
Guice - фреймворк, заточенный, по сути, только под одну задачу, которая в русскоязычной Википедии называется несколько неуклюже: "внедрение зависимости" (dependency injection - DI). Аналогичную функциональность реализуют в своих подмодулях и другие фреймворки вроде Spring или Seam. Guice - облегченная версия подобных библиотек и его применение позволяет для необходимой функциональности не "тащить" за собой вышеуказанных монстров. Ниже, собственно, о возможностях Guice.

  1. DI и модульность приложения
  2. Элементы AOP
  3. Unit тесты
  4. Интеграция с Servlet API


DI и модульность приложения

Рассмотрим простой пример зависимости класса Main от некоторого функционала класса HelperImpl, реализующего интерфейс Helper.
UML диаграмма:

Helper.java:
public interface Helper {
void doSomething();
}
HelperImpl.java:
public class HelperImpl extends Helper {
public void doSomething() {
// useful code here
}
}
Тогда зависимость внедряется с помощью аннотации @Inject.
Main.java:
public class Main  {
@Inject
private Helper helper;
public Helper getHelper() {
 return helper;
} 
}
Контекст приложения инкапсулируется в Guice в так называемых модулях. Как правило, это наследники от класса AbstractModule. Связывание интерфейса с классом настраивается в методе configure c помощью вызова методов bind и to.
ExampleModule.java:
public class ExampleModule extends AbstractModule {
protected void configure() {
bind(Helper.class).to(HelperImpl.class);
}
}
В принципе, все готово. Остальное зависит от того, где в приложении поднимается сконфигурированный Guice контекст. Пока пример в случае консольного приложения (далее будут примеры сервлетов веб-приложения и Unit-тестов).
Application.java:
public class Application {

 public static void main(String[] args) {
  Application app = new Application();
  Injector injector = Guice.createInjector(new ExampleModule());
  Main main = injector.getInstance(Main.class);
  main.getHelper();
 }
}
К моменту вызова getter-метода поле helper будет проинициализировано.

Элементы AOP

Элементы аспекто-ориентированного программирования представлены в Guice, как минимум, механизмом "перехватывания" (interception). Рассмотрим, это на примере валидации параметра, передаваемого в метод. Пусть есть функционал по работе с пользователями, для исполнения которого необходимо проверять корректность передаваемых данных.
UserDAO.java:
public class UserDAO {
@ValidateUser
public void saveUser(User user) {
// a code to save user
}
@ValidateUser
public void deleteUser(User user) {
// a code to delete user
}
}
@ValidateUser - аннотация, на которую мы настроим объект-перехватчик.
ValidateUser.java:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateUser {
}
Код перехватчика будет проверять наличие id у пользователя.
ValidateUserInterceptor.java:
public class ValidateUserInterceptor
implements MethodInterceptor {

        @Override
        public Object invoke(MethodInvocation invocation)
        throws Throwable
        {
                Object[] args = invocation.getArguments();
  boolean isValid = false;
                for (Object arg : args) {
                        if (arg instanceof User) {
                                User user = (User) arg;
                                if (user.getId() != null) {
                                    isValid = true;break;
                                }
                        }
                }
  if (isValid) return invocation.proceed();
throw new IllegalArgumentException
("validation of user failed");
        }
}
Осталось настроить перехватчик на аннотацию в модуле приложения:
UserModule.java:
public class UserModule extends AbstractModule {

        @Override
        protected void configure() {
ValidateUserInterceptor validateUserInterceptor = new
 ValidateUserInterceptor();
                bindInterceptor(Matchers.any(),
Matchers.annotatedWith(ValidateUser.class),
validateUserInterceptor);
        }

}

Unit-тесты

Под Guice есть готовый фреймворк для написания Unit-тестов - MycilaGuice. Эта библиотека умеет самое главное - поднимать Guice контекст при запуске JUnit тестов. Делается это тоже с помощью аннотаций.
UserDAOTest.java:
@RunWith(MycilaJunitRunner.class)
@GuiceContext(UserModule.class)
public class UserDAOTest {
@Inject
private UserDAO userDao;
@Test
public void testSave() {
userDao.save(new User("1"));
}
@Test
public void testDelete() {
userDao.delete(new User("2"));
}

}

Интеграция с Servlet API

Наконец, Guice можно интегрировать в веб-приложение. Для этого необходимо создать класс-слушатель, который наследуется от класса GuiceServletContextListener.
WebGuiceContextListener.java:
public class WebGuiceContextListener extends GuiceServletContextListener {

        @Override
        protected Injector getInjector() {
                return Guice.createInjector(new WebModule());
        }

}
WebModule - модуль веб-приложения, где можно настраивать мэппинг сервлетов, не считая рассмотренных выше возможностей Guice модулей.
WebModule.java:
public class WebModule extends ServletModule {

        @Override
        protected void configureServlets() {
                serve("/webapplication/download").with(DownloadServlet.class);
        }
}
Осталось поправить конфигурационный файл веб-приложения web.xml и - бинго! - можно пользоваться всеми преимуществами Guice.
web.xml:
<web-app>
        <filter>
                <filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
        </filter>

        <filter-mapping>
                <filter-name>guiceFilter</filter-name>
                <url-pattern>/*</url-pattern>
        </filter-mapping>
        <listener>
                <listener-class>somepackage.WebGuiceContextListener</listener-class>
        </listener>

</web-app>
Metal War Online

5 комментариев:

  1. Да, framework отличный. Сам на этой неделе перевел свой текущий проектик на Guice для пробы.

    Правда сам по себе он немного скуден, но гибок. Это в духе гугла - дать ядро, а все остальное пусть додумывает сообщество =) А spring на данный момент - это все-таки экосистема.

    Из неудобств заметил пока лишь, что provider'ы не параметризуешь. То есть можно, конечно, разные экземпляра provider'а в методе configure биндить, но
    1) провайдер может быть "тяжелым"
    2) провайдер сам может требовать инъекций
    Поэтому приходится два разных класса провайдера создавать, отличающихся лишь значением параметра.
    Но это скорее всего можно более элегантно решить, я пока еще только начал копать...

    ОтветитьУдалить
  2. спасибо!
    на мой взгляд не хватает мотивации, зачем все это нужно. Какие плюсы перед другими подходами и т.д.

    ОтветитьУдалить
  3. Насчет параметризации ничего не могу сказать. Я сам еще не до конца разобрался с возможностями Guice. Но пока такой необходимости не возникало.

    По поводу мотивации: ссылка на страницу в Википедии отчасти мотивацию описывает. Если вкратце, то это дает:
    1. Слабосвязанный гибкий дизайн приложения:
    - замена избыточного наследования ассоциацией;
    - уход от инициализации зависимостей в конструкторах или с помощью фабрик;
    2. Комфортное тестирование функциональности.

    ОтветитьУдалить
  4. Судя по тому что вы рассказываете о Guice, в пределе всё равно придётся переводить на Spring.

    И вообще, что значит spring = монстр?
    Spring можно подключать не весь, а только нужную часть, например есть spring-beans, spring-context для DI и spring-aop для АОП. Но никто не заставляет подключать остальные модули для DI.

    ОтветитьУдалить
  5. Согласен. Впрочем, Guice более легкий: ни XML конфигураций, ни auto-wiring. В последнем случае еще у Guice преимущество в плане проверки типов (type safety) на стадии компиляции. А так, конечно, у Spring есть свои преимущества - хотя бы в виде репутации и надежности, но основные преимущества где-то заложены в остальных модулях. Вообщем, это Guice - это альтернатива, а не silver bullet for DI.

    ОтветитьУдалить