CDI with Non Quarkus Spring Jars - quarkus

We are trying to migrate our application from Spring to Quarkus Spring. We are Ok to re-write our application logic as Required.
Problem comes as we are using shared spring jars which are legacy and there are many in numbers. Most spring jars are using Dependency Injection using #Autowired, but corresponding class that is getting injected does not have #Component Annotations.
Will describe problem with example:
Class "MyProject" belong to Quarkus Applicatioin
public class MyProject {
#Autowired
private OtherLevelOne otherLevelOne;
public void invokeMe() {
this.otherLevelOne.call();
}
}
Class OtherLevelOne and OtherLevelTwo belongs to a dependency spring jar.
public class OtherLevelOne {
#Autowired
private OtherLevelTwo otherLevelTwo;
public void call() {
this.otherLevelTwo.otherCall();
}
}
public class OtherLevelTwo {
public void otherCall() {
}
}
Issue Faced.
During compilation of Quakrus Application we get error:
[ERROR] Failed to execute goal io.quarkus.platform:quarkus-maven-plugin:2.1.3.Final:build (quarkus-app-build) on project service-quarkus: Failed to build quarkus application: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[ERROR] [error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: Found 2 deployment problems:
[ERROR] [1] Unsatisfied dependency for type OtherLevelOne and qualifiers [#Default]
Things Tried: Added a Producer class for Both OtherLevelOne and OtherLevelTwo. This helped in resolving the Compile time Unsatisfied dependency issue and Build was success. But now we faced a NullPointException as runtime when method "call" of OtherLevelOne tried to access the instance of "OtherLevelTwo". So Looks like second level injection did not happen. Possibly Quarkus does not support this (without some modifications like adding beans.xml and #Component annotations to all the classes in dependency jars).
#Dependent
public class ProducerClass {
#Produces
public OtherLevelOne createOtherLevelOne() {
return new OtherLevelOne();
}
#Produces
public OtherLevelTwo createOtherLevelTwo(){
return new OtherLevelTwo();
}
}
Question
Is there a way we can make Quarkus work in this scenario without compiling all the dependencies with Quarkus?
Is it possible to write a Quarkus extenstion that can help in achieving Dependency Injection of required Types?
Thanks.

In quarkus configuration via XML is not supported, so you will need to create a producer method to make OtherLevelOne and OtherLevelTwo beans

Related

SpringBoot 3 native compilation not generating bean definition for second JpaRepository and failing to start with -Dspring.aot.enabled=true

I am facing an issue with Spring Boot 3 native compilation where the project contains two JpaRepository connecting to two different datasources. The creation of the second datasource configuration depends on the first datasource and JpaRepository as it contains the details about the databases to connect.
The problem is that the Spring Boot Maven plugin process-aot goal does not generate bean definition for repositories which are processed later on. As a result, the application fails to start with the -Dspring.aot.enabled=true property enabled.
I have tried several solutions, including:
Adding the #DependsOn annotation to the second datasource configuration class, but it didn't work.
Adding the #DependsOn annotation to the second JpaRepository, but it also didn't work.
Adding a #Configuration class that contains both datasource configurations, but it also didn't work.
Here is a simplified version of my code:
package com.company.multidatabases.config
#Configuration
public class DataSource1Config {
// datasource1 configuration
#Autowired
private MyEntity1Repository repo;
private Map<Object,Object> dataSources;
}
package com.company.config
#Configuration
public class DataSource2Config {
#Autowired
private DataSource1Config dataSource1Config;
#Bean
public DataSource dataSource(){
return // AbstractDataSourceRouting with datasources map from DataSource1Config
}
// datasource2 configuration that depends on dataSource1Config
}
package com.company.multidatabases.repository
#Repository
public interface MyEntity1Repository extends JpaRepository<MyEntity1, Long> {
// MyEntity1Repository definition
}
package com.company.repository
#Repository
public interface MyEntity2Repository extends JpaRepository<MyEntity2, Long> {
// MyEntity2Repository definition that depends on DataSource2Config
}
And here is the error message I get:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myEntity1Repository' available
Any help or suggestion is highly appreciated. Thank you in advance.

Adding legacy singleton to Spring ApplicationContext for Injection

I am trying to create a lightweight web service around a legacy java library to expose it as a web service using Spring Boot. I am new to Spring, while I have a lot of java experiance writing libraries all my web service experiance is in ASP.NET.
I can instantiate an instance of my library object but I can't figure out how to then have that object be injected into my controllers via #Autowired when the application is spun up.
This is my main application:
#SpringBootApplication
public class ResolverWebServiceApplication {
private static ArgumentParser newArgumentParser() {
ArgumentParser parser = ArgumentParsers.newFor("Resolver").build();
// configuring the parser
return parser;
}
public static void main(String[] args) throws ArgumentParserException {
ArgumentParser parser = newArgumentParser();
Namespace ns = parser.parseArgs(args);
ResolverOptions options = new ResolverOptions.Builder(ns)
.build();
ResolverContext context = new ResolverContext(options);
// ^^^ I need to get this injected into my controllers ^^^
SpringApplication.run(ResolverWebServiceApplication.class, args);
}
}
And then a simple controller which needs the class injected:
#RestController
public class VersionController {
#Autowired
private ResolverContext context; // And here the instance needs to be injected.
#GetMapping(path = "/version", produces = MediaType.APPLICATION_JSON_VALUE)
public long version() {
return context.getResolver().getVersionAsLong();
}
}
I could make the context a singleton which the controllers just refer to but I want to be able to test my controllers by mocking the context. There is also obviously a lot of validation and error handeling that needs to be added.
I can't have it be a Bean since I only want to instantiate one for my entire application.
The closest question I have found is this one: Registering an instance as 'singleton' bean at application startup. But I can't put the options in the configuration files. The application might be spun up in a container or on a users machine and requires the ability to accept arguments to initialize the library class. It would be a real usability degradation if someone had to manually edit the application config for these options.
You need to tell spring to consider the required classes from your lib when initializing the application context i.e Configure and let spring know how to create a bean and then let spring handle dependency injection for you.
First of all, add required jar that you have in your build file, say pom.xml for maven, in your current project. Idea is to have it on your classpath when you build the project.
As you said it is legacy lib and I am assuming it is not a spring bean, then
In your configuration class, return it as a bean, using #Bean annotaion.
#Configuration
public class YourConfigurationClass {
#Bean
SomeBean returnSomeBeanFromLegacyLib() {
return new SomeClassFromLib();
}
Once you return this bean from your config, it should be available to Spring Context for dependency injection whereever you #Autowire the required dependency.

ReactiveCrudRepository is not found by Spring to be injected into other classes

Problem:
I have a ReactiveCrudRepository which I want to use in a RestController but it's not found by Spring to be injected anymore. Before I refactored the repository towards becoming reactive (it was a CrudRepository before) the repository was found and injected by Spring.
Now I get this error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in de.shinythings.microservices.core.product.services.ProductServiceImpl required a bean of type 'de.shinythings.microservices.core.product.persistence.ProductRepository' that could not be found.
Action:
Consider defining a bean of type 'de.shinythings.microservices.core.product.persistence.ProductRepository' in your configuration.
The repository looks like this: link to GitHub
interface ProductRepository : ReactiveCrudRepository<ProductEntity, String> {
fun findByProductId(productId: Int): Mono<ProductEntity>
}
The rest controller looks like this: link to GitHub
#RestController
class ProductServiceImpl(
private val repository: ProductRepository,
private val mapper: ProductMapper,
private val serviceUtil: ServiceUtil
) : ProductService { // implementation left out here }
What I tried so far:
I enabled the debug flag in my application.yml to learn more from the output but this did not generate useful insights.
I removed the ProductRepository dependency from the ProductServiceImpl class to not get the above error when starting up Spring.
Then I wrote myself a little test to ask the ApplicationContext for the ProductRepository specifically:
#SpringBootTest
class BeanLoadingDebugging {
#Autowired
private lateinit var applicationContext: ApplicationContext
#Test
fun test() {
val bean = applicationContext.getBean(ProductRepository::class.java)
Assert.notNull(bean, "Bean not found!")
}
}
This also does not work!
So it seems that this repository just does not want to be found. I double-checked this and tried the same with the not reactive CrudRepository and this was found. 🤷‍♂️
Full disclosure:
I'm new to Spring / Spring Boot and happy for any advice here.
The complete code can be found on GitHub.
First, You have to update the dependency from org.springframework.boot:spring-boot-starter-data-mongodb to org.springframework.boot:spring-boot-starter-data-mongodb-reactive.
Second, enable reactive support like below,
#SpringBootApplication
#ComponentScan("de.shinythings")
#EnableReactiveMongoRepositories
class RecommendationServiceApplication
After these two changes i can see the test de.shinythings.microservices.core.recommendation.BeanLoadingDebugging success.
Look more into Spring reactive mongo

Bean not found error : Unsatisfied dependency expressed through field

I have the below Main App:-
Both packages are in different module and i have "com.app.api is included in the pom.xml of com.app.batch
//commented #SpringBootApplication(scanBasePackages={"com.app.batch", "com.app.api"})
public class App
{
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
}
In com.app.api i have class ApiClass
#Service
public class ApiClass {}
in `com.app.batch i have
#Component
public class JobRunner implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
// TODO Auto-generated method stub
apiClass.getData(1111);
}
}
When i comment #SpringBootApplication(scanBasePackages={"com.app.batch", "com.app.api"}) i get the following error
Field apiClass in com.app.batch.config.JobRunner required a bean of
type 'com.com.api.ApiClass' that could not be found.
How can i resolve the issue without using scanBasePackages .I don't want to use scanBasePackages as the module can get added in future and it can get cumberson
If your not interested to use
#SpringBootApplication(scanBasePackages={"com.app.batch", "com.app.api"})
you need to change the package hierarchy so that spring scans the beans easily.
Your main SpringBootApplication class should be in com.app package
and remaining classes should be in sub-packages.
Like com.app.batch and com.app.api are sub-package of com.app
By using this kinda package hierarchy you no need scanBasePackages.
What is the package of the App class?
It needs to be in the base package so that Spring Boot Application scans all the packages inside it.
#SpringBootApplication annotation enables the following annotations/features on its own:
#EnableAutoConfiguration: enable Spring Boot’s auto-configuration mechanism
#ComponentScan: enable #Component scan on the package where the application is located
#Configuration: allow to register extra beans in the context or import additional configuration classes
For further details, you can read here

Spring Boot Application with dependency having multiple datasources

I am trying to create a Spring Boot Application, with a dependency jar which has got context.xml configured with multiple datasources.
In My spring boot application, I added #ImportResource("context.xml") to the #Configuration class and now, I get an exception that
"No qualifying bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 4: XXXDataSource,YYYDataSource,ZZZDataSource,aaaaDataSource".
I read the documentation on multiple datasources in Spring Boot, but unable to fix this issue. Not sure, how I can configure my class, as I cannot change the dependency jar to change the way datasources are configured.
Please help!
You can use the "Primary" attribute on your datasource bean to make your autowiring choose it by default.
<bean primary="true|false"/>
If you are using Java configuration, use the #Primary annotation instead.
http://docs.spring.io/spring-framework/docs/4.0.4.RELEASE/javadoc-api/org/springframework/context/annotation/Primary.html
#Component
public class FooService {
private FooRepository fooRepository;
#Autowired
public FooService(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}
}
#Component
public class JdbcFooRepository {
public JdbcFooService(DataSource dataSource) {
// ...
}
}
#Primary
#Component
public class HibernateFooRepository {
public HibernateFooService(SessionFactory sessionFactory) {
// ...
}
}
If this still doesn't resolve the issue, you can name the bean, and use the #Qualifier annotation in your java classes, or use the "ref" attribute in your Spring XML configuration.
https://spring.io/blog/2014/11/04/a-quality-qualifier
#Autowired
#Qualifier( "ios") // the use is unique to Spring. It's darned convenient, too!
private MarketPlace marketPlace ;
If you require one of the datasources in the jar and are unable to modify the configuration, rather than importing the xml from the jar, copy the configurations you need into your own local spring context configuration.

Resources