В Play есть модуль для интеграции Guice:
http://www.playframework.org/modules/guice-1.2/home
В дополнение к документации модуля, этот пост от @_felipera может помочь вам начать работу.
http://geeks.aretotally.in/dependency-injection-with-play-framework-and-google-guice
Как использовать модуль Guice
Добавить зависимость
1
2
3
|
require: - play - play -> guice 1.2 |
Загрузите зависимости
1
|
play deps |
Создайте новый класс, который будет введен в контроллер
services.MyService
1
2
3
4
|
package services; public interface MyService { public void sayHello(); } |
services.MyServiceImpl
01
02
03
04
05
06
07
08
09
10
11
|
package services; public class MyServiceImpl implements MyService { public MyServiceImpl(){ play.Logger.info( "constructor!" ); } @Override public void sayHello() { play.Logger.info( "hello" ); } } |
Настроить инжектор
01
02
03
04
05
06
07
08
09
10
11
12
|
package config; public class GuiceConfig extends GuiceSupport { @Override protected Injector configure() { return Guice.createInjector( new AbstractModule() { @Override protected void configure() { bind(MyService. class ).to(MyServiceImpl. class ).in(Singleton. class ); } }); } } |
Это установит класс как синглтон. Каждый раз, когда у класса есть зависимость MyService, инжектор будет внедрять один и тот же экземпляр MyServiceImpl.
Вставьте зависимость, используя аннотацию @Inject
01
02
03
04
05
06
07
08
09
10
11
|
package controllers; public class Application extends Controller { @Inject static MyService myService; public static void index() { myService.sayHello(); render(); } } |
тестирование
Следующим моим шагом было создание теста, и тут возникла первая проблема.
1
|
play test |
Http: // Localhost: 9000 / @ тесты
Ошибка компиляции! Проблема в том, что в модуле есть папка «test». Эта папка должна иметь какой-то модульный или функциональный тест, но вместо этого она имеет три примера приложений. Соглашение в игровых модулях заключается в том, что приложения такого типа должны находиться в папке «samples-and-test».
Я сделал форк проекта, чтобы переименовать эту папку:
https://github.com/axelhzf/play-guice-module
Я также сделал pull-запрос, но пока не получил ответа:
https://github.com/pk11/play-guice-module/pull/5
Чтобы запустить этот тест, достаточно было переименовать папку «test»:
01
02
03
04
05
06
07
08
09
10
|
@InjectSupport public class InjectTest extends UnitTest { @Inject static MyService myService; @Test public void injectOk(){ assertNotNull(myService); } } |
Добавление большего количества зависимостей
По умолчанию Play автоматически обнаруживает аннотацию @Inject для классов, которые наследуются от Controller, Job и Mail. Если вы хотите внедрить зависимости в другие классы, вы должны использовать @InjectSupport.
Как правило, наши услуги не так просты, как MyService. Распространены зависимости между сервисами. Guice решает эту проблему, анализируя зависимости и создавая объекты в правильном порядке.
services.MyDependentService
1
2
3
4
5
|
package services; public interface MyDependentService { public void sayHelloWorld(); } |
service.MyDependentServiceImpl
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
package services; @InjectSupport public class MyDependentServiceImpl implements MyDependentService { @Inject static MyService myService; public MyDependentServiceImpl(){ play.Logger.info( "Init MyDependentServiceImpl" ); } public void sayHelloWorld(){ myService.sayHello(); play.Logger.info( "world" ); } } |
InjectTest
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@InjectSupport public class InjectTest extends UnitTest { @Inject static MyDependentService myDependentService; @Test public void injectOk(){ assertNotNull(myDependentService); myDependentService.sayHelloWorld(); } } |
Связывание в GuiceConfig
1
|
bind(MyDependentService. class ).to(MyDependentServiceImpl. class ).in(Singleton. class ); |
И это вывод консоли
1
2
3
4
5
|
20 : 34 : 39 , 090 INFO ~ Init MyServiceImpl 20 : 34 : 39 , 095 INFO ~ Init MyDependentServiceImpl 20 : 34 : 39 , 095 INFO ~ Application 'lazySingleton' is now started ! 20 : 34 : 39 , 136 INFO ~ hello 20 : 34 : 39 , 136 INFO ~ world |
Конструктор инъекций
В модуле мне не нравится то, что поля, которые вам разрешено вводить, должны быть статическими. Я бы предпочел объявить зависимости как параметры конструктора. Таким образом, было бы более очевидно, что для создания экземпляра MyDependentServiceImpl вам необходим экземпляр MyService. Более того, при работе с модульным тестом легче передавать фиктивные объекты в качестве параметра, чем настраивать инжектор.
В документации к модулю я не нашел ссылки на то, как это сделать. Тем не менее, я нашел статью, объясняющую, как это сделать с помощью провайдера:
http://ericlefevre.net/wordpress/2011/05/08/play-framework-and-guice-use-providers-in-guice-modules/
Позже я нашел вопрос о StackOverflow, который дал мне другую подсказку:
http://stackoverflow.com/questions/8435686/does-injector-getinstance-always-call-a-constructor
В Edit он говорит, что забыл поместить аннотацию @Inject в конструктор. Я попытался сделать то же самое, и, наконец, это сработало:
01
02
03
04
05
06
07
08
09
10
11
|
public class MyDependentServiceImpl implements MyDependentService { private final MyService myService; @Inject public MyDependentServiceImpl(MyService myService){ this .myService = myService; play.Logger.info( "Inicializando MyDependentServiceImpl" ); } ... |
Ленивые синглтоны
Это еще одна последняя деталь, чтобы иметь идеальную конфигурацию Google Guice.
Службы инициализируются при запуске приложения.
1
2
3
|
21 : 38 : 11 , 801 INFO ~ Inicializando MyServiceImpl 21 : 38 : 11 , 805 INFO ~ Inicializando MyDependentServiceImpl 21 : 38 : 11 , 805 INFO ~ Application 'lazySingleton' is now started ! |
Когда приложение находится в рабочем режиме, это правильное поведение. Но в режиме разработки я предпочитаю, чтобы синглтоны запускались по требованию. Могут быть службы, которые требуют времени для запуска, и я хочу, чтобы запуск приложения был максимально быстрым.
С Google Guice вы можете достичь этого, используя Scopes:
http://code.google.com/p/google-guice/wiki/Scopes
Все, что вам нужно сделать, это установить параметр Stage:
1
2
|
Stage stage = Play.mode.isDev() ? Stage.DEVELOPMENT : Stage.PRODUCTION; return Guice.createInjector(stage, new AbstractModule() {….. |
Перезапуск теста
1
2
3
4
5
6
|
22 : 00 : 03 , 353 WARN ~ You're running Play! in DEV mode 22 : 00 : 04 , 615 INFO ~ Connected to jdbc:h2:mem:play;MODE=MYSQL;LOCK_MODE= 0 22 : 00 : 04 , 811 INFO ~ Guice injector created: config.GuiceConfig 22 : 00 : 04 , 819 INFO ~ Init MyServiceImpl 22 : 00 : 04 , 824 INFO ~ Init MyDependentServiceImpl 22 : 00 : 04 , 824 INFO ~ Application 'lazySingleton' is now started ! |
По электронной почте Ой! Синглтон инициализируются до запуска приложения. Возможно, это неправильное использование переменной сцены. Давайте попробуем тест:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class StageTest { @Test public void testDevelopment(){ Injector injector = createInjector(Stage.DEVELOPMENT); System.out.println( "development - before getInstance" ); MyService instance = injector.getInstance(MyService. class ); System.out.println( "development - after getInstance" ); } @Test public void testProduction(){ Injector injector = createInjector(Stage.PRODUCTION); System.out.println( "production - before getInstance" ); MyService instance = injector.getInstance(MyService. class ); System.out.println( "production - after getInstance" ); } public Injector createInjector(Stage stage){ Injector injector = Guice.createInjector(stage, new AbstractModule(){ @Override protected void configure() { bind(MyService. class ).to(MyServiceImpl. class ); } }); return injector; } } |
И результат:
1
2
3
4
5
6
7
|
INFO: development - before getInstance INFO: Inicializando MyServiceImpl INFO: development - after getInstance INFO: Inicializando MyServiceImpl INFO: production - before getInstance INFO: production - after getInstance |
Как сказано в документации, в режиме разработки Singletons инициализируются лениво.
Если это работает, когда я пытался использовать модуль воспроизведения, почему это не сработало?
Просмотр кода:
https://github.com/pk11/play-guice-module/blob/master/src/play/modules/guice/GuicePlugin.java
@OnApplicationStart модуль находит все классы, аннотированные @InjectSupport, и внедряет их зависимости. Чтобы внедрить зависимости, модуль вызывает метод getBean (). И здесь мы находим проблему: при вызове getBean () создается экземпляр класса.
Я нашел решение этой проблемы:
https://groups.google.com/d/msg/google-guice/405HVgnCzsQ/fBUuueP6NfsJ
Это код:
Эти классы создают прокси для каждого класса, помеченного как @LazySingleton. Когда объект вводится, инжектор фактически вводит прокси. При первом вызове метода прокси позаботится об инициализации класса.
Используя эти классы, конфигурация инжектора выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class GuiceConfig extends GuiceSupport { @Override protected Injector configure() { Stage stage = Play.mode.isDev() ? Stage.DEVELOPMENT : Stage.PRODUCTION; return Guice.createInjector(stage, new AbstractModule() { @Override protected void configure() { bindScope(LazySingleton. class , MoreScopes.LAZY_SINGLETON); bindLazySingletonOnDev(MyService. class , MyServiceImpl. class ); bindLazySingletonOnDev(MyDependentService. class , MyDependentServiceImpl. class ); } protected <T> void bindLazySingletonOnDev(Class<T> expected, Class<? extends T> implClass){ if (Play.mode.isDev()){ bind(implClass).in(MoreScopes.LAZY_SINGLETON); Provider<T> provider = LazyBinder.newLazyProvider(expected, implClass); bind(expected).toProvider(provider); } else { bind(expected).to(implClass).in(Scopes.SINGLETON); } } }); } } |
Я добавлю эти классы на развилку, чтобы иметь полный модуль, который можно повторно использовать в разных проектах.
Вывод
За последние несколько лет Dependency Injection превратилась из неясного и мало понятного термина в часть инструментария каждого программиста. В этой статье мы увидели, как легко интегрировать Guice, очень удобную библиотеку от Google, в приложение Play Framework. Кроме того, мы также рассмотрели, как настроить его поведение для лучшего опыта разработки.
Оригинал статьи опубликован на http://axelhzf.tumblr.com .
Ссылка: Playframework + Google Guice от нашего партнера JCG блог.