Многомодульный проект Spring Boot Gradle не может найти расположение шаблонов

#spring-boot #gradle #intellij-idea #mustache

Вопрос:

Я управляю своим проектом с помощью Gradle, и в корневом проекте есть 3 модуля(Spring Boot).

Каждый модуль имеет свои собственные шаблоны в пользовательском расположении шаблона, то есть /WEB-INF/templates/ я устанавливаю эти свойства для каждого application.yml .

Однако при запуске приложений отображается журнал предупреждений

 2021-09-16 22:53:39.153  WARN 17043 --- [  restartedMain] o.s.b.a.m.MustacheAutoConfiguration      : Cannot find template location: /WEB-INF/templates/ (please add some templates, check your Mustache configuration, or set spring.mustache.check-template-location=false)
 

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

Вот моя сборка.gradle

 // root project
plugins {
    id 'org.springframework.boot' version '2.5.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

subprojects {

    group = 'yes.youcan'
    version = '0.0.1-SNAPSHOT'

    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'org.springframework.boot'

    repositories {
        mavenCentral()
    }

    configurations {
        compileOnly {
            extendsFrom annotationProcessor
        }
    }

    compileJava {
        sourceCompatibility = 1.8
    }

    test {
        useJUnitPlatform()
    }

    project(':child2') {
        apply plugin: 'war'
    }

    project(':child3') {
        apply plugin: 'war'
    }

    dependencyManagement {
        imports {
            mavenBom("org.springframework.boot:spring-boot-dependencies:2.5.4")
        }
    }
}

// child project 1
ext {
    keycloakVersion = '15.0.2'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation "org.springframework.boot:spring-boot-starter-log4j2"
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-mustache'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    implementation 'org.keycloak:keycloak-spring-boot-starter'

    compileOnly 'org.projectlombok:lombok'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'io.rest-assured:rest-assured'
}

dependencyManagement {
    imports {
        mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keycloakVersion}"
    }
}

// child project 2
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation "org.springframework.boot:spring-boot-starter-log4j2"
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-mustache'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    compileOnly 'org.projectlombok:lombok'
    compileOnly 'com.querydsl:querydsl-jpa'
    compileOnly 'com.querydsl:querydsl-core'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'javax.persistence:javax.persistence-api'
    annotationProcessor 'javax.annotation:javax.annotation-api'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'com.querydsl:querydsl-jpa'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'io.rest-assured:rest-assured'
}

def generated = 'src/main/generated'

sourceSets {
    main.java.srcDirs  = [ generated ]
}

tasks.withType(JavaCompile) {
    options.annotationProcessorGeneratedSourcesDirectory(file(generated))
}

clean.doLast {
    file(generated).deleteDir()
}

// child project 3
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation "org.springframework.boot:spring-boot-starter-log4j2"
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-mustache'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    compileOnly 'com.querydsl:querydsl-jpa'
    compileOnly 'com.querydsl:querydsl-core'
    compileOnly 'org.projectlombok:lombok'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'javax.persistence:javax.persistence-api'
    annotationProcessor 'javax.annotation:javax.annotation-api'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'com.querydsl:querydsl-jpa'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'io.rest-assured:rest-assured'
}

clean {
    delete file('src/main/generated')
}
 

Это конфигурация, когда приложение запускается независимо

 plugins {
    id 'org.springframework.boot' version '2.5.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
    id 'war'
}

group = 'yes.youcan
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-mustache'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    compileOnly 'com.querydsl:querydsl-jpa'
    compileOnly 'com.querydsl:querydsl-core'
    compileOnly 'org.projectlombok:lombok'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'javax.persistence:javax.persistence-api'
    annotationProcessor 'javax.annotation:javax.annotation-api'
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"

    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testImplementation 'io.rest-assured:rest-assured'
}

clean {
    delete file('src/main/generated')
}

test {
    useJUnitPlatform()
}
 

application.yml

 spring:
  profiles:
    active: local

  mustache:
    prefix: /WEB-INF/templates/
    suffix: .html

  data:
    web:
      pageable:
        default-page-size: 5

server:
  port: 8081

---

spring:
  config:
    activate:
      on-profile: local

  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password:

  h2:
    console:
      enabled: true
      path: /h2-console

  jpa:
    hibernate.ddl-auto: create-drop
    properties.hibernate.format_sql: true
    properties.hibernate.dialect: org.hibernate.dialect.H2Dialect
    defer-datasource-initialization: true

logging:
  level:
    org.hibernate.SQL: debug
    org.springframework.security: error

---

spring:
  config:
    activate:
      on-profile: dev

  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password:

  h2:
    console:
      enabled: true
      path: /h2-console

  jpa:
    hibernate.ddl-auto: create-drop
    properties.hibernate.format_sql: true
    properties.hibernate.dialect: org.hibernate.dialect.H2Dialect

logging.level.org.hibernate.SQL: debug
logging.level.org.springframework.security: error

 

And this is my project structure

 ├── child1
│   ├── gradle
│   │   └── wrapper
│   └── src
│       └── main
│           ├── java
│           │   └── yes
│           │       └── youcan
│           ├── resources
│           └── webapp
│               ├── resources
│               │   └── css
│               └── WEB-INF
│                   └── templates
├── child2
│   ├── gradle
│   │   └── wrapper
│   └── src
│       └── main
│           ├── generated
│           │   └── yes
│           │       └── youcan
│           ├── java
│           │   └── yes
│           │       └── youcan
│           ├── resources
│           └── webapp
│               ├── resources
│               │   ├── css
│               │   ├── img
│               │   └── js
│               └── WEB-INF
│                   └── templates
└── child3
    ├── gradle
    │   └── wrapper
    └── src
        └── main
            ├── java
            │   └── yes
            │       └── youcan
            ├── resources
            └── webapp
                ├── resources
                │   ├── css
                │   ├── img
                │   └── js
                └── WEB-INF
                    └── templates
 

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

===== ОТРЕДАКТИРОВАНО 23/09/21 === = =

Я нашел кое-что необычное! Когда запускается проект с несколькими модулями, он проверяет наличие ресурса, в котором находятся шаблоны /WEB-INF/templates/ TemplateLocation.java , следующим образом

 public boolean exists(ResourcePatternResolver resolver) {
        Assert.notNull(resolver, "Resolver must not be null");
        if (resolver.getResource(this.path).exists()) {
            return true;
        }
        try {
            return anyExists(resolver);
        }
        catch (IOException ex) {
            return false;
        }
    }
 

Я проверил путь к корневому файлу во время обработки этой проверки resolver.getResource('/').getFile() , и он возвращает некоторый временный каталог /AppData/.../tomcat-docbase... , с другой стороны, когда я выполнял ту же работу над проектом с одним модулем, он возвращает фактический каталог проекта D:Java...

Я надеюсь, что это может быть ключом к разгадке проблемы

Комментарии:

1. Итак, где в проекте templates находится ваша папка? Вы добавляете его в путь к классу в Gradle, если он не находится в стандартном каталоге источника/ресурса?

2. It's weird because i run the application independently which is not open project as a multi module project just open project as a single project it works well. Тогда возникает вопрос — какой файл Gradle используется для настройки этого проекта, когда он работает, а когда не работает. Сравните эти конфигурации, чтобы по возможности найти проблему.

3. @Andrey Спасибо за ваш комментарий, я забыл указать расположение шаблонов дерева, поэтому добавил в виде текста. Они идут дальше webapp/WEB-INF/templates/ . Также я добавил файл сборки, но я не думаю, что он выглядит по-другому

4. Проверьте это baeldung.com/spring-thymeleaf-template-directory#change-default

5. @Andrey Спасибо за ваше предложение, но, к сожалению, оно не подходит для моего случая, и я приложил более подробную информацию о своей проблеме, надеюсь, это вам поможет