Статьи

Изучение Netflix регулятора — часть 1

Я работал с Netflix Governator в течение последних нескольких дней, и мне пришлось опробовать небольшой пример, используя Governator, чтобы сравнить его с набором функций внедрения зависимостей в Spring Framework. Нижеследующее ни в коем случае не является исчерпывающим, я остановлюсь на этом в следующей серии постов.

Таким образом, губернатор для непосвященных — это расширение для Google Guice, которое дополняет его некоторыми функциями, подобными Spring, цитируя сайт губернатора:

сканирование пути к классам и автоматическое связывание, управление жизненным циклом, конфигурация для сопоставления полей, проверка полей и параллельный прогрев объектов

Здесь я продемонстрирую две функции: сканирование пути к классам и автоматическое связывание.

Внедрение базовой зависимости

Рассмотрим BlogService, в зависимости от BlogDao:

01
02
03
04
05
06
07
08
09
10
11
12
public class DefaultBlogService implements BlogService {
    private final BlogDao blogDao;
 
    public DefaultBlogService(BlogDao blogDao) {
        this.blogDao = blogDao;
    }
 
    @Override
    public BlogEntry get(long id) {
        return this.blogDao.findById(id);
    }
}

Если бы я использовал Spring для определения зависимости между этими двумя компонентами, конфигурация была бы следующей:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package sample.spring;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sample.dao.BlogDao;
import sample.service.BlogService;
 
@Configuration
public class SampleConfig {
 
    @Bean
    public BlogDao blogDao() {
        return new DefaultBlogDao();
    }
 
    @Bean
    public BlogService blogService() {
        return new DefaultBlogService(blogDao());
    }
}

В Spring конфигурация зависимостей указывается в классе с аннотацией @Configuration. Методы, аннотированные @Bean, возвращают компоненты, обратите внимание на то, как blogDao внедряется посредством инжектора конструктора в методе blogService.

Модульный тест для этой конфигурации заключается в следующем:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package sample.spring;
 
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sample.service.BlogService;
 
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
 
public class SampleSpringExplicitTest {
 
    @Test
    public void testSpringInjection() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(SampleConfig.class);
        context.refresh();
 
        BlogService blogService = context.getBean(BlogService.class);
        assertThat(blogService.get(1l), is(notNullValue()));
        context.close();
    }
 
}

Обратите внимание, что Spring предоставляет хорошую поддержку для модульного тестирования, лучшим тестом будет следующее:

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
29
30
31
32
33
34
35
36
package sample.spring;
 
package sample.spring;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import sample.service.BlogService;
 
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
 
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SampleSpringAutowiredTest {
 
    @Autowired
    private BlogService blogService;
 
    @Test
    public void testSpringInjection() {
        assertThat(blogService.get(1l), is(notNullValue()));
    }
 
    @Configuration
    @ComponentScan("sample.spring")
    public static class SpringConig {
 
    }
 
}

Это базовое внедрение зависимостей, поэтому указывать такой регулятор зависимостей само по себе не требуется, достаточно Guice, вот как будет выглядеть конфигурация с использованием модулей Guice:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package sample.guice;
 
import com.google.inject.AbstractModule;
import sample.dao.BlogDao;
import sample.service.BlogService;
 
public class SampleModule extends AbstractModule{
 
    @Override
    protected void configure() {
        bind(BlogDao.class).to(DefaultBlogDao.class);
        bind(BlogService.class).to(DefaultBlogService.class);
    }
}

и модульный тест для этой конфигурации следующий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package sample.guice;
 
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.junit.Test;
import sample.service.BlogService;
 
import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;
 
public class SampleModuleTest {
 
    @Test
    public void testExampleBeanInjection() {
        Injector injector = Guice.createInjector(new SampleModule());
        BlogService blogService = injector.getInstance(BlogService.class);
        assertThat(blogService.get(1l), is(notNullValue()));
    }
 
}

Classpath Scanning и Autobinding

Сканирование пути к классам — это способ обнаружения компонентов путем поиска маркеров в пути к классам. Образец с Spring должен уточнить это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Repository
public class DefaultBlogDao implements BlogDao {
    ....
}
 
@Service
public class DefaultBlogService implements BlogService {
 
    private final BlogDao blogDao;
 
    @Autowired
    public DefaultBlogService(BlogDao blogDao) {
        this.blogDao = blogDao;
    }
    ...
}

Здесь аннотации @Service, @Repository используются в качестве маркеров для указания того, что это компоненты, а зависимости определяются аннотацией @Autowired в конструкторе DefaultBlogService.

Учитывая это, конфигурация теперь упрощена, нам просто нужно предоставить имя пакета, который должен быть проверен на наличие таких аннотированных компонентов, и вот как будет выглядеть полный тест:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package sample.spring;
...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SampleSpringAutowiredTest {
 
    @Autowired
    private BlogService blogService;
 
    @Test
    public void testSpringInjection() {
        assertThat(blogService.get(1l), is(notNullValue()));
    }
 
    @Configuration
    @ComponentScan("sample.spring")
    public static class SpringConig {}
}

Губернатор оказывает подобную поддержку:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@AutoBindSingleton(baseClass = BlogDao.class)
public class DefaultBlogDao implements BlogDao {
    ....
}
 
@AutoBindSingleton(baseClass = BlogService.class)
public class DefaultBlogService implements BlogService {
    private final BlogDao blogDao;
 
    @Inject
    public DefaultBlogService(BlogDao blogDao) {
        this.blogDao = blogDao;
    }
    ....
}

Здесь аннотация @AutoBindSingleton используется в качестве аннотации маркера для определения привязки guice, учитывая, что тест со сканированием пути к классам следующий:

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
29
30
31
32
package sample.gov;
 
import com.google.inject.Injector;
import com.netflix.governator.guice.LifecycleInjector;
import com.netflix.governator.lifecycle.LifecycleManager;
import org.junit.Test;
import sample.service.BlogService;
 
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
 
public class SampleWithGovernatorTest {
 
    @Test
    public void testExampleBeanInjection() throws Exception {
        Injector injector  = LifecycleInjector
                .builder()
                .withModuleClass(SampleModule.class)
                .usingBasePackages("sample.gov")
                .build()
                .createInjector();
 
        LifecycleManager manager = injector.getInstance(LifecycleManager.class);
 
        manager.start();
 
        BlogService blogService = injector.getInstance(BlogService.class);
        assertThat(blogService.get(1l), is(notNullValue()));
    }
 
}

Посмотрите, как пакет для сканирования указан с помощью компонента LifecycleInjector регулятора, это автоматически обнаруживает компоненты и связывает их вместе.

Просто чтобы обернуть функции сканирования пути к классам и автосвязки, регулятор, как Spring, предоставляет поддержку для тестирования junit, и лучшим тестом будет следующее:

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
29
30
package sample.gov;
 
import com.google.inject.Injector;
import com.netflix.governator.guice.LifecycleTester;
import org.junit.Rule;
import org.junit.Test;
import sample.service.BlogService;
 
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
 
public class SampleWithGovernatorJunitSupportTest {
 
    @Rule
    public LifecycleTester tester = new LifecycleTester();
 
    @Test
    public void testExampleBeanInjection() throws Exception {
        tester.start();
        Injector injector = tester
                .builder()
                .usingBasePackages("sample.gov")
                .build()
                .createInjector();
 
        BlogService blogService = injector.getInstance(BlogService.class);
        assertThat(blogService.get(1l), is(notNullValue()));
    }
 
}

Вывод

Если вы заинтересованы в дальнейшем изучении этого вопроса, у меня есть пример в этом проекте github , я буду расширять этот проект по мере того, как узнаю больше о регуляторе.

Ссылка: Изучение регулятора Netflix — часть 1 от нашего партнера по JCG Биджу Кунджуммена в блоге all and sundry.