
ãŸããã
HiïŒ
usernameïŒ
ïŒ ä»å¹Žã¯å€ãã®èå³æ·±ãæ°è£œåãšè¯ããã¥ãŒã¹ããããããŸããã ãªã¢ã¯ãã£ãã«ãŒãã«ãšçµã¿èŸŒã¿ã®KotlinãµããŒããåããåŸ
æã®Spring 5ãªãªãŒã¹ãçºè¡šãããŸããããããã«ã¯ãŸã å€ãã®èå³æ·±ãããšããããŸãã ã»ãã¹ãã£ã³ã¯ã Kotlinã§æ°ããæ©èœçãªSpringæ§æã¢ãããŒããå°å
¥ããŸãã ã JUnit 5ãèµ·åããŸããã Kotlin 1.2ã¯ããã«ããã©ãããã©ãŒã ã¢ããªã±ãŒã·ã§ã³ã®ãµããŒããæ¹åããããªãªãŒã¹ã«è¿ã¥ããŠããŸãã ãããŠä»å¹Žã¯éèŠãªã€ãã³ããéå¬ãããŸãã ïŒ Kotlinã¯ãGradleã®Groovy Dslã§ã®ãã«ããããKotlin Dslã䜿çšãããã«ãã«ç§»è¡ããŸããã
éåžžãæ°ããã¹ã¿ãã¯ããããã«éå§ããæ¹ãç°¡åã§ãããå€ãã¢ãããŒãã®å®è£
æ¹æ³ã«ã€ããŠã¯åžžã«çåãçããŸãã ãããã£ãŠãJavaã§èšè¿°ãããã¢ããªã±ãŒã·ã§ã³ã®äŸãGradleã§LombokãšGroovy Dslã䜿çšããSpring Boot 1.5ïŒSpring 4 +ïŒãSpring boot 2ïŒSpring 5ïŒãJUnit 5ãKotlinãžã®ã¹ããããã€ã¹ãããã®åãæ¿ããèŠãŠãæ©èœçãªã¹ã¿ã€ã«ã§ãããžã§ã¯ããå®è£
ããŠã¿ãŸãããspring-boot
ãªãspring-webflux
ã Groovy DslããKotlin Dslã«ã¢ããã°ã¬ãŒãããæ¹æ³ãšåæ§ã«ã æçš¿ã§ã¯ãäž»ãªçŠç¹ã¯ç§»è¡ã«ããã®ã§ãæ¢ã«SpringãSpring BootãGradleã«ç²ŸéããŠãããšäŸ¿å©ã§ãã
èªãã®ãé¢åãªäººã®ããã«ã githubã§ä»ã®çã®ããã®ãµã³ãã«ã³ãŒããèŠãããšãã§ããŸã-ç§ã¯ç«ã®äžã§å°ããŸãïŒ
1.åºæ¬ã¢ããªã±ãŒã·ã§ã³ããå§ããŸããã
äŸãšããŠãSpring Boot 1.5.8ããã³Spring 4.3.12ã«åºã¥ããã·ã³ãã«ãªãŠãŒã¶ãŒç®¡çã¢ããªã±ãŒã·ã§ã³ãåãäžããŸãããã
æ§æãèªã¿åãäŸã«ã€ããŠã¯ã src/main/resources/application.ym
ã«ãã¡ã€ã«ãäœæããŸãããã®ãã¡ã€ã«ã«ã¯ãã¢ããªã±ãŒã·ã§ã³ã®èµ·åããŒããšãã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšããdb
ã»ã¯ã·ã§ã³ãæå®ããŸãã
server: port: 8080 db: url: localhost:8080 user: vasia password: vasiaPasswordSecret
ãã³ãã¯ã®æ¥ç¶ïŒ
compileOnly("org.projectlombok:lombok:1.16.18")
ã¢ãããŒã·ã§ã³@ConfigurationProperties
ããã³@Configuration
ãšãšãã«DBConfigurationã¯ã©ã¹ã䜿çšããŠãæ§æãã¡ã€ã«ããã»ã¯ã·ã§ã³ãèªã¿åããŸãã åãæ§æãã¡ã€ã«ã§ãããŒã¿ããŒã¹æ¥ç¶èšå®ã䜿çšããŠDbConfig
BeanãäœæããŸãã
@Configuration @ConfigurationProperties @Getter @Setter public class DBConfiguration { private DbConfig db; @Bean public DbConfig configureDb() { return new DbConfig(db.getUrl(), db.getUser(), unSecure(db.getPassword())); } private String unSecure(String password) { int secretIndex = password.indexOf("Secret"); return password.substring(0, secretIndex); } @Data @AllArgsConstructor @NoArgsConstructor public static class DbConfig { private String url; private String user; private String password; } }
è€éã«ãªããªãããã«ããŠãŒã¶ãŒãã¡ã¢ãªã«ä¿åããŸãã ãããè¡ãã«ã¯ã UserRepository
ãªããžããªãè¿œå ããŸãã æ¥ç¶èšå®ã䜿çšããŠéåžžBean
ãäœæããããšã確èªããããã«ã DbConfig
ãã³ã³ãœãŒã«ã«DbConfig
ãŸãã
UserRepository @Repository public class UserRepository { private DBConfiguration.DbConfig dbConfig; public UserRepository(DBConfiguration.DbConfig dbConfig) { this.dbConfig = dbConfig; System.out.println(dbConfig); } private Long index = 3L; private List<User> users = Arrays.asList( new User(1L, "Oleg", "BigMan", 21), new User(2L, "Lesia", "Listova", 25), new User(3L, "Bin", "Bigbanovich", 30) ); public List<User> findAllUsers() { return new ArrayList<>(users); } public synchronized Optional<Long> addUser(User newUser) { Long newIndex = nextIndex(); boolean addStatus = users.add(newUser.copy(newIndex)); if (addStatus) { return Optional.of(newIndex); } else { return Optional.empty(); } } public Optional<User> findUser(Long id) { return users.stream() .filter(user -> user.getId().equals(id)) .findFirst(); } public synchronized boolean deleteUser(Long id) { Optional<User> findUser = users.stream() .filter(user -> user.getId().equals(id)) .findFirst(); Boolean status = false; if (findUser.isPresent()) { users.remove(findUser.get()); status = true; } return status; } private Long nextIndex() { return index++; } }
ããã€ãã®ã³ã³ãããŒã©ãŒãè¿œå ããŸãã
- StatsControllerããµãŒãã¹ããåä¿¡ãããŠãŒã¶ãŒã«é¢ããçµ±èšãçæãã1ã€ã®ã¡ãœãããåããŠããŸãã
çµ±èšã³ã³ãããŒã© RestController("stats") public class StatsController { private StatsService statsService; public StatsController(StatsService statsService) { this.statsService = statsService; } @GetMapping public StatsResponse stats() { Stats stats = statsService.getStats(); return new StatsResponse(true, "user stats", stats); } }
- UserControllerããŠãŒã¶ãŒã®ãªã¹ãã管çããããã®ç°¡åãªã¡ãœãããåããŠããŸãã
ãŠãŒã¶ãŒã³ã³ãããŒã©ãŒ @RestController public class UserController { private UserRepository userRepository; public UserController(UserRepository userRepository) { this.userRepository = userRepository; } @GetMapping("users") public UserResponse users() { List<User> users = userRepository.findAllUsers(); return new UserResponse(true, "return users", users); } @GetMapping("user/{id}") public UserResponse users(@PathVariable("id") Long userId) { Optional<User> user = userRepository.findUser(userId); return user .map(findUser -> new UserResponse(true, "find user with requested id", Collections.singletonList(findUser))) .orElseGet(() -> new UserResponse(false, "user not found", Collections.emptyList())); } @PutMapping(value = "user") public Response addUser(@RequestBody User user) { Optional<Long> addIndex = userRepository.addUser(user); return addIndex .map(index -> new UserAddResponse(true, "user add successfully", index)) .orElseGet(() -> new UserAddResponse(false, "user not added", -1L)); } @DeleteMapping("user/{id}") public Response deleteUser(@PathVariable("id") Long id) { boolean status = userRepository.deleteUser(id); if (status) { return new Response(true, "user has been deleted"); } else { return new Response(false, "user not been deleted"); } } }
ãããŠãçµ±èšçšã®ããŒã¿ãæºåãããããžãã¹ããžãã¯ããåããçµ±èšã³ã³ãããŒã©ãŒçšã®å°ããªãµãŒãã¹ãè¿œå ããŸãã
ã¹ã¿ãããµãŒãã¹ @Service public class StatsService { private UserRepository userRepository; public StatsService(UserRepository userRepository) { this.userRepository = userRepository; } public Stats getStats() { List<User> allUsers = userRepository.findAllUsers(); User oldestUser = allUsers.stream() .max(Comparator.comparingInt(User::getAge)) .get(); User youngestUser = allUsers.stream() .min(Comparator.comparingInt(User::getAge)) .get(); return new Stats( allUsers.size(), oldestUser, youngestUser ); } }
ã¢ããªã±ãŒã·ã§ã³ã®åäœããã¹ãããã«ã¯ãã³ã³ãããŒã©ãŒããšã«@SpringRunner
ã䜿çšããŠèµ·åãããã¹ããè¿œå ããŸãã ã©ã³ãã ãªããŒãã§ã¢ããªã±ãŒã·ã§ã³ãèµ·åãããšãSpringã³ã³ããã¹ãå
šäœããã®äžã§çºçããŸãã 以äžã¯ã StatsControllerTest
ã³ã³ãããŒã©ãŒã®ãã¹ãã³ãŒãã§ãã ãã®äžã§ããµãŒãã¹ã®ã€ã³ã¹ã¿ã³ã¹ãšããŠã@ MockBeanã䜿çšããŠmock
ãäœæããŸãã Spring spring-boot-starter-test
ãšäžç·ã«ããã«äœ¿çšã§ããTestRestTemplateã䜿çšããŠãã³ã³ãããŒã©ãŒã«ãªã¯ãšã¹ããéä¿¡ããŸãã
StatsControllerTest @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class StatsControllerTest { @Autowired private TestRestTemplate restTemplate; @MockBean private StatsService statsServiceMock; @Test public void statsControllerShouldReturnValidResult() { Stats expectedStats = new Stats( 2, new User(1L, "name1", "surname1", 25), new User(2L, "name2", "surname2", 30) ); when(this.statsServiceMock.getStats()).thenReturn(expectedStats); StatsResponse expectedResponse = new StatsResponse(true, "user stats", expectedStats); StatsResponse actualResponse = restTemplate.getForObject("/stats", StatsResponse.class); assertEquals("invalid stats response", expectedResponse, actualResponse); } }
ãŸããStatsServiceãµãŒãã¹ã®åçŽãªmockitoããŒã¹ã®ãã¹ããè¿œå ããŸãã
StatsServiceTest public class StatsServiceTest { @Test public void statsServiceShouldReturnRightData() { UserRepository userRepositoryMock = mock(UserRepository.class); User youngestUser = new User(1L, "UserName1", "Sr1", 21); User someOtherUser = new User(2L, "UserName2", "Sr2", 25); User oldestUser = new User(3L, "UserName3", "Sr3", 30); when(userRepositoryMock.findAllUsers()).thenReturn(Arrays.asList( youngestUser, someOtherUser, oldestUser )); StatsService statsService = new StatsService(userRepositoryMock); Stats actualStats = statsService.getStats(); Stats expectedStats = new Stats( 3, oldestUser, youngestUser ); Assert.assertEquals("invalid stats", expectedStats, actualStats); } }
æçµçãªãã«ãã¹ã¯ãªããã¯æ¬¡ã®ããã«ãªããŸãã
group 'evgzakharov' version '1.0-SNAPSHOT' buildscript { ext { springBootVersion = '1.5.8.RELEASE' } repositories { jcenter() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: "java" apply plugin: "org.springframework.boot" sourceCompatibility = 1.8 dependencies { compile("org.springframework.boot:spring-boot-starter-web") compileOnly("org.projectlombok:lombok:1.16.18") testCompile("org.springframework.boot:spring-boot-starter-test") }
ã©ããããããã§ã¯ãšããŸããã¯ãªãã®ã¯äœ¿çšããŠããŸããã ãã¹ãŠã¯èª°ããæ®éã«è¡ãããšã§ãã
ä»ã®ã¯ã©ã¹ãšå®å
šãªã³ãŒãäŸã¯ããã«ãããŸã ã
å®è¡ããŠããã¹ãŠãæ©èœããããšã確èªããŸã
cd step1_start_project gradle build && gradle build && java -jar build/libs/step1_start_project-1.0-SNAPSHOT.jar
ãã®åŸãã¢ããªã±ãŒã·ã§ã³ã¯ããŒã8080ã§èµ·åããã¯ãã§ãããšã³ããã€ã³ãã/ statsãã«ãã£ãŠæ£ããçããè¿ãããããšã確èªããŸãã ãããè¡ãã«ã¯ãã¿ãŒããã«ã§ã³ãã³ããå®è¡ããŸãã
curl -XGET "http://localhost:8080/stats"
çãã¯æ¬¡ã®ãšããã§ãã
{ "success": true, "description": "user stats", "stats": { "userCount": 3, "oldestUser": { "id": 3, "name": "Bin", "surname": "Bigbanovich", "age": 30 }, "youngestUser": { "id": 1, "name": "Oleg", "surname": "BigMan", "age": 21 } } }
åäœäžã®ã¢ããªã±ãŒã·ã§ã³ã®æºåãã§ããŸããã ããã§ã¯ãã³ãŒãã®æŽæ°ãšæžãæããå§ããŸãããã
2. Spring Boot 2ïŒSpring 5ïŒããã³JUnit 5ã«æž¡ããŸã
æãç°¡åãªç§»è¡ããå§ããŸãããã ãŸããSpring BootããŒãžã§ã³ã2.0.0.M5ã«ã¢ããã°ã¬ãŒãããŸãã æ®å¿µãªãããå·çæç¹ã§ã¯ããªãªãŒã¹ããŒãžã§ã³ã¯ãŸã ãªãªãŒã¹ãããŠããªãããã次ã®ãªããžããªããã«ãã¹ã¯ãªããã«è¿œå ããŸãã
maven { url = "http://repo.spring.io/milestone" }
ã¹ã¿ãžãªã§ãããžã§ã¯ããæŽæ°ãã spring-starter-*
äŸåé¢ä¿ããªããªã£ããšã©ãŒããã£ããããããšããŠããŸãã ããã¯ãäŸåé¢ä¿ããŒãžã§ã³ã®èªåæ§æãå¥ã®ãã©ã°ã€ã³ã«ç§»åãããšããäºå®ã«ãããã®ã§ãã ãã«ãã¹ã¯ãªããã«è¿œå ããŸãã
apply plugin: "io.spring.dependency-management"
ã¢ããªã±ãŒã·ã§ã³ãæŽæ°ããŠããŸããããã¹ãŠãæã£ãŠããŸãã ãã®ãããªåçŽãªãããžã§ã¯ãã®å Žåããããspring-boot
æ°ããããŒãžã§ã³ã«ã¢ããã°ã¬ãŒãããããã«å¿
èŠãªããšã®ãã¹ãŠã§ãããå®éã®ãããžã§ã¯ãã§ã¯ããã¡ããä»ã®åé¡ãçºçããå¯èœæ§ããããŸãã
ããã§ã¯ãJUnit 5ã«é²ã¿ãŸãããã
ãã¬ãŒã ã¯ãŒã¯ã®æ°ããããŒãžã§ã³ã«ã¯å€ãã®èå³æ·±ãããšããããŸã ãå°ãªããšãæ°ããããã¥ã¡ã³ããèŠãã ãã§ååã§ãã
çŸåšãJUnit 5ã¯3ã€ã®ã¡ã€ã³ãµããããžã§ã¯ãã§æ§æãããŠããŸãã
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
ã©ãã§
JUnit Platform
ã¯ãJVMã§ãã¹ããå®è¡ããããã®åºç€ã§ãã ãŸãããã¹ããã¬ãŒã ã¯ãŒã¯ãå®è¡ããããã®TestEngine API
ãæäŸãTestEngine API
ã
JUnit Jupiter
TestEngine
æ°ããããã°ã©ãã³ã°ã¢ãã«ãšãJUnit 5ã®ãã¹ãããã³æ¡åŒµæ©èœãèšè¿°ããããã®æ¡åŒµã¢ãã«ãçµã¿åãããŠæ§æãããŸãããŸããJupiterãã©ãããã©ãŒã ã§èšè¿°ããããã¹ããå®è¡ããTestEngine
ãå«ããµããããžã§ã¯ããå«ãŸããŸãã
JUnit Vintage
-JUnit 3ããã³JUnit 4ã§èšè¿°ããããã¹ããå®è¡ããTestEngine
ãæäŸããŸãã
gradleããæ°ãããã¹ããå®è¡ããã«ã¯ãæ°ãããã©ã°ã€ã³ãæ¥ç¶ããå¿
èŠããããŸãã
buildscript { ... dependencies { âŠ. classpath("org.junit.platform:junit-platform-gradle-plugin:1.0.1") } } apply plugin: "org.junit.platform.gradle.plugin"
TestEngine
çŸåšã®ããŒãžã§ã³ã§ãµããŒããããŠãããã¹ããå®è¡ã§ããŸãã ããã«ãæ°ãããã¹ãã«å®å
šã«åãæ¿ããããšãåæã«ã次ã®äŸåé¢ä¿ãè¿œå ããŸãã
testCompile("org.junit.jupiter:junit-jupiter-api:$junitVersion") testRuntime("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
ããã§ããã¹ãŠãæ°ãããã¹ããå®è¡ããæºåãæŽããŸãããå€ããã¹ããæžãæããã ãã§ãã å°ãå€æŽããã®ãããæ£ç¢ºã§ãã JUnit 5ãžã®ç§»è¡ã¯éåžžã«ç°¡åã§ãã äž»ãªããšã¯ãJUnitã¢ãããŒã·ã§ã³ã®ã€ã³ããŒããå€æŽããããšã§ãã
ãããŠã @DisplayName
ã¢ãããŒã·ã§ã³ã䜿çšããŠãã¹ãã®ååãæå®ããæ°ããæ©èœãªã©ãæ°ããæ©èœãè¿œå ã§ããŸãã 以åã¯ãéåžžãã¡ãœããã®ååã«ããã¹ã察象ã®å®å
šãªèª¬æãå«ããå¿
èŠããããŸããã ããã§ã泚éã«èª¬æãå
¥åããã¡ãœããã®ååãçãããããšãã§ããŸãã
ãããã£ãŠãæŽæ°ãããStatsServiceTest
ãã¹ãã¯æ¬¡ã®ããã«ãªããŸãã
@DisplayName("Service test with mockito") public class StatsServiceTest { @Test @DisplayName("stats service should return right data") public void test() {
Intellij Ideaã¯ãã§ã«JUnit 5ããµããŒãããŠããããããã®äžã§çŽæ¥ãã¹ããå®è¡ã§ããŸãã

ãã ãããŸã JUnit5ããµããŒãããŠããªãå¥ã®ã¹ã¿ãžãªã䜿çšããŠããå Žåã§ããã¯ã©ã¹ã«@RunWith(JUnitPlatform.class)
ã¢ãããŒã·ã§ã³@RunWith(JUnitPlatform.class)
ãè¿œå ããããšã«ãããJUnit 4ã®æ©èœã䜿çšããŠãã¹ããå®è¡ã§ããŸãã
ãŸããããŒãžã§ã³5以éãJupiterãã¹ãã®ãµããŒããç»å ŽããŸããã ãã®ããã SpringExtension
ã¯ã©ã¹ãè¿œå ããã SpringRunner
代ããã«äœ¿çšãããããã«SpringRunner
ã StatsControllerTest
ã¯æ¬¡ã®ããã«ãªããŸãã
@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DisplayName("StatsController test") public class StatsControllerTest {
ãã¹ããå®è¡ãããã¹ãŠãæ©èœããããšã確èªããŸãã

ãããã¯ãã¹ãŠãå¿
èŠãªå€æŽã§ãã ãã¹ãŠãæ©èœããããšãåéããŠæ€èšŒããŸã
cd step2_migration_to_spring5_junit5 gradle build && gradle build && java -jar build/libs/step2_migration_to_spring5_junit5-1.0-SNAPSHOT.jar
ã¢ããªã±ãŒã·ã§ã³ãèµ·åããåã«ããã¹ãã«é¢ããæ
å ±ã衚瀺ããããã®æŽæ°ããã圢åŒããããŸãã
Test run finished after 3535 ms [ 4 containers found ] [ 0 containers skipped ] [ 4 containers started ] [ 0 containers aborted ] [ 4 containers successful ] [ 0 containers failed ] [ 6 tests found ] [ 0 tests skipped ] [ 6 tests started ] [ 0 tests aborted ] [ 6 tests successful ] [ 0 tests failed ]
ã¢ããªã±ãŒã·ã§ã³ã®èµ·åãåŸ
ã£ãŠãããã»ã¯ã·ã§ã³1ã®ããã«curl
ã䜿çšããŠããã¹ãŠãæ©èœããããšã確èªããŸãã ãã®JUnit 5ããã³Spring 5ãžã®ç§»è¡ã¯å®äºãããšèŠãªãããšãã§ããŸãã
3.ã³ããªã³ã«æž¡ã
Kotlinã§ã®çç±ã®åé¡ã«ã€ããŠã¯è§ŠããããããŸããïŒããã§ããããã¯ããªãå
šäœè«çãªãããã¯ã§ãïŒã èŠããã«ãç§ã®äž»èŠ³çãªæèŠã¯ãçŸæç¹ã§ã¯JVMããé ãé¢ããããšãªãçŸããç°¡æœãªã³ãŒããæžãããšãã§ããå¯äžã®éçåä»ãèšèªã§ãããéåžžã«éèŠãªããšã«ã¯ãæ¢åã®Javaã©ã€ãã©ãªãšã®éåžžã«ã¹ã ãŒãºã§ã·ãŒã ã¬ã¹ãªçµ±åãç¹ã«Kotlinã¯ã³ã¬ã¯ã·ã§ã³ãæã¡èŸŒãŸããJavaã®æšæºã³ã¬ã¯ã·ã§ã³ã䜿çšããŸãã ããã«ããã«ããã©ãããã©ãŒã ã¢ããªã±ãŒã·ã§ã³ãå®å
šã«Kotlinã§äœæããããšã匷ãæåŸ
ããŠããŸãã
Kotlinãæ¥ç¶ããŸãã ãããè¡ãã«ã¯ããã«ãã¹ã¯ãªããã«kotlin
ãã©ã°ã€ã³ãè¿œå ãã java
ãã©ã°ã€ã³ãåé€ããŸãã
buildscript { ext { ... kotlinVersion = "1.1.51" } dependencies { ⊠classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } ... apply plugin: 'kotlin'
ãŸããSpring 4ã«ã¯ååãªkolin-stdlib
ãè¿œå ããå¿
èŠããããŸãããKotlinã®çµã¿èŸŒã¿ãµããŒãããããäžéšã®å Žæã§ã¯ãªãã¬ã¯ã·ã§ã³ã䜿çšããŠãããããSpring 5ã«ã¯kotlin-reflect
ãæ¥ç¶ããå¿
èŠããããŸãã
compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion") compile("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
Javaã³ãŒãã®å€æãå§ããŸãããã ããã§ã¯ãæåã®æ®µéã§ãIntellij Ideaã¹ã¿ãžãªã圹ç«ã¡ãŸãã Javaãã¡ã€ã«ãéããShiftããŒã2åæŒããŸããæ€çŽ¢ãã£ãŒã«ãã«ãjavaãã¡ã€ã«ãkotlinã«å€æããšå
¥åããŸãã åæ§ã«ãä»ã®ãã¹ãŠã®* .javaãã¡ã€ã«ã«å¯ŸããŠã¢ã¯ã·ã§ã³ãç¹°ãè¿ããŸãã

çµã¿èŸŒã¿ã®ã³ã³ããŒã¿ãŒã§ååã§ããããã®åŸãæã§ã³ãŒãããããã«ä¿®æ£ããå¿
èŠããããŸãã ããã¯äž»ã«ãã©ã®ã¿ã€ããnullable
ããããšãã§ããã©ã®ã¿ã€ããnullable
not-nullable
ãããã®æ±ºå®ã«é¢ä¿ããŸãã ãããŠå€ãã®ç¹ã§ãå€æã¯äžå¯Ÿäžã§ãã ã€ãŸããåºåã¯æ¬è³ªçã«åãJavaã³ãŒãã§ãããKotlinã§ã®ã¿èšè¿°ãããŠããŸãïŒãã ããå®åã³ãŒãã®å€§éšåã¯ãããŸããïŒ
DBConfiguration
äŸã䜿çšããŠå€æãèŠãŠã¿ãŸãããã ã¹ã¿ãžãªã«ããå€æåŸãã³ãŒãã¯æ¬¡ã®ããã«ãªããŸãã
@Configuration @ConfigurationProperties @Getter @Setter class DBConfiguration { var db: DbConfig? = null set(db) { field = this.db } @Bean fun configureDb(): DbConfig { return DbConfig(this.db!!.url, this.db!!.user, unSecure(this.db!!.password)) } private fun unSecure(password: String?): String { val secretIndex = password!!.indexOf("Secret") return password.substring(0, secretIndex) } @Data @AllArgsConstructor @NoArgsConstructor class DbConfig { var url: String? = null set(url) { field = this.url } var user: String? = null set(user) { field = this.user } var password: String? = null set(password) { field = this.password } } }
ãŸã ããŸãçŸãããªãã®ã§ã次ã®ããšãè¡ããŸãã
- ãã³ãã¯ãåé€ããŸãã Kotlinã®æ©èœã¯ãã®ãã¹ãŠã®æ©èœãå®å
šã«ã«ããŒããŠãããããä»ã¯å¿
èŠãããŸããã
- ããããã£ãããã¹ãŠã®
set
ãåé€ããŸãã @ConfigurationProperties
ã¢ãããŒã·ã§ã³ã¯ããããªãã¯getter
ããã³setter
ããfield
@ConfigurationProperties
å¿
èŠãšãããããããããããã§ã¯å¿
èŠãããŸããã ãããŠãnullãèš±å¯ããªãåã«æž¡ããŸããããã©ã«ãã§ã¯ã空ã®è¡ãæå®ããŸãã ããã«nullable
å€ãnullable
åãæ®ãããšã¯ã§ããŸãããã³ãŒãå
ã§ããã«ãããææ
¢ããå¿
èŠããããŸãïŒãããŠãå®è·µã瀺ããŠããããã«ãæ§æããã®ããŒã¿ã«å¯ŸããŠnullå€ãèš±å¯ããªãå€ãäœæããããšããå§ãããŸãïŒã DbConfig
ã¯ã©ã¹ã®å Žåã¯ãããã§data
修食åãè¿œå ã§ããŸããããã¹ãŠã®ããããã£ã«ã€ããŠãããã©ã«ãå€ãæå®ããå¿
èŠããããŸããããã«ãããã¯ã©ã¹ã«åŒæ°ã®ãªãã³ã³ã¹ãã©ã¯ã¿ãŒããããŸãã DBConfiguration
ã¯ã©ã¹ã®open
ãè¿œå ãopen
ã ããã¯ãSpringã«çµã¿èŸŒãŸããã¯ã©ã¹åå®çŸ©ã¡ã«ããºã ãæ£ããæ©èœããããšãä¿èšŒããããã«å¿
èŠã§ãã openãè¿œå ããªãå¥ã®ãªãã·ã§ã³ããããŸããã kotlin-spring
ãã©ã°ã€ã³ãkotlin-spring
ã«æ¥ç¶ããŸããããã¯ãã³ã³ãã€ã«æ®µéã§åå®çŸ©ã«å¿
èŠãªã¯ã©ã¹ïŒããã³ã¡ãœããïŒãéããŸãããæ瀺çãªã¢ãããŒãã奜ã¿ãŸãã- åã®æ®µèœãšåæ§ã«ãããã©ã«ãã®ã¡ãœããã
final
ããã configureDb
open修食åã«@Bean
ã¢ãããŒã·ã§ã³ãè¿œå ãconfigureDb
ã¯åãçç±ã«ãããŸãã substringBefore
ã¡ãœããã®extension
unSecure
ã¡ãœãããç°¡çŽ åãunSecure
æ¹ååŸã次ã®ãã®ãåŸãããŸãã
@Configuration @ConfigurationProperties open class DBConfiguration { var db: DbConfig = DbConfig() @Bean open fun configureDb(): DbConfig { return DbConfig(db.url, db.user, unSecure(db.password)) } private fun unSecure(password: String): String { return password.substringBefore("Secret") } data class DbConfig( var url: String = "", var user: String = "", var password: String = "" ) }
åæ§ã«ã StatsService
ãµãŒãã¹ãå€æããã³ç°¡çŽ åããŸãã 次ã®ãã®ãåŸãããŸãã
@Service open class StatsService(private val userRepository: UserRepository) { open fun getStats(): Stats { val allUsers = userRepository.findAllUsers() if (allUsers.isEmpty()) throw RuntimeException("not find any user") val oldestUser = allUsers.maxBy { it.age } val youngestUser = allUsers.minBy { it.age } return Stats( allUsers.size, oldestUser!!, youngestUser!! ) } }
Javaã§ã¯ãã³ã³ãããŒã©ãŒå¿çã®åããªã¢ã³ãã®ã¯ã©ã¹ããšã«åå¥ã®ãã¡ã€ã«ãäœæããå¿
èŠããããŸããã ãã®çµæã4ã€ã®ã¯ã©ã¹ãããããããããåå¥ã®ãã¡ã€ã«ã«ãããŸããã
Kotlinã®ç»å Žã«ãããããªãç°¡æœãªèšé²ã§ãã¹ãŠã1ã€ã®ãã¡ã€ã«ã«åããããšãã§ããããã«ãªããŸããã
interface Response { val success: Boolean val description: String } data class DeleteResponse( override val success: Boolean, override val description: String ) : Response data class StatsResponse( override val success: Boolean, override val description: String, val stats: Stats ) : Response data class UserAddResponse( override val success: Boolean, override val description: String, val userId: Long ) : Response data class UserResponse( override val success: Boolean, override val description: String, val users: List<User> ) : Response
ä»ã®ãã¹ãŠã®ã¯ã©ã¹ãåãæ¹æ³ã§ç·šéããŠããã¹ãã«é²ã¿ãŸãããã ããã§ã¯ãKocklinãè¿œå ã®ã©ã€ãã©ãªãæ¥ç¶ããå¿
èŠãããmockitoãç©æ¥µçã«äœ¿çšããŠããŸãã
testCompile("com.nhaarman:mockito-kotlin-kt1.1:1.5.0")
ãŸãããã¹ãŠã®ãã¹ãã§ãå€ãmockitoã®ã€ã³ããŒããåé€ããcom.nhaarman.mockito_kotlinããã®ã€ã³ããŒãã«å€æŽããå¿
èŠããããŸãã
when
in Kotlinã¯ããŒã¯ãŒãã§ããããã代ããã«`when`
ãæ®ããªããã¹ãã§ã¯ãã©ã€ãã©ãªã®`when`
ã䜿çšããŸãã ãããŠãåçŽãªmock<T>()
æ§é ã䜿çšããŠmokaãäœæã§ããããã«ãªããŸããããŸãã宣èšæã«æ¢ç¥ã§ããå Žåãã¿ã€ãã¯ãªãã·ã§ã³ã«ãªããŸãã
ãŸãã宣èšãããfield
ã«æ³šæãæã䟡å€ãããfield
ããã®field
ã®å€ã¯ããã¹ãã®éå§æã«ãã¹ããã¬ãŒã ã¯ãŒã¯ã«ãã£ãŠåæåãããŸããã ãã¹ãŠã®å®£èšã®Kotlinã§ã¯ãå€ãããã«åæåããå¿
èŠããããŸãããã®ããããã®ãããªfield
nullable
åã®ã¿ãæå®ããåæå€ãšããŠnullãæå®ãããã lateinit
䜿çšã§ãlateinit
ïŒãã®å Žåã¯æãŸããïŒã lateinit
ã®lateinitã¯æ¬¡ã®ããã«æ©èœããŸãïŒå®£èšæã«å€æ°ã®å€ãåæåããããšã¯ã§ããŸããããå€ãååŸããããšãããšãåæåãããŠããããšã確èªãããåæåãããŠããªãå Žåã¯äŸå€ãã¹ããŒãããŸãã ãããã£ãŠããã®æ©äŒãæ
éã«äœ¿çšãã䟡å€ããããŸãã ãã®ãããªå¯èœæ§ã¯ãæ¢åã®Javaãã¬ãŒã ã¯ãŒã¯ãšã®äŸ¿å©ãªå¯Ÿè©±ã®ããã«æ¬è³ªçã«çŸããŸããã
field
JavaããKotlinãžã®ç§»è¡ã®äŸã¯ã宣èšæã«åæåãããŸããã
Kotlinã®ç»å Žã«ããçŽ æŽãããè¿œå ã¯ã @DisplayName
ã¢ãããŒã·ã§ã³ãäžèŠã«ãªã£ãããšã§ãã é·ãã¡ãœããåãã¹ããŒã¹ãå«ãã¹ããŒã¹ã«æžãæããããšãã§ããŸãã ãŸããã¹ã¿ãžãªã¯ä»¥äžã®ãã«ããæäŸããŸãã

Kotlinã®äž»ãªå©ç¹ã®1ã€ã¯ãnullablityãåã·ã¹ãã ã«çµ±åããããšã§ãããã®å©ç¹ãæ倧éã«æŽ»çšããããã«ãã³ã³ãã€ã«ãã©ã°ã-Xjsr305 = strictããè¿œå ã§ããŸãã ãããè¡ãã«ã¯ããã«ãã¹ã¯ãªããã«è¿œå ããŸãã
compileKotlin { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = ["-Xjsr305=strict"] } }
ãã®ãªãã·ã§ã³ã䜿çšãããšãKotlinã¯null 5åã«é¢ãã泚éãSpring 5ã§æ€èšããNPEãååŸããå¯èœæ§ã倧å¹
ã«æžå°ããŸãã
ããã«ãã¿ãŒã²ããjvm 8ã瀺ããŸãïŒKotlinãjvm 8ãã€ãã³ãŒãã«ã³ã³ãã€ã«ãããå ŽåããããŸã§ã¯jvm 6ãã€ãã³ãŒãã«ã³ã³ãã€ã«ããå¯èœæ§ããããŸãïŒã
ãã®å€æã§å®äºããããšãã§ããŸãã ã¢ããªã±ãŒã·ã§ã³ãåéããŠèµ·åããŸãã
cd step3_migration_to_kotlin gradle build && java -jar build/libs/step3_migration_to_kotlin-1.0-SNAPSHOT.jar
ãããŠãç§ãã¡ã¯äœãå£ããããã¹ãŠãããŸããããšç¢ºä¿¡ããŠããŸãã ãã®å Žåã次ã«é²ã¿ãŸãã
4. spring-webfluxãšæ©èœçãªKotlinã«æž¡ããŸã
Springã®ããã°æçš¿ãèŠããšãã«ããã®ç§»è¡ã«è§ŠçºãããŸãã ã ãã®äžã§ãSébastienDeleuzeã¯ãspring-webfluxã«åºã¥ããŠãããspring-bootã䜿çšããªãæ©èœçã¢ãããŒãã§Springã¢ããªã±ãŒã·ã§ã³ãåæåããäŸã瀺ããŠããŸãã
Spring 5ã®ç»å Žã«ãããããŸããŸãªWebãµãŒããŒãšããŸããŸãªã¢ãããŒãã§ã¢ããªã±ãŒã·ã§ã³ãå¯å€çã«åæåããããšãå¯èœã«ãªããŸããã

圌ã®äŸã§ã¯ãSébastienã¯Nettyã§ã¢ããªã±ãŒã·ã§ã³ãå®è¡ããŸããäŸã¯ããã«ãããŸã ã å€æŽã®ããã«ãUndertowã§ã¢ããªã±ãŒã·ã§ã³ãèµ·åããŸãã
ã¹ããªã³ã°ããŒããªãã§Springãèµ·åããããšããå§ããŸãããã GenericApplicationContext
, web- . , GenericApplicationContext
WebHttpHandlerBuilder
, HttpHandler
, , , web- .
Spring :
, . :
class Application(port: Int? = null, beanConfig: BeanDefinitionDsl = beansConfiguration()) { private val server: Undertow init { val context = GenericApplicationContext().apply { beanConfig.initialize(this) loadConfig() refresh() } val build = WebHttpHandlerBuilder.applicationContext(context).build() val adapter = build .run { UndertowHttpHandlerAdapter(this) } val startupPort = port ?: context.environment.getProperty("server.port")?.toInt() ?: DEFAULT_PORT server = Undertow.builder() .addHttpListener(startupPort, "localhost") .setHandler(adapter) .build() } fun start() { server.start() } fun stop() { server.stop() } private fun GenericApplicationContext.loadConfig() { val resource = ClassPathResource("/application.yml") val sourceLoader = YamlPropertySourceLoader() val properties = sourceLoader.load("main config", resource, null) environment.propertySources.addFirst(properties) } companion object { private val DEFAULT_PORT = 8080 } } fun main(args: Array<String>) { Application().start() }
, spring-boot
. application.yml, @ConfigurationProperties
spring-boot
, yml ( snakeyaml) spring-boot-starter
. spring-boot-starter
, spring-boot
.
beansConfiguration
, . , , , , . :
fun beansConfiguration(beanConfig: BeanDefinitionDsl.() -> Unit = {}): BeanDefinitionDsl = beans { bean<DBConfiguration>() //controllers bean<StatsController>() bean<UserController>() //repository bean<UserRepository>() //services bean<StatsService>() //routes bean<Routes>() bean("webHandler") { RouterFunctions.toWebHandler(ref<Routes>().router(), HandlerStrategies.builder().viewResolver(ref()).build()) } //view resolver bean { val prefix = "classpath:/templates/" val suffix = ".mustache" val loader = MustacheResourceTemplateLoader(prefix, suffix) MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply { setPrefix(prefix) setSuffix(suffix) } } //processors bean<CommonAnnotationBeanPostProcessor>() bean<ConfigurationClassPostProcessor>() bean<ConfigurationPropertiesBindingPostProcessor>() beanConfig() }
beans
, Spring 5 spring-context
. Kotlin . bean
, . , , , , ViewResolver
.
Spring , @Bean
, @Configuration
, @ConfigurationProperties
, @PostConstruct
, , , BeanPostProcessor
.
, , .
Routes
. âwebHandlerâ RouterFunctions.toWebHandler(ref<Routes>().router(), âŠ)
.
ref<Routes>()
, spring-context
, :
inline fun <reified T : Any> ref(name: String? = null) : T = when (name) { null -> context.getBean(T::class.java) else -> context.getBean(name, T::class.java) }
, Routes
:
open class Routes( private val userController: UserController, private val statsController: StatsController ) { fun router() = router { accept(APPLICATION_JSON).nest(userController.nest()) accept(APPLICATION_JSON).nest(statsController.nest()) GET("/") { ok().render("index") } } }
, , , router
spring-context
. Sébastien :
accept(TEXT_HTML).nest { GET("/") { ok().render("index") } GET("/sse") { ok().render("sse") } GET("/users", userHandler::findAllView) } "/api".nest { accept(APPLICATION_JSON).nest { GET("/users", userHandler::findAll) } accept(TEXT_EVENT_STREAM).nest { GET("/users", userHandler::stream) } }
, . , , .
, nest
. :
interface Controller { fun nest(): RouterFunctionDsl.() -> Unit }
StatsController
:
open class StatsController(private val statsService: StatsService) : Controller { override fun nest(): RouterFunctionDsl.() -> Unit = { GET("/stats") { ok().body(stats()) } } open fun stats(): Mono<StatsResponse> { val stats = statsService.getStats() return Mono.just(StatsResponse(true, "user stats", stats)) } }
â/statsâ, GET stats
. Flux
Mono
, spring-webflux
. UserController
:
open class UserController(private val userRepository: UserRepository) { fun nest(): RouterFunctionDsl.() -> Unit = { GET("/users") { ok().body(users()) } GET("/user/{id}") { ok().body(user(it.pathVariable("id").toLong())) } PUT("/user") { ok().body(addUser(it.bodyToMono(User::class.java))) } DELETE("/user/{id}") { ok().body(deleteUser(it.pathVariable("id").toLong())) } } open fun users(): Mono<UserResponse> {
, . , , , , .
. StatsServiceTest
, . , StatsControllerTest
:
@DisplayName("StatsController test") open class StatsControllerTest { private val statsServiceMock = mock<StatsService>() private val port = 8181 private val configuration = beansConfiguration { bean { statsServiceMock } } private val application = Application(port, configuration) @BeforeEach fun before() { reset(statsServiceMock) application.start() } @AfterEach fun after() { application.stop() } @Test fun `stats controller should return valid result`() { val expectedStats = Stats( 2, User(1L, "name1", "surname1", 25), User(2L, "name2", "surname2", 30) ) whenever(statsServiceMock.getStats()).thenReturn(expectedStats) val expectedResponse = StatsResponse(true, "user stats", expectedStats) val response: StatsResponse = "http://localhost:$port/stats".GET() assertEquals(expectedResponse, response, "invalid response") } }
. Spring, . . .
restTemplate. : "http://localhost:$port/stats".GET()
. GET , . OkHttp3:
var client = OkHttpClient() val JSON = MediaType.parse("application/json; charset=utf-8") val mapper: ObjectMapper = ObjectMapper() .registerKotlinModule() inline fun <reified T> String.GET(): T { val request = Request.Builder() .url(this) .build() return client.newCall(request).executeAndGet(T::class.java) } inline fun <reified T> String.PUT(data: Any): T { val body = RequestBody.create(JSON, mapper.writeValueAsString(data)) val request = Request.Builder() .url(this) .put(body) .build() return client.newCall(request).executeAndGet(T::class.java) } inline fun <reified T> String.POST(data: Any): T { val body = RequestBody.create(JSON, mapper.writeValueAsString(data)) val request = Request.Builder() .url(this) .post(body) .build() return client.newCall(request).executeAndGet(T::class.java) } inline fun <reified T> String.DELETE(): T { val request = Request.Builder() .url(this) .delete() .build() return client.newCall(request).executeAndGet(T::class.java) } fun <T> Call.executeAndGet(clazz: Class<T>): T { execute().use { response -> return mapper.readValue(response.body()!!.string(), clazz) } }
UserControllerTest
.
UserControllerTest @DisplayName("UserController test") class UserControllerTest { private var port = 8181 private lateinit var userRepositoryMock: UserRepository private lateinit var configuration: BeanDefinitionDsl private lateinit var application: Application @BeforeEach fun before() { userRepositoryMock = mock() configuration = beansConfiguration { bean { userRepositoryMock } } application = Application(port, configuration) application.start() } @AfterEach fun after() { application.stop() } @Test fun `all users should be return correctly`() { val users = listOf( User(1L, "name1", "surname1", 25), User(2L, "name2", "surname2", 30) ) whenever(userRepositoryMock.findAllUsers()).thenReturn(users) val expectedResponse = UserResponse(true, "return users", users) val response: UserResponse = "http://localhost:$port/users".GET() assertEquals(expectedResponse, response, "invalid response") } @Test fun `user should be return correctly`() { val user = User(1L, "name1", "surname1", 25) whenever(userRepositoryMock.findUser(1L)).thenReturn(user) whenever(userRepositoryMock.findUser(2L)).thenReturn(null) val expectedResponse = UserResponse(true, "find user with requested id", listOf(user)) val response: UserResponse = "http://localhost:$port/user/1".GET() assertEquals(expectedResponse, response, "not find exists user") val expectedMissedResponse = UserResponse(false, "user not found", emptyList()) val missingResponse: UserResponse = "http://localhost:$port/user/2".GET() assertEquals(expectedMissedResponse, missingResponse, "invalid user response") } @Test fun `user should be added correctly`() { val newUser1 = User(null, "name", "surname", 15) val newUser2 = User(null, "name2", "surname2", 18) whenever(userRepositoryMock.addUser(newUser1)).thenReturn(15L) whenever(userRepositoryMock.addUser(newUser2)).thenReturn(null) val expectedResponse = UserAddResponse(true, "user add successfully", 15L) val response: UserAddResponse = "http://localhost:$port/user".PUT(newUser1) assertEquals(expectedResponse, response, "invalid add response") val expectedErrorResponse = UserAddResponse(false, "user not added", -1L) val errorResponse: UserAddResponse = "http://localhost:$port/user".PUT(newUser2) assertEquals(expectedErrorResponse, errorResponse, "invalid add response") } @Test fun `user should be deleted correctly`() { whenever(userRepositoryMock.deleteUser(1L)).thenReturn(true) whenever(userRepositoryMock.deleteUser(2L)).thenReturn(false) val expectedResponse = DeleteResponse(true, "user has been deleted") val response: DeleteResponse = "http://localhost:$port/user/1".DELETE() assertEquals(expectedResponse, response, "invalid response") val expectedErrorResponse = DeleteResponse(false, "user not been deleted") val errorResponse: DeleteResponse = "http://localhost:$port/user/2".DELETE() assertEquals(expectedErrorResponse, errorResponse, "invalid response") } }
:
group 'evgzakharov' version '1.0-SNAPSHOT' buildscript { ext { springBootVersion = "2.0.0.M5" junitVersion = "5.0.1" kotlinVersion = "1.1.51" } repositories { jcenter() maven { url = "http://repo.spring.io/milestone" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath("org.junit.platform:junit-platform-gradle-plugin:1.0.1") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") } } apply plugin: "org.springframework.boot" apply plugin: "org.junit.platform.gradle.plugin" apply plugin: 'kotlin' apply plugin: "io.spring.dependency-management" sourceCompatibility = 1.8 repositories { jcenter() maven { url = "http://repo.spring.io/milestone" } } dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion") compile("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") compile("org.springframework.boot:spring-boot-starter") compile("org.springframework:spring-webflux") compile("io.undertow:undertow-core") compile("com.samskivert:jmustache") compile("com.fasterxml.jackson.module:jackson-module-kotlin") testCompile("com.nhaarman:mockito-kotlin-kt1.1:1.5.0") testCompile("com.squareup.okhttp3:okhttp:3.9.0") testCompile("org.junit.jupiter:junit-jupiter-api:$junitVersion") testRuntime("org.junit.jupiter:junit-jupiter-engine:$junitVersion") } compileKotlin { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = ["-Xjsr305=strict"] } } compileTestKotlin { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = ["-Xjsr305=strict"] } }
, .
cd step4_migration_to_webflux gradle build && java -jar build/libs/step4_migration_to_webflux-1.0-SNAPSHOT.jar
curl
5. Kotlin Dsl
Kotlin Dsl ( 0.12.1 ), .
, Groovy Dsl, IDE, . Gradle Kotlin Dsl 3.0 ( 4.2.1), Intellij Idea â â Kotlin.
. , . , , . :
- ,
- , , Kotlin Dsl , Groovy Dsl, Groovy Closure Kotlin . , , artifactory:
artifactory { setContextUrl("${project.findProperty("artifactory_contextUrl")}") publish(delegateClosureOf<PublisherConfig> { repository(delegateClosureOf<GroovyObject> { setProperty("repoKey", "ecomm") setProperty("username", project.findProperty("artifactory_user")) setProperty("password", project.findProperty("artifactory_password")) setProperty("mavenCompatible", true) defaults(delegateClosureOf<GroovyObject> { invokeMethod("publishConfigs", "wgReports") }) }) }) }
Groovy :
artifactory { contextUrl = "${artifactory_contextUrl}" publish { repository { repoKey = 'ecomm' username = "${artifactory_user}" password = "${artifactory_password}" mavenCompatible = true } defaults { publishConfigs('wgReports') } } }
, Gradle API, Closure, Kotlin Dsl.
. â.ktsâ build.gradle :
compile
dependencies
, Kotlin Gradle:
plugins { id("org.jetbrains.kotlin.jvm") version "1.1.51" }
- Groovy
group 'evgzakharov'
, , Kotlin . group = "evgzakharov"
- buildscript . . ,
ext.springBootVersion = "2.0.0.M5"
: extra[âspringBootVersionâ] = "2.0.0.M5"
, buildscript
val springBootVersion by extra { "2.0.0.M5" }
dependencies
, :
val springBootVersion: String by project.extra val junitVersion: String by project.extra val kotlinVersion: String by project.extra
, Kotlin, .
, , compileKotlin
. :
tasks.withType<KotlinCompile> { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = listOf("-Xjsr305=strict") } }
Kotlin Dsl:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile group = "evgzakharov" version = "1.0-SNAPSHOT" buildscript { val springBootVersion by extra { "2.0.0.M5" } extra["junitVersion"] = "5.0.1" extra["kotlinVersion"] = "1.1.51" repositories { jcenter() maven { setUrl("http://repo.spring.io/milestone") } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") classpath("org.junit.platform:junit-platform-gradle-plugin:1.0.1") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.51") } } plugins { id("org.jetbrains.kotlin.jvm") version "1.1.51" } apply { plugin("org.springframework.boot") plugin("org.junit.platform.gradle.plugin") plugin("io.spring.dependency-management") } repositories { jcenter() maven { setUrl("http://repo.spring.io/milestone") } } val junitVersion: String by project.extra val kotlinVersion: String by project.extra dependencies { compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlinVersion") compile("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") compile("org.springframework.boot:spring-boot-starter") compile("org.springframework:spring-webflux") compile("io.undertow:undertow-core") compile("com.samskivert:jmustache") compile("com.fasterxml.jackson.module:jackson-module-kotlin") testCompile("com.nhaarman:mockito-kotlin-kt1.1:1.5.0") testCompile("com.squareup.okhttp3:okhttp:3.9.0") testCompile("org.junit.jupiter:junit-jupiter-api:$junitVersion") testRuntime("org.junit.jupiter:junit-jupiter-engine:$junitVersion") } tasks.withType<KotlinCompile> { kotlinOptions { jvmTarget = "1.8" freeCompilerArgs = listOf("-Xjsr305=strict") } }
:
cd step5_migration_kotlin_dsl gradle build && java -jar build/libs/step5_migration_kotlin_dsl-1.0-SNAPSHOT.jar
:)
ãŸãšã
. Kotlin, Spring JUnit, Kotlin Dsl, Spring Spring-Boot ()
, , spring-webflux. , , . , Spring . , , , , Spring . .
Spring Boot 2 JUnit 5 , . ? , , .
⊠Kotlin! â . . stackoverflow , , Mockito, , . , , , , Java Kotlin ( )
github
! :)