How to split Spring Conditional configuration? - java

My application uses its own Spring configuration Condition to provide beans according to setup, but because of volumes, I expect to split long #Configuration instead of adding #Conditional to tens of beans.
At the moment, my main security configuration looks like
#Configuration #EnableOAuth2Client #EnableWebSecurity
#Import(OptionalAuthenticationConfiguration.class)
public class WebshopSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired(required = false)
private OptionalAuthenticationConfiguration auth;
And here is conditional configuration
#Configuration
#Conditional(MyCondition.class)
public class OptionalAuthenticationConfiguration {
[...]
Because of #Conditional on optional configuration, #Import annotation fails with
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'webshopSecurityConfig': Injection of autowired dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not autowire field: private OptionalAuthenticationConfiguration
WebshopSecurityConfig.auth; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [OptionalAuthenticationConfiguration] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Even if bean attribute WebshopSecurityConfig.auth has required = false.
Is there a way to achieve such conditional configuration splitting in Spring?

Make OptionalAuthenticationConfiguration an interface with two implementations.
An actual implementation with #Conditional(MyCondition.class) and an empty implementation with #ConditionalOnMissingBean.
This way when MyCondition doesn't allow the configuration to be created, it will get replaced by a placeholder empty implementation.

What I described in this question is expected to work... as far as "MyCondition" properly returns true, and my trouble was there.
Project context was far more complex than shown here: "OptionalAuthenticationConfiguration" is in fact a Spring Security SAML setup which expects many beans from "WebSecurityConfigurerAdapter".
As a result, my code ended with circular "#Import" between these two configurations. I had to move 5 beans from "OptionalAuthenticationConfiguration" to "WebshopSecurityConfig" and make each of them "#Conditional".

Related

Spring, problem with #Value in bean constructor with prototype scope

cfg c= context.getBean(cfg.class);
First time it has to be work, but second time appear error:
No qualifying bean of type 'java.lang.String' available: expected at
least 1 bean which qualifies as autowire candidate. Dependency
annotations: {}
Why?
#Configuration
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
class cfg{
public cfg(#Value("${xx}") String xx) {
System.out.println(xx);
}
}
application.properties
xx = 7
Also I found that if replace #Configuration with #Component or add (proxyBeanMethods = false) the problem goes away.
Source code
#Configuration indicates that a class declares one or more #Bean methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime. If you want #Value to work you will need #PropertySource annotation.
If you annotate it with #Component, then it will be a fully-fledged Spring Bean on which #Value works out of the box.

How to add a protected configuration with access to properties to a Spring Boot Test context?

i use JUnit 5 with Spring Boot 2.5.2 . Now i want to write a Unit test, that does not load the full Application context.
Therefore i annotate my test like that:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {ConfigurationA.class})
class Test1{
...
}
In ConfigurationA the Bean1 gets created.
The Problem is that the ConfigurationA accesses an ConfigurationB for creating Bean1, but ConfigurationB is protected.
Now i get the following Error:
Error creating bean with name 'Bean1' defined in com.package.sample.config.ConfigurationA: Unsatisfied dependency expressed through method 'createBean1' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.package.sample.config.ConfigurationB available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
When i change #ContextConfiguration... to #SpringBootTest it works but the whole Context gets loaded.
Is there any solution to load not the whole context?
Just make ConfigurationA extend ConfigurationB

Mapstruct cannot find impl

I am using mapstruct to transform a DTO into an object and vice versus and I am getting the following exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.rppjs.customer.online.portal.dtos.mapper.UserMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1506)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1101)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1062)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:819)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:725)
I can see that UserMapper.impl is being generated but still the above exception. My code is on github on this branch 42_RenameCodeBaseToCustomerOnlinePortal. The code is pretty simple and not many lines of code. The exception is generated as part of the RegistrationEndpointIT.java.
Please could you take a look where I am going wrong? It is using a gradle wrapper.
Additionally, I get the following exception when running Application.java:
Description:
Parameter 0 of constructor in
com.rppjs.customer.online.portal.endpoints.RegistrationEndpoint
required a bean of type
'com.rppjs.customer.online.portal.dtos.mapper.UserMapper' that could
not be found.
Action:
Consider defining a bean of type
'com.rppjs.customer.online.portal.dtos.mapper.UserMapper' in your
configuration.
Please note, Application.java is a Spring boot application.
The problem is that RegistrationEndpoint uses the mapper as constructor argument. Since it is a component Spring wants to autowire it. But neither UserMapper nor UserMapperImpl are spring beans, therefore the exceptions.
You have two options:
Remove the UserMapper constructor argument and get your mapper with Mappers.getMapper(UserMapper.class). Best practive would be to also public MAPPER instance inside your mapper (see the example here)
If you need autowired dependencies inside your mapper you can define your mapper as as spring bean as follows:
#Mapper(componentModel = "spring")
#Component
public interface UserMapper() {
//...
}

spring boot scanning and injecting external non-spring beans

What does it take, or is it even possible for Spring to scan and inject non-spring annotated classes? For example.
resource.jar
com.project.resource.ResourceInterface
com.project.resource.StandardResource <-- concrete implementation
#Singleton <--- Standard CDI annotation
public class StandardResource implements ResourceInterface{
#Override
public void something(){}
}
Now let's say I have a spring boot application which depends on resource.jar.
com.project.resource.SpringApp
#SpringBootApplication(scanBasePackages = {"com.project"})
#EnableAutoConfiguration
public class SpringApp{
... initializer
#Inject
private ResourceInterface resourceService; <--- this is not found
}
Is this supposed to work out of the box? Is this even possible? I'm using spring boot 2.0.0.RELEASE. I'm getting the following error:
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'MainController': Unsatisfied dependency expressed through field 'resourceService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.project.resource.ResourceInterface' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#javax.inject.Inject()}
Thanks
For Spring framework #Singleton has no meaning, as such even if class is picked up by component scanning it's going to be ignored. In order for Spring to recognize your class you can:
Create a configuration class in com.project.resource with #Bean of
ResourceInterface and instantiate it as StandardResource.
Since you are using Spring Boot you can create Auto-configuration (which will be similar to the first option) in resource.jar. You can follow examples
from creating autoconfiguration. With this approach no changes needed in com.project.resource
After that your spring boot app will run normally

Java Spring JPA Repository

I'm a Spring noob and I'm struggling with it.
Basically before starting develop my Server with Spring in conjunction with JPA I tried to start a simple example just to get used to this framework.
I've already get succeded in make Spring working with some frameworks as Log4J, Swagger and others. Now I'm trying to work with JPA and there are some points i can find out the solution.
I saw some blogs on how to develop with it and from all thousands options i choose to create my Repository Interfece and extend Repository<T, ID>. You can see my code bellow:
package com.example.model;
#Entity
public class Person {
#Id
public Integer id;
public String name;
public Person(){}
}
package com.example.repository;
public interface PersonRepository extends Repository<Person, Integer> {
Collection<Person> findAll();
}
package com.example.controller;
#RestController
public class PersonController {
#Autowired
private PersonRepository repo;
#RequestMapping(value = "/persons", method = RequestMethod.GET)
public Collection<Person> getAll() {
return repo.findAll();
}
}
package com.example;
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
And I also have the application.properties file:
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/test_db
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driver-class-name=org.postgresql.Driver
When I put the server running I get the following exception:
: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.example.repository.PersonRepository com.example.controllers.PersonController.repo; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.repository.PersonRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations:
: Closing JPA EntityManagerFactory for persistence unit 'default'
: Stopping service Tomcat
: Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.example.repository.PersonRepository com.example.controllers.PersonController.repo; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.example.repository.PersonRepository] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
I created a Github Repository to share the code here.
Any clue about what am I doing wrong?
First thing here is why you need to implements the base interface Repository as doing so, you will not have usual CRUD operations. For such operations is better to implements CrudRepository. Since you implement CrudRepository no need to define a findAll() Method and many well known others you can find in doc mentioned.
Furthermore, when using #SpringBootApplication Spring boot use default values. If you see the #SpringBootApplication definition you will see that :
Many Spring Boot developers always have their main class annotated with #Configuration, #EnableAutoConfiguration and #ComponentScan. Since these annotations are so frequently used together (especially if you follow the best practices above), Spring Boot provides a convenient #SpringBootApplication alternative.
The #SpringBootApplication annotation is equivalent to using #Configuration, #EnableAutoConfiguration and #ComponentScan with their default attributes: [...]
That means when using default values for ComponentScan your packages sturctures shloud be as following :
com.example.model -> your entities
com.example.repositoriy -> your repositories
com.example.controller -> controllers
com.example -> MainApplication class
Here is an example for default project structure
The main Application Class should be in higher level package then the others. Unless you have to specify packages location with #ComponentScan.
As you are beginner with the framework. I suggest you to always see classes definitions in official documentation.
UPDATE :
Here is an example from one of my spring boot projects
Also see this spring guide for JPA
Just annotate your interface with #Repository. And if that doesnt work try adding #EnableJPARepositories to the main class.
Try adding the #Repository annotation to your PersonRepository, that could be the reason it doesn't find it.
You need to annotate your PersonRepository interface with #Respository, otherwise the spring context won't recognize it or create an implementation for it.

Categories