Custom Bean not loaded in Spring Boot Cloud AutoConfiguration Class - spring-boot

I want to provide a specific Bean, so that this Bean overrides the Bean in a Spring Cloud AutoConfiguration class.
First try
Therefore I've created a Configuration class:
#Configuration
public class MyLocalConfig {
#Bean
public ApiClient apiClient() throws IOException {
return ClientBuilder.standard(false).build();
}
}
Prioritizing by using #Primary or #Order annotations does not help.
Second try (EDIT)
I also tried to use an AutoConfiguration. But even the #AutoConfigureBefore Annotation is ignored.
#Configuration
#AutoConfigureBefore(KubernetesClientAutoConfiguration.class)
public class LocalKubeAutoConfiguration {
#Bean
public ApiClient apiClient() throws IOException {
return ClientBuilder.standard(false).build();
}
}
My Configuration class Beans are always instantiated after the Beans in KubernetesClientAutoConfiguration class.
Therefore the AutoConfiguration class does not use my Bean.
The doc says: At any point, you can start to define your own configuration to replace specific parts of the auto-configuration.
Questions:
What's my mistake?
How can I prioritize the configurations?
Here's my other code:
Main Class
#SpringBootApplication
public class SpringBootAdminApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootAdminApp.class, args);
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>testme</artifactId>
<version>1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
</project>

In the main class
add #SpringBootApplication(scanBasePackages = ....)
so the customized package will be scanned
this annotation just tell spring where to search.
https://www.baeldung.com/spring-component-scanning

Spring Boot Cloud uses Bootstrap Configuration to load the configurations.
By default, bootstrap properties (not bootstrap.properties but
properties that are loaded during the bootstrap phase) are added with
high precedence, so they cannot be overridden by local configuration.
Therefore I have to add a BootstrapConfiguration:
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.demo.LocalKubeConfig
The Bean in the BootstrapConfiguration will be loaded before KubernetesClientAutoConfiguration.

Related

Using Spring Actuator with Spring CommandLineRunner application

I am developing a TCP socket server application in java using Spring Boot. Eventhough, I am not using the spring-boot-starter-web dependency, I still would like to benefit from the actuator endpoint to monitor the application using external monitoring tool like prometheus.
A minimal equivalent application would be:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
DemoApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringBootConsoleApplication.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
#Component
public class SpringBootConsoleApplication
implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(SpringBootConsoleApplication.class);
#Override
public void run(String... args) throws InterruptedException {
LOG.info("EXECUTING : command line runner");
int i = 0;
while(true) {
LOG.info("iteration: {}", i++);
Thread.sleep(1000);
}
}
}
application.yml
spring:
jmx:
enabled: true
management:
endpoints:
jmx:
unique-names: true
exposure:
include: '*'
web:
exposure:
include: '*'
health:
show-details: always
Using jconsole, I can get the actuator data via JMX, but how to get them from the actuator rest endpoint?
Are there some missing (or unnecessary) config in the application.yml or missing dependencies?
I have read the other posts:
how could access actuator endpoints in non-web application
Spring boot actuator for commandline runner application
without managing to make the rest actuator working on my application.
As you have a command line application there is no web server included and therefore also no Actuator REST endpoints.

The entity field value is updated after exception throwing and transaction rollback

I have developed a test project to reproduce this issue.
This is a project structure:
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>value-updated-after-fail-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
Persone.java file:
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#RequiredArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
#ToString
#FieldDefaults(level = AccessLevel.PRIVATE)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
#NonNull
String name;
}
PersonRepository.java file:
#Repository
public interface PersonRepository extends JpaRepository<Person, Long> {}
PersonService.java file:
#Component
public class PersonService {
private final PersonRepository repository;
public PersonService(PersonRepository repository) {
this.repository = repository;
}
#Transactional
public Person create(String name) {
return repository.save(new Person(name));
}
#Transactional
public Person save(Person person) {
if(StringUtils.isBlank(person.getName())) {
throw new RuntimeException();
}
Person personFromDB = getById(person.getId());
personFromDB.setName(person.getName());
return repository.save(personFromDB);
}
#Transactional
public Person getById(Long id) {
return repository.findById(id)
.orElseThrow(NullPointerException::new);
}
#Transactional
public void deleteAll() {
repository.deleteAll();
}
}
application.properties file:
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
TestApplicationConfiguration.java file
#SpringBootConfiguration
#EnableAutoConfiguration
#EnableJpaRepositories
#EntityScan("net.example.model")
#ComponentScan(basePackages = "net.example")
public class TestApplicationConfiguration {}
PersonServiceTest.java file:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
class PersonServiceTest {
#Autowired
private PersonService service;
#AfterEach
void tearDownEach() {
service.deleteAll();
}
#Test
void rename() {
String expected = "name";
Person person = service.create(expected);
Person personFromDB = service.getById(person.getId());
personFromDB.setName("");
assertThrows(RuntimeException.class, () -> service.save(personFromDB));
assertEquals(expected, service.getById(personFromDB.getId()).getName());
}
}
The issue: Last assertion fails
org.opentest4j.AssertionFailedError:
Expected :name
Actual :
What I already tried to fix this?
I tried to remove the #Transactional annotation for the PersonService#getById method to avoid getting the entity from the cache. - This didn't fix the issue
I tried to add spring.cache.type=none to the application.properties file to disable the cache. - This didn't fix the issue
Why do I think it's the cache?
When I debugged this, I found that the PersonService#getById() method doesn't return actual data, but the method returns a cached object with a changed title.
The database isn't changed after calling the PersonService#save method because it throws an exception
Perhaps I'm not developing the tests correctly.
Maybe I should change the method of saving changed data.
Please share best practices and articles to better understand how to update data and how to properly configure and write tests for Spring Boot applications.
Thanks a lot for the comment from Andrey B. Panfilov.
I investigated the #Transactional and the first level cache of Hibernate.
Indeed, each test method call in a class annotated with the #DataJpaTest annotation creates, runs, and rollbacks a transaction.
Each transaction creates and closes the Hibernate session. As we know, the first level cache exists until the session closes. That's why it's also called session cache.
You can see the evidence in the following screenshots:
In the first screenshot, you can see that SpringExtension, which is defined in the #DataJpaTest annotation, opens a new session before each test is called.
In the second screenshot, you can see that SpringExtension closes the session after each test is called.
I decided to override the default transaction propagation:
#Transactional(propagation = Propagation.NEVER) it doesn't create a transaction when the method is called and throw an exception if the method is called in an existing transaction
Links that helped me:
Data Access
Transaction Propagation and Isolation in Spring #Transactional
Transaction Propagation with illustrations
Hibernate Caching - First Level Cach

Why does Spring Boot app start when two #Bean methods are present for restTemplate

I have this dependency:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
And I have this config:
#Configuration
public class AppConfig {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(ofMillis(3000))
.setReadTimeout(ofMillis(3000))
.build();
}
}
I wonder why Spring does not fail on start. It never inits first restTemplate but inits second one. I would expect Spring to fail.
This is a valid case, you are defining two bean definitions with same name of same bean, then the last bean defined will replace then first one bean defined.
If you want to an exception is thrown when two beans are defined by same name then you can configure by using setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) method from DefaultListableBeanFactory is who overrides whether it should be allowed to override bean definitions by registering a different definition with the same name.

Spring Boot/Tomcat, "Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean"

I know there are others questions on this, and I have checked them, but none solves my issue.
Very simple POM, ready:ing my app for use with an embedded web server, simply so I can start it up...
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
Very easy Boot class:
#SpringBootApplication
#RestController
public class SpringBootApp {
public static void main(String[] args) {
SpringApplication.run(SpringApplication.class, args);
}
#RequestMapping(value = "/")
public String hello() {
return "Hello World from Tomcat";
}
}
Tried:
Extend the SpringBootServletInitializer class.
Update spring-boot-starter-parent to the latest (2.1.3).
Setting spring.profiles.active=default in application.properties.
Manually inject TomcatWebServerFactory (ugh...) in #Configuration
class.
Run mvn dependency:purge-local-repository and removed /repository dir
from my .m2 dir, fetching all again with mvn clean install -U.
ETC... I do not see why it is not working, I though Spring Boot was supposed to be easy.
Unable to start ServletWebServerApplicationContext due to missing
ServletWebServerFactory bean.
First argument for SpringApplication.run() is expected to be the primary configuration class of your application. In your case it is SpringBootApp.class and not SpringApplication.class. Following would be the correct configuration for your Spring Boot application.
public static void main(String[] args)
{
SpringApplication.run(SpringBootApp.class, args);
}

How to use #NotNull with spring boot?

I have this dependency:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
Which have it's version managed by
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
And I have this piece:
import javax.validation.constraints.NotNull;
//other imports ommited
#Component
#ConfigurationProperties(prefix = "collector")
public class CollectorProperties {
#NotNull
private String urlCDI;
//getters and setters
}
And my SpringApplication.run class has this pice:
#SpringBootApplication
#EnableConfigurationProperties
#ComponentScan({ "otherPackages", "packageWhereCollectorPropertiesIs" })
When I have my application.properties with this line
collector.urlCDI=https://www.cetip.com.br/Home
It works as it was supposed inside other spring beans:
//#Component class variables:
#Autowired
private CollectorProperties props;
//inside some method
URL url = new URL(props.getUrlCDI());
But when I remove it or alter property key I get lots of NPE instead of validations errors. What I'm doing wrong? Doesn't hibernate-validator contains an implementation of javax.validation.constraints.NotNull interface?
Add ยด#Validated' annotation to your properties class

Resources