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>
Да, framework отличный. Сам на этой неделе перевел свой текущий проектик на Guice для пробы.
ОтветитьУдалитьПравда сам по себе он немного скуден, но гибок. Это в духе гугла - дать ядро, а все остальное пусть додумывает сообщество =) А spring на данный момент - это все-таки экосистема.
Из неудобств заметил пока лишь, что provider'ы не параметризуешь. То есть можно, конечно, разные экземпляра provider'а в методе configure биндить, но
1) провайдер может быть "тяжелым"
2) провайдер сам может требовать инъекций
Поэтому приходится два разных класса провайдера создавать, отличающихся лишь значением параметра.
Но это скорее всего можно более элегантно решить, я пока еще только начал копать...
спасибо!
ОтветитьУдалитьна мой взгляд не хватает мотивации, зачем все это нужно. Какие плюсы перед другими подходами и т.д.
Насчет параметризации ничего не могу сказать. Я сам еще не до конца разобрался с возможностями Guice. Но пока такой необходимости не возникало.
ОтветитьУдалитьПо поводу мотивации: ссылка на страницу в Википедии отчасти мотивацию описывает. Если вкратце, то это дает:
1. Слабосвязанный гибкий дизайн приложения:
- замена избыточного наследования ассоциацией;
- уход от инициализации зависимостей в конструкторах или с помощью фабрик;
2. Комфортное тестирование функциональности.
Судя по тому что вы рассказываете о Guice, в пределе всё равно придётся переводить на Spring.
ОтветитьУдалитьИ вообще, что значит spring = монстр?
Spring можно подключать не весь, а только нужную часть, например есть spring-beans, spring-context для DI и spring-aop для АОП. Но никто не заставляет подключать остальные модули для DI.
Согласен. Впрочем, Guice более легкий: ни XML конфигураций, ни auto-wiring. В последнем случае еще у Guice преимущество в плане проверки типов (type safety) на стадии компиляции. А так, конечно, у Spring есть свои преимущества - хотя бы в виде репутации и надежности, но основные преимущества где-то заложены в остальных модулях. Вообщем, это Guice - это альтернатива, а не silver bullet for DI.
ОтветитьУдалить