Override thymeleaf templateEngine configuration stops working in Spring Boot 2.1.8 - spring-boot

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

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.

Split jackson configuration into separate properties

I'm using Spring Boot 2.2.5.RELEASE and would like to split my application.properties into separate files. There are already similar questions on StackOverflow but none of them seem to work for configuring Jackson.
My current non working solution is the following:
root/
- application.properties (without Jackson configuration)
- jackson-configuration.properties (includes Jackson configuration)
Jackson configuration class:
#Configuration
#PropertySource("/jackson-configuration.properties")
public class JacksonConfiguration {
}
Please note, I've tried different ways to specify the path including:
"/jackson-configuration.properties"
"jackson-configuration.properties"
"classpath:/jackson-configuration.properties"
"classpath:jackson-configuration.properties"
Spring Boot does not seem to use the configuration. If I copy it over into the application.properties - it works.
Content of jackson-configuration.properties:
spring.jackson.property-naming-strategy=SNAKE_CASE
spring.jackson.mapper.sort-properties-alphabetically=true
spring.jackson.deserialization.fail-on-unknown-properties=true
spring.jackson.parser.strict-duplicate-detection=true
spring.jackson.time-zone=Europe/Zurich
My application is annotated with #SpringBootApplication , so it should scan for additional properties.
/edit
I just realized the problem is the testing, not the productive code itself. If I start the application it works. What doess not work is testing with #JsonTest. I can fix this problem by adding the following line to my tests #ContextConfiguration(classes = {JacksonConfiguration.class}). But in turn, this causes the annotation #JsonComponent to stop working but only for the #JsonTest annotated classes.
See the documentation here. Here is an excerpt from the documentation
In order to resolve ${...} placeholders in definitions or
#Value annotations using properties from a PropertySource, you must
ensure that an appropriate embedded value resolver is registered in
the BeanFactory used by the ApplicationContext. This happens
automatically when using in XML. When
using #Configuration classes this can be achieved by explicitly
registering a PropertySourcesPlaceholderConfigurer via a static #Bean
method.
You need to create a bean like this
#Bean
public static PropertySourcesPlaceholderConfigurer devPropertyPlaceholderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocations(new PathMatchingResourcePatternResolver().getResources("file:pathtToFile"));
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}

Spring Bean Overriding My ObjectMapper Configuration

Had an issue today where a dependency wired with a configuration kept winning when it came to some ObjectMapper configuration I was trying to do. I added the following to my Spring Boot application.
#Configuration
public class CustomObjectMapperConfig {
#Autowired
public void configureObjectMapper(ObjectMapper objectMapper) {
objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
}
I'd actually rather not use timestamps, but for backwards compatibility I had to do this. I put a debug point at the line in question here and it got hit, but I kept getting dates returned to me in ISO format, which is the default for most or our projects.
I finally figured out that a company dependency that I was bringing in had the following:
#Configuration
public class ObjectMapperPropertiesConfig {
/**
* #deprecated Spring boot jackson properties should be used instead.
*/
#Deprecated
#Autowired
public void setObjectMapper(
final ObjectMapper objectMapper) {
objectMapper
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, !ignoreUnknown);
}
...
This class was getting loaded after my class, so it alwasy won with regards to the WRITE_DATES_AS_TIMESTAMPS feature.
To fix this I ended up adding a #DependsOn annotation. This forced the other bean to load first giving my bean the chance to win the configuration war. Finding the correct name of the bean was difficult. It ended up looking something like this:
#DependsOn("path.to.object.ObjectMapperPropertiesConfig")
Note: The #Deprecated annotation here tell me that this code will be going away in future versions in favor of spring boot properties anyway. For now, my change will work.

In spring should you create beans for existing classes?

I'm creating a spring application and I want to use an unaltered instance of SimpleUrlAuthenticationFailureHandler for use in my WebSecurityConfigurerAdapter class
.failureHandler(authFailureHandler)
Is it acceptable to use new SimpleUrlAuthenticationFailureHandler in this instance or should I create a bean in my appconfig to return the class? I feel like I should do it via Bean but I can't explain to my colleague exactly why this is.
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
vs
//in appconfig
#Bean
public FailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler();
}
//back in the class
#Autowired
FailureHandler failureHandler
...
...
.failureHandler(failureHandler)
It may be acceptable, but using a bean enables: declaring it once and injecting it into several clients, hides the decision of which implementation is being used from the web security configuration, and allows for mocking in tests. Whether these concerns apply to your application is up to debate with your colleagues.

Resolving bean conflicts in third-party packages

I have an application, let's call it MyApp. An example of the build.gradle looks like this:
compile group: 'com.example', name: 'library'
compile .. spring-heatoas ..
Now, the way this library works is that it expects the host to give it a bean of type ObjectMapper. It by itself does not define an ObjectMapper as the configuration of the same is completely open to the host library. So basically, in the host MyApp I have this config:
#Configuration
public class SpringConfig {
#Bean
public ObjectMapper objectMapper() { ... }
}
Everything is fine and working, till I had the spring-hateos dependency. spring-hateos defines it's own ObjectMapper which goes by the name _hal_objectMapper. So, after adding it, I get a conflict between the two beans. I tried:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public ObjectMapper objectMapper() { ... }
Not only does this not work, but even if it did, I guess it has the potential to break functionality as the host application can no longer configure the ObjectMapper. A straightforward solution obviously is to make the library accept a named-bean and then declare it in my host application with a #Qualifier annotation. But getting the library to change (and there are a couple of libraries like that) company-wide is going to be a major pain. While we are considering that solution, is there a way to solve this?
The annotation #Primary advises spring to use that annotated bean if many beans of the same type are available. This could resolve your issue.
Your code should look like this :
#Configuration
public class SpringConfig {
#Bean
#Primary
public ObjectMapper objectMapper() { ... }
}
The problem may be now, that spring-hateos also uses this objectMapper, which is not configured as expected.
A complete solution could be, to create a child spring context.
Only for your ObjectMapper and the 3rd party bean, so that the objectMapper is not visible for the rest of you application.
How this is done depends on how you instantiate the 3rd party bean, and also where it will be used. With the provided information I cannot describe that more detailed.
You can find a good entry here :
modularizing-configurations
(see chapter 'Nesting #Configuration classes' or search for 'child')

Resources