How does Spring boot put Thymeleaf view resolver into its bean container - spring

It is a spring boot project and the web page is rendered by Thymeleaf. When I put spring-boot-starter-thymeleaf in the pom.xml and start the applicaiton, it is trying to find all the beans which implements ViewResolver in its container. And you see here it find thymeleafViewResolver.
I am just curious that when and how does Spring boot put this ThymeleafViewResolver class into its bean container?

It is due to SpringBoot auto-configuration feature which will automatically create a bean dynamically based on different conditions such as if a library can be found from the class paths, or if developers already define a bean of certain type etc...
If you turn on the debug mode by putting debug=true in the application.properties , it will print out a report during application startup saying which beans are auto created due to which conditions.
In the example of the spring-boot-starter-thymeleaf , you can find the following from the report :
ThymeleafAutoConfiguration.ThymeleafWebMvcConfiguration.ThymeleafViewResolverConfiguration#thymeleafViewResolver matched:
- #ConditionalOnMissingBean (names: thymeleafViewResolver; SearchStrategy: all) did not find any beans (OnBeanCondition)
And by tracing the source codes of ThymeleafViewResolverConfiguration :
#Bean
#ConditionalOnMissingBean(name = "thymeleafViewResolver")
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(this.templateEngine);
resolver.setCharacterEncoding(this.properties.getEncoding().name());
//.......
return resolver;
}
You could find out thymeleafViewResolver is in a type of ThymeleafViewResolver and #ConditionalOnMissingBean here means that this bean will only be created if there is no bean of the ThymeleafViewResolver type is defined yet.

Related

Disabling a DatabaseInitializerDetector in Spring Boot

I am defining my own Liquibase auto configuration to manage multitenancy, which involves a prototype SpringLiquibase bean:
#Bean
#Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
SpringLiquibase tenantCreatedLiquibase(String tenantId) {
// construct a SpringLiquibase instance
}
The tenantCreatedLiquibase prototype bean is instantiated at runtime via an ObjectProvider<SpringLiquibase>.
But LiquibaseDatabaseInitializerDetector creates a dependency from jdbcTemplate on my tenantCreatedLiquibase bean. The ApplicationContext fails to load because the tenantCreatedLiquibase prototype bean requires a tenantId argument.
How can I disable the LiquibaseDatabaseInitializerDetector? Or otherwise work around this problem?
One workaround is to exclude any auto configuration that references DatabaseInitializationDependencyConfigurer. In my case, that meant excluding JdbcTemplateAutoConfiguration and SqlInitializationAutoConfiguration (in addition to LiquibaseAutoConfiguration, which is excluded because I provide my own custom replacement).
I'd still prefer direct control over the registration of DatabaseInitializerDetectors, but I'm not sure that's possible.

Override thymeleaf templateEngine configuration stops working in Spring Boot 2.1.8

I have a running Spring Boot 2.0.1 application. After upgrading to Spring Boot 2.1.8, I get the following error during startup:
The bean 'templateEngine', defined in class path resource [org/springframework/boot/autoconfigure/thymeleaf/ThymeleafAutoConfiguration$ThymeleafDefaultConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [my/package/ThymeleafConfig.class] and overriding is disabled.
I don't use AutoConfiguration, so I cannot understand why the ThymeleafAutoConfiguration class kicks in.
Also I don't want the autoconfigured templateEngine to be used, but my own definition of templateEngine. So enabling bean-overriding is not an option here. (Remember: It wants to override my correctly registered bean with the default version, which is not what I want)
Here is my bean definition (overriding templateEngine):
public class MyThymeleafConfig {
#Bean
public ClassLoaderTemplateResolver templateResolver() {
final ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
...
return resolver;
}
#Bean
public ISpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
engine.setTemplateResolver(templateResolver());
return engine;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
}
I know that Spring Boot has altered something in the bean registration process, according to the 2.1. release notes:
Bean overriding has been disabled by default to prevent a bean being accidentally overridden. If you are relying on overriding, you will need to set spring.main.allow-bean-definition-overriding to true.
But I don't think this applies here. In fact I DON'T want my bean to be overridden. If I set the above parameter, the app comes up, but uses the default thymeleaf templateEngine.
And finally: The templateEngine bean in ThymeleafDefaultConfiguration has a #ConditionalOnMissingBean annotation. So if a bean is already there, imho it just should leave it and not try to override.
I think, I am missing something vital here. Any thoughts will be welcome. Thanks in advance.
Patrick
Switching again back to my former running application and do deep debugging there shed some light in this matter.
What I found: I never used my own templateEngine bean that I defined in the config (see code in question). Both versions of the app have at runtime: a viewResolver that contains a default templateEngine (from the ThymeleafAutoConfiguration) that contains two templateResolvers (a default one and my own, defined in my config). Removing the templateEngine bean definition in my config made the app start up, this is the solution to the problem. So the disabled overriding in Spring Boot >2.1.0 indeed led to the described behaviour, because my (useless) bean definition was not overridden automatically.
Still there are some questions left:
- Obviously my app runs fine with the default templateEngine. But what if I really need to override it? right now I don't have the possibility, because defining my own will fail in starting up and if I allow overriding, the default one is used. Anyone having an explanation to this, feel free to add it in a comment.
- What is the use of the #ConditionalOnMissingBean annotation in the ThymeleafAutoConfiguration when it's obviously ignored, resulting in the error described in the question. Here too, I'm willing and eager to learn. so please share your thoughts in the comments.
Thanks, Patrick

Spring Boot disable creation of JpaProperties configuration in WAR deployment

I'm trying to setup a Spring Boot application that uses EclipseLink instead of Hibernate. The project has a similar structure to this example project:
https://github.com/spring-projects/spring-data-examples/blob/master/jpa/eclipselink/src/main/java/example/springdata/jpa/eclipselink/Application.java
The main difference is that our application needs to be packaged as a WAR... so the main Application.java extends SpringBootServletInitializer instead of the JpaBaseConfiguration shown in the example (we have another bean with #Configuration that extends JpaBaseConfiguration).
When the app starts it encounters a ClassNotFoundException for the a naming strategy that comes form Hibernate:
Error creating bean with name 'spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties': Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/boot/model/naming/ImplicitNamingStrategy
It seems that this only happens when the application extends from SpringBootServletInitializer.
Turning on debug shows this:
JpaBaseConfiguration.JpaWebConfiguration:
Did not match:
- #ConditionalOnProperty (spring.jpa.open-in-view=true) found different value in property 'open-in-view' (OnPropertyCondition)
Matched:
- #ConditionalOnClass found required class 'org.springframework.web.servlet.config.annotation.WebMvcConfigurer'; #ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
- found ConfigurableWebEnvironment (OnWebApplicationCondition)
Is there any way to exclude the JpaWebConfiguration, but still have the application extend from SpringBootServletInitializer?
UPDATE: After debugging this further I don't believe the JpaWebConfiguration is the issue as that is in the unmatched section of the debug, but something is still causing the creation of a JpaProperties bean and hitting the ClassNotFoundException.

How to get Spring Boot to create a bean validator before creating all the other beans

I'm trying to add a LocalValidatorFactoryBean to an existing Spring Boot web application.
No matter what I have tried (listed in a moment) it only creates the validator after most other beans (verified with both logging and breakpoints), so they never get validated.
Tangentially, I have hibernate-validator on the classpath and am attempting to use javax.validation.constraints on my #Component properties.
Application class has #Configuration, #EnableAutoConfiguration and #ComponentScan({"my.package.**"}).
Adding an application.xml with the bean <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
Adding the above bean to validator.xml and adding #ImportResource("validator.xml")
Adding a new #Bean to the Application class. public Validator validator() { return new LocalValidatorFactoryBean(); }
Adding #Order(Ordered.HIGHEST_PRECEDENCE) to the above #Bean
Adding a BeanValidator #Component to the scanned packages.
And adding #Order to it.
In all cases, the validator is loaded only after FilterRegistrationBean has finished logging its business, but the beans I want to validate have already been created and used in setting up data connections and security for example.
Its been a few years since I've used Spring, but I don't remember these problems when defining everything in an application.xml. Is this just something that spring-boot doesn't support and I should move back to traditional Spring application config?
How can I get it to validate all my beans?
I forgot to make a BeanValidationPostProcessor.

Implicit dependency between BeanNameAutoProxyCreator and imported configuration

At my company, we're working on an aspect-oriented trace interceptor, similar to DebugInterceptor. We're configuring a CustomizableTraceInterceptor and using a BeanNameAutoProxyCreator to auto-proxy beans for AOP.
The problem we're facing is that, when we introduce the BeanNameAutoProxyCreator in the configuration:
#Configuration
#Import(BConfig.class)
#EnableAspectJAutoProxy
public class AConfig {
#Bean
public static BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setInterceptorNames(new String[] {DEBUG_INTERCEPTOR_NAME});
beanNameAutoProxyCreator.setBeanNames(new String[] {BEANS_NAMES_EXPRESSION});
return beanNameAutoProxyCreator;
}
}
We get a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [X], where X is a Resteasy Proxy. This Resteasy Proxy is declared in BConfig.
Now, if I move the Resteasy Proxy bean configuration up to AConfig, this issue is solved, and #DependsOn solves the issue too.
My questions are 3: when is Spring able to resolve dependencies between beans? Why using a BeanNameAutoProxyCreator changes this behavior? What is the recommended way of solving this issue (BeanPostProcessor, #DependsOn, etc.).
The static BeanNameAutoProxyCreator depends on a normal bean (probably due to the BEANS_NAMES_EXPRESSION). Because it is static it is loaded/bootstrapped before any other beans and especially before the bean processing #Import. So basically when analyzing which beans to process, BConfig hasn't yet been loaded. That is why it works when you move the bean to AConfig or at a depends-on for this bean.
I would probably revert the use of a BeanNameAutoProxyCreator and rely on the #EnableAspectJAutoProxy together with an aspect using the bean pointcut to attach the desired interceptor.
There is also another risk in introducing the BeanNameAutoProxyCreator next to #EnableAspectJAutoProxy it can lead to a proxy of a proxy being created, due to 2 different AOP strategies/mechanisms.

Resources