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>