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

суббота, 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