스프링 부트 초창기에 Spring MVC 에서 Thymeleaf를 사용해보았는데, WebFlux 에서 사용하기는 처음이었다.
더군다나 코틀린 언어도 처음 접해보는 거라서 많이 낯설었다.
기존에 알던데로 application.yml에 설정을 작성하고, viewResolver를 추가하고, addResourcesHandler를 추가하고....
Spring MVC에서 하던대로 했는데 아무리 해도 templates/index.html을 뷰리졸버가 찾지 못했다.
구글 검색에서 나오는 많은 예시들을 따라해봤는데 여전히 안 되더라..
폭풍 검색 끝에 실행되는 소스코드를 찾았다.
(나중에 webflux에 익숙해지면 이 포스트를 보며 한심하게 느껴지려나....)
프로젝트 명: 두끼 (Dookki) * 그 떡볶이 맞음
build.gradle.kt
dependencies {
// webflux
implementation("org.springframework.boot:spring-boot-starter-webflux")
// thymeleaf
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
// kotlin
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
// others
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}
DookkiApplication
/**
* DookkiApplication
*/
@ComponentScan("com.domain.dookki")
@SpringBootApplication
class DookkiApplication : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
return application.sources(DookkiApplication::class.java)
}
}
fun main(args: Array<String>) {
val application = SpringApplication(DookkiApplication::class.java)
application.webApplicationType = WebApplicationType.REACTIVE
application.run(*args);
// runApplication<DookkiApplication>(*args)
}
맨 밑에 runApplication<Application>(*args) 이 한줄만으로도 어플리케이션 실행이 가능한데, 간결한 라인을 놔두고 굳이 세 줄이나 입력하여 run 한 이유는, 소스코드에서 WebConfig를 재정의할 수가 없더라.
자동으로 @EnableWebflux를 하고 모든 것을 기본 설정으로 서버를 실행하는 것 같았다.
아마도 application.yml에 정의한 대로 적용이 되지 않을까 싶다.
그러나 여기에서는 WebConfig에 뷰 리졸버를 재정의하였기 때문에 WebFluxConfigurer를 상속한 클래스를 작성할 필요가 있다.
WebConfig
/**
* WebConfig
*/
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer, ApplicationContextAware {
private var context: ApplicationContext? = null
override fun setApplicationContext(applicationContext: ApplicationContext) {
this.context = applicationContext
}
/***********************************
* Setting thymeleaf view resolvers
***********************************/
override fun configureViewResolvers(registry: ViewResolverRegistry) {
registry.viewResolver(thymeleafReactiveViewResolver())
}
@Bean
fun thymeleafReactiveViewResolver(): ThymeleafReactiveViewResolver {
val viewResolver = ThymeleafReactiveViewResolver()
viewResolver.templateEngine = thymeleafTemplateEngine()
return viewResolver
}
@Bean
fun thymeleafTemplateEngine(): ISpringWebFluxTemplateEngine {
val templateEngine = SpringWebFluxTemplateEngine()
templateEngine.setTemplateResolver(thymeleafTemplateResolver())
return templateEngine
}
@Bean
fun thymeleafTemplateResolver(): ITemplateResolver {
val resolver = SpringResourceTemplateResolver()
resolver.setApplicationContext(context!!)
resolver.prefix = "classpath:templates/"
resolver.suffix = ".html"
resolver.templateMode = TemplateMode.HTML
resolver.isCacheable = false
resolver.checkExistence = false
resolver.characterEncoding = "UTF-8"
resolver.order = 1
return resolver
}
@Bean
fun staticResourceRouter(): RouterFunction<ServerResponse?>? {
return RouterFunctions.resources("/**", ClassPathResource("static/"))
}
// End of setting thymeleaf view resolvers
}
정적 리소스 경로 지정에 대해서...
처음에는 SpringMVC에서 하는것처럼, WebFluxConfigurer 클래스에 정의된 addResourcesHandler 함수를 override 했는데 아무리 해도 css, js 파일을 못찾더라.
그런데 위에 있는 소스코드처럼 RouterFunction<ServerResponse>를 반환하는 빈을 정의하니까 드디어 되더라.
뭐지? 일단 되서 좋은데 이유는 모르겠다.
templates/index.html
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" th:href="@{/css/index.css}">
<script th:src="@{/js/index.js}"></script>
<title>Dookki</title>
</head>
<body>
<div id="container">
</div>
</body>
<script th:inline="javascript">
console.log("")
</script>
</html>
WebRouter
/**
* WebRouter
*/
@Controller
class WebRouter {
@GetMapping("/", "/index")
fun index (): String {
return "index"
}
}
위의 방식은 MVC 에서 사용하는 패턴이고 WebFlux에서는 RouterFunction 클래스를 사용하던데 내가 미숙해서 우선은 Controller를 사용하였다.
Controller 클래스의 위치는 단연 DookkiApplication 클래스 내에 @ComponentScan 에 정의한 패키지 하위에 있어야 한다.
resources 하위 디렉토리 구조
참조
https://stackoverflow.com/questions/43622053/how-to-serve-static-content-using-webflux
https://myhappyman.tistory.com/109
'생계유지형 개발자 > Spring Framework' 카테고리의 다른 글
[Spring5] Spring MVC vs WebFlux (0) | 2021.03.23 |
---|---|
[MyBatis] 계층형 결과 리스트 생성 (resultMap, collection 활용) (0) | 2021.02.25 |
[Spring] proprteis 파일 한글 깨짐 (0) | 2020.07.15 |
[spring] application.yml에 정의한 값이 상수이며 0으로 시작할 때 @Value 형변환 오류 (2) | 2020.01.03 |
[spring] 매번 까먹는 JUnit4 테스트케이스 클래스 생성 (0) | 2019.12.19 |