Guice - фреймворк, заточенный, по сути, только под одну задачу, которая в русскоязычной Википедии называется несколько неуклюже: "внедрение зависимости" (dependency injection - DI). Аналогичную функциональность реализуют в своих подмодулях и другие фреймворки вроде Spring или Seam. Guice - облегченная версия подобных библиотек и его применение позволяет для необходимой функциональности не "тащить" за собой вышеуказанных монстров. Ниже, собственно, о возможностях Guice.
DI и модульность приложения
Рассмотрим простой пример зависимости класса Main от некоторого функционала класса HelperImpl, реализующего интерфейс Helper.
UML диаграмма:
Helper.java:
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:
Application.java:
Элементы AOP
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>
