0

I have a standard Spring application which I normally develop in IntelliJ. When it is started from IntelliJ preconfigured runner, then everything works, but when the app is compiled and run with the following commands:

./gradlew clean
./gradlew openApiGenerate
./gradlew build
java -jar build/libs/joyjobcore-0.0.1-SNAPSHOT.jar

Then I have an error with a wrong path to my static resources:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cardUploader': Invocation of init method failed; nested exception is java.io.FileNotFoundException: class path resource [static/logo_joyjob.png] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/home/bogdan/Programming/Java/joyjobcore/build/libs/joyjobcore-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/static/logo_joyjob.png
Caused by: java.io.FileNotFoundException: class path resource [static/logo_joyjob.png] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/home/bogdan/Programming/Java/joyjobcore/build/libs/joyjobcore-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/static/logo_joyjob.png
        at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:217) ~[spring-core-5.3.23.jar!/:5.3.23]
        at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:180) ~[spring-core-5.3.23.jar!/:5.3.23]
        at com.kil.joyjobcore.service.CardUploader.onInit(CardUploader.java:21) ~[classes!/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.23.jar!/:5.3.23]
        at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.23.jar!/:5.3.23]
        at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.23.jar!/:5.3.23]
        ... 26 common frames omitted

As far as I see, java adds "!" symbol to the actual case, why does it happen?

Here is my build.gradle. Maybe it is misconfigured?

plugins {
    id 'org.springframework.boot' version '2.7.4'
    id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    id 'java'
    id "org.openapi.generator" version "6.2.1"
}

group = 'com.kil'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // https://mvnrepository.com/artifact/net.sf.jasperreports/jasperreports
    implementation group: 'net.sf.jasperreports', name: 'jasperreports', version: '6.17.0'

    // https://mvnrepository.com/artifact/com.lowagie/itext
    implementation group: 'com.lowagie', name: 'itext', version: '4.2.2', ext: 'pom'

    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'

    //todo remove from openapigeneration and hier
    implementation 'org.openapitools:jackson-databind-nullable:0.2.4'
    implementation 'io.swagger:swagger-annotations:1.6.8'

    implementation 'com.talanlabs:avatar-generator-8bit:1.1.0'
    implementation 'org.flywaydb:flyway-core'
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'
    implementation 'org.mapstruct:mapstruct:1.5.3.Final'
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.4'
    implementation 'net.ttddyy:datasource-proxy:1.8.1'

    implementation 'com.google.apis:google-api-services-forms:v1-rev20220908-2.0.0'
    implementation 'com.google.apis:google-api-services-drive:v3-rev20221219-2.0.0'
    implementation 'com.google.api-client:google-api-client:2.2.0'
    implementation 'com.google.auth:google-auth-library-oauth2-http:1.15.0'

    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'org.postgresql:postgresql'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

String generatedSourcesDir = "$buildDir/generated/openapi"

openApiGenerate {
    generatorName = "java"
    inputSpec = "$rootDir/src/main/resources/specs/cvGenerator.yaml".toString()
    outputDir = generatedSourcesDir
//    outputDir = "$buildDir/generated".toString()
    groupId = 'com.kil.joyjob'
    apiPackage = "com.kil.joyjob.api"
    invokerPackage = "com.kil.joyjob.invoker"
    modelPackage = "com.kil.joyjob.model"
    configOptions = [
            annotationLibrary: "none",
            booleanGetterPrefix: "is",
            library: "webclient",
            dateLibrary: "java8"
    ]
}

compileJava {
    options.compilerArgs += [
            '-Amapstruct.defaultComponentModel=spring',
            '-Amapstruct.unmappedTargetPolicy=ERROR'
    ]
}

sourceSets {
    getByName("main") {
        java {
            srcDir("$generatedSourcesDir/src/main/java")
        }
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

Bogdan Lashkov
  • 319
  • 3
  • 17
  • It means whatever comes after the `!` is inside the JAR file. See https://stackoverflow.com/a/9530575 though the `!` in `classes!` looks incorrect. – LJ replica Mar 20 '23 at 04:03

1 Answers1

0

Is this a webflux (reactive) application or a web (mvc) application? I see that you have both

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'

You really should remove the dependency that you are NOT using. It makes a difference and I would be surprised if you don't run into other issues.

If you use the webflux dependency, add a resourceHandler bean

@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry
        .addResourceHandler("/**")
        .addResourceLocations("classpath:/static/")
        .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
   }
}

If this is a web (mvc) applicaiton, add

@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry
        .addResourceHandler("/**")
        .addResourceLocations("classpath:/static/")
        .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
   }
}

Spring serves all static content under / (the root part of the request). In the above, we are saying take the handler that normally serves up content from /** and also add the /static/ directory from the jar. I.e. src/main/resources/static which is what from your error message it looks like you're trying to do.

I think (I don't use this method personally) you can achieve the same thing via the application.properties, via the spring.mvc.static-path-pattern=/static/** or spring.webflux.static-path-pattern=/static/** accordingly.

It probably works in intellij because it has made its own classpath -- which is different from a jar. I suspect that if you ran ./gradlew bootRun from the command line it would not work, as that is using the same classpath as the jar file.

Pickled Brain
  • 313
  • 1
  • 6