wimdeblauwe / htmx-spring-boot

Spring Boot and Thymeleaf helpers for working with htmx
Apache License 2.0
423 stars 41 forks source link

Ver. 3.0.0 breaks with spring-boot 3.1.2 #76

Closed jenschurchill closed 9 months ago

jenschurchill commented 9 months ago

org.springframework.boot version '3.1.2'.

    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-undertow'
    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation group: 'io.github.wimdeblauwe', name: 'htmx-spring-boot', version: '3.0.0'
    implementation group: 'io.github.wimdeblauwe', name: 'htmx-spring-boot-thymeleaf', version: '3.0.0'

Results in...

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: Factory method 'requestMappingHandlerMapping' threw exception with message: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}

...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1824) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1383) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:2014) ~[spring-beans-6.0.11.jar:6.0.11]
    at io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxMvcAutoConfiguration.addInterceptors(HtmxMvcAutoConfiguration.java:45) ~[htmx-spring-boot-3.0.0.jar:3.0.0]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.addInterceptors(WebMvcConfigurerComposite.java:88) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration.addInterceptors(DelegatingWebMvcConfiguration.java:83) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getInterceptors(WebMvcConfigurationSupport.java:358) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.initHandlerMapping(WebMvcConfigurationSupport.java:511) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerMapping(WebMvcConfigurationSupport.java:312) ~[spring-webmvc-6.0.11.jar:6.0.11]
    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.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.11.jar:6.0.11]
    ... 20 common frames omitted

Seems to work fine with...

implementation group: 'io.github.wimdeblauwe', name: 'htmx-spring-boot-thymeleaf', version: '2.0.0'
dsyer commented 9 months ago

Works for me. Maybe a complete sample would help.

BTW it looks a bit odd to have tomcat and undertow on the classpath at the same time, but I don't think that's the problem here, just something you might need to fix later.

jenschurchill commented 9 months ago

? - I don't see a reference to tomcat in the exception...

Using Undertow instead of Tomcat; The documented way as far as I know, is...

configurations {
    implementation {
        exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-undertow'
    implementation 'org.springframework.boot:spring-boot-starter-web'
...
}

However... Removing that and using tomcat instead, and making this change:

//import io.github.wimdeblauwe.hsbt.mvc.HtmxResponse;
//import io.github.wimdeblauwe.hsbt.mvc.HxRequest;
import io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxResponse;
import io.github.wimdeblauwe.htmx.spring.boot.mvc.HxRequest;

I get this exception (entire log attached)...

Connected to the target VM, address: '127.0.0.1:39847', transport: 'socket'
2023-09-19T11:44:35.947+02:00  INFO 82248 --- [           main] c.s.I.InstallClientApplication           : Starting InstallClientApplication using Java 17.0.8.1 with PID 82248 (./build/classes/java/main started by user in .)
2023-09-19T11:44:35.955+02:00 DEBUG 82248 --- [           main] c.s.I.InstallClientApplication           : Running with Spring Boot v3.1.2, Spring v6.0.11
2023-09-19T11:44:35.959+02:00  INFO 82248 --- [           main] c.s.I.InstallClientApplication           : The following 1 profile is active: "dev"
2023-09-19T11:44:41.121+02:00  INFO 82248 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 3000 (http)
2023-09-19T11:44:41.146+02:00  INFO 82248 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-09-19T11:44:41.146+02:00  INFO 82248 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.11]
2023-09-19T11:44:41.376+02:00  INFO 82248 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-09-19T11:44:41.384+02:00  INFO 82248 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 5202 ms
2023-09-19T11:44:41.694+02:00  WARN 82248 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: Factory method 'requestMappingHandlerMapping' threw exception with message: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}
2023-09-19T11:44:41.701+02:00  INFO 82248 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-09-19T11:44:41.815+02:00  INFO 82248 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-09-19T11:44:41.881+02:00 ERROR 82248 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.class]: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: Factory method 'requestMappingHandlerMapping' threw exception with message: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:659) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:647) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:942) ~[spring-context-6.0.11.jar:6.0.11]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[spring-context-6.0.11.jar:6.0.11]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.2.jar:3.1.2]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-3.1.2.jar:3.1.2]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) ~[spring-boot-3.1.2.jar:3.1.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-3.1.2.jar:3.1.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.2.jar:3.1.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.2.jar:3.1.2]
    at com.sitemule.InstallClient.InstallClientApplication.main(InstallClientApplication.java:37) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping]: Factory method 'requestMappingHandlerMapping' threw exception with message: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:171) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655) ~[spring-beans-6.0.11.jar:6.0.11]
    ... 19 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.web.servlet.ViewResolver' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("viewResolver")}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1824) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1383) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:2014) ~[spring-beans-6.0.11.jar:6.0.11]
    at io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxMvcAutoConfiguration.addInterceptors(HtmxMvcAutoConfiguration.java:45) ~[htmx-spring-boot-3.0.0.jar:3.0.0]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite.addInterceptors(WebMvcConfigurerComposite.java:88) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration.addInterceptors(DelegatingWebMvcConfiguration.java:83) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getInterceptors(WebMvcConfigurationSupport.java:358) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.initHandlerMapping(WebMvcConfigurationSupport.java:511) ~[spring-webmvc-6.0.11.jar:6.0.11]
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.requestMappingHandlerMapping(WebMvcConfigurationSupport.java:312) ~[spring-webmvc-6.0.11.jar:6.0.11]
    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.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.11.jar:6.0.11]
    ... 20 common frames omitted

Disconnected from the target VM, address: '127.0.0.1:39847', transport: 'socket'

Process finished with exit code 1

This is a part of a larger project, I will create a simple test case, a 1. See if the exception persists, 2. Upload it if it does. Thank you for your response.

jenschurchill commented 9 months ago

BTW. I would just note, that I don't have a configuration class or anything that manipulates viewresolver, or templateengine, or anything like that...

But I do have a application-dev.yaml profile, that does this...

spring:
  thymeleaf: # Thymeleaf
    cache: false
    mode: HTML
    encoding: UTF-8
    prefix: file:src/main/resources/templates/
  web:
    resources: # Static resources
      static-locations: file:src/main/resources/static/
    cache:
      period: 0

However; If I run without that, I get the same exception.

dsyer commented 9 months ago

If you can provide a minimal reproducer I can take a look. It seems there must be something else unique about your project because mine works.

jenschurchill commented 9 months ago

Agreed, I will do that.

jenschurchill commented 9 months ago

This breaks it...

src/main/java/com/example/demo/configurations/WebResourceConfiguration.java

package com.example.demo.configurations;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebResourceConfiguration implements WebMvcConfigurer {
}

I can easily solve that, since that class is not needed, and the overrides I have in there can be set in the properties file instead, and then the viewResolver exception disappears, and things seem to work.

As mentioned, this exception is not thrown, when using 2.0.0 and having a @EnableWebMvc annotated configuration class.

dsyer commented 9 months ago

I'm surprised it ever worked because if you @EnableWebMvc then you disable all the autoconfiguration from Spring Boot for Web MVC. If you omit that annotation that should work as well.

jenschurchill commented 9 months ago

The real app (has the configuration needed in the class), and attached demo (works with 2.0.0) works fine And kept running fine after adding htmx-spring-boot 2.0.0.

It only broke when I added 3.0.0, but as stated, I have now moved the config I have in the class, and into yaml, and deleted the class, and now it works again. So as far as I am concerned, my issue can be closed.

But the fact remains, that spring-boot can start with an empty @EnableWebMvc class, it can start with an empty @EnableWebMvc class and 2.0.0, but it breaks when upgrading to 3.0.0.

I have attached the minimal demo, with 2.0.0 and an empty @EnableWebMvc activated, to show that it works. Switch to 3.0.0 (swap comments in build.gradle and TestController), and it breaks.

demo.zip

jenschurchill commented 9 months ago

BTW. This is by no means a negative critic, I thank you and your peers for the work and attention. As stated, my issue is fixed, and if anyone else comes across this, they can fix it using the "workaround" from this thread.

If you decide, that this is not an issue to be fixed, I have no qualms with that, and refer to line no. 1 :)

dsyer commented 9 months ago

I think we should close it as "won't fix" - in version 2.0 you would get a ThymeleafViewResolver but no global ViewResolver provided by Spring Boot MVC autoconfiguration - it's that one you need for this library to work, so 3.0.0 is working as designed.

jenschurchill commented 9 months ago

Agreed, and have a great day sir!