Camel No Route when Autowiring Constructor - java

Im using spring boot with modules. I have a parent project with several sub modules.
Camel Routes are failing to start up when I configure the route with Contructor Autowiring.
I get Total 0 routes, of which 0 are startedWhen starting constructor like this.
private final ScanProcessor scanProcessor;
private final ScheduleProcessor scheduleProcessor;
private final TagProcessor tagProcessor;
private final LatestScanProcessor latestScanProcessor;
private final RabbitMqService rabbitMqService;
#Autowired
public DashboardRoute(ScanProcessor scanProcessor,
ScheduleProcessor scheduleProcessor,
TagProcessor tagProcessor,
LatestScanProcessor latestScanProcessor,
RabbitMqService rabbitMqService){
this.scanProcessor = scanProcessor;
this.scheduleProcessor = scheduleProcessor;
this.tagProcessor = tagProcessor;
this.latestScanProcessor = latestScanProcessor;
this.rabbitMqService = rabbitMqService;
}
#Override
public void configure() throws Exception {
from(CONSUME_SCHEDULE_ROUTE)
.routeId("consume-schedule")
.process(scheduleProcessor); // no strings
}
The whole thing works when I dont autowire any of the beans and delcare the route like this.
from(CONSUME_SCHEDULE_ROUTE)
.routeId("consume-schedule")
.process("scheduleProcessor") // notice this is a string
Does camel support spring route Contructor autowiring? Do I need to take some extra config steps to handle this properly? I prefer linking beans directly that way when I refactor class names it links back ok.

I tried similar example as yours and it worked correctly. You can make sure that you have #Compoent in you route class and all the processor classes and service class.
Also you can try to add #Autowired on the local variable. (Constructor should work fine. This is just an extra stap to make sure your constructor works)
#Component
#ServletComponentScan(basePackages = "com.example.camel")
public class ServiceRoutes extends RouteBuilder {
#Autowired
private ScanProcessor scanProcessor;
#Autowired
private ScheduleProcessor scheduleProcessor;
#Autowired
private TagProcessor tagProcessor;
#Autowired
private LatestScanProcessor latestScanProcessor;
#Autowired
private RabbitMqService rabbitMqService;
#Override
public void configure() throws Exception {
from(CONSUME_SCHEDULE_ROUTE)
.routeId("consume-schedule")
.process(scheduleProcessor);
}
}
Hope this helps.

Related

Why Autowiring on Test Classes Doesn't Work?

I'm trying to write a test method for my Spring Boot application which creates a meeting on Zoom. I haven't done any unit test before, it's my first try.
I want to test the create method of my service in different scenarios (different meeting types). To test it, I need my MeetingService interface. When I try to Autowire it with #RequiredArgsConstructor from Lombok; I get this error message:
org.junit.jupiter.api.extension.ParameterResolutionException: No
ParameterResolver registered for parameter
[tech.obss.zoom.service.MeetingService arg0] in constructor [public
tech.obss.zoom.ZoomIntegrationServiceApplicationTests(tech.obss.zoom.service.MeetingService,tech.obss.zoom.config.AccountConfig)].
ZoomIntegrationServiceApplicationTests:
package tech.obss.zoom;
#SpringBootTest
#RunWith(SpringRunner.class)
#RequiredArgsConstructor
#ActiveProfiles("test")
class ZoomIntegrationServiceApplicationTests {
private final MeetingService meetingService;
private final AccountConfig accountConfig;
#ParameterizedTest
#ValueSource(ints = { 1, 2, 3, 4 })
void createMeeting(int type) {
User user = User.builder().email(accountConfig.getEmail()).userId(accountConfig.getUserId()).build();
meetingService.createMeeting(...);
}
}
I've seen a solution by using #BeforeEach annotation and creating the service by yourself. However, my MeetingService has another 5 different classes on its constructor, and those 5 of them have different dependencies. That's why it would be very difficult for me to do it this way.
MeetingServiceImpl:
#Service
#Slf4j
#RequiredArgsConstructor
public class MeetingServiceImpl implements MeetingService {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(3);
private final MeetingRepository meetingRepository;
private final CreateMeetingRepository createMeetingRepository;
private final WebClient zoomWebClient;
private final MeetingMapper meetingMapper;
private final AccountConfig accountConfig;
#Override
public MeetingDto createMeeting(CreateMeeting createMeeting) {
...
}
}
Is there an easier way to solve this?
The exception you see is caused by that fact that your test class ZoomIntegrationServiceApplicationTests is instantiated by JUnit. JUnit has no knowledge about your Spring services which is why it doesn't how to resolve the parameters of the constructor of the test class (you can deduce that from the fact that the name of the exception starts with org.junit...)
The easiest way to fix this is to to remove the #RequiredArgsConstructor annotation from your test class and to instead annotate your fields with #Autowired, like so:
#Autowired
private MeetingService meetingService;
#Autowired
private AccountConfig accountConfig;
Spring also has a #TestConstructor annotation that might be used for applications that run tests with JUnit Junipter.

Spring inject Repository mixed with normal property in constructor

I have Service class and Repository interface (Spring Data). I have also one abstract class:
public abstract class TestingMethod {
public TestingMethod() {
timeSum = 0;
}
protected long timeSum;
}
And class that extends it:
#Component
public class LimitTestingMethod extends TestingMethod {
#Autowired
private GeoTestDataRepository geoTestDataRepository;
private final int limitSize;
public LimitTestingMethod(int limitSize) {
super();
this.limitSize = limitSize;
}
}
In my Service I want to create instance of LimitTestingMethod and set its argument limitSize.
Something like:
LimitTestingMethod ltm3 = new LimitTestingMethod(3);
LimitTestingMethod ltm10 = new LimitTestingMethod(10);
But I got error:
Description: Parameter 0 of constructor in
com.exence.postgiscma.testingMethod.LimitTestingMethod required a bean
of type 'int' that could not be found. Action: Consider defining a
bean of type 'int' in your configuration.
Is that possible to achieve something like I want?
All best!
//EDIT
As I can see in comments it's bad approach. So maybe someone will give me advise how to project this better?
Is this good solution to pass repo as argument in constructor (I guess that not, but I can't get the idea how to do this better)?
LimitTestingMethod ltm3 = new LimitTestingMethod(3, geoTestDataRepository);
Is there a good and elegant solution?
As you are creating instances outside the scope of Spring your current solution won't work. The error comes from the fact that you have annotated it with #Component, it will detect it at startup and tries to create a bean, and fails.
To solve this you can do 1 of 2 things.
Let Spring handle the creation of the beans by using the ApplicationContext as a factory, providing additional arguments and make the bean prototype scoped.
Let Spring handle the injection after you manually created the instance using the ApplicationContext.
Use ApplicationContext as a factory
First make your bean a prototype so that it will be constructed when needed.
#Component
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class LimitTestingMethod extends TestingMethod { ... }
Now an instance won't be created during startup. In your service inject the ApplicationContext and use the getBean method to get your desired instance.
public class Service {
#Autowired
private ApplicationContext ctx;
public void yourMethod() {
LimitTestingMethod ltm3 = ctx.getBean(LimitTestingMethod.class, 3);
LimitTestingMethod ltm10 = ctx.getBean(LimitTestingMethod.class, 10);
}
}
This will let Spring create the instance using the value passed in for the constructor and do the autowiring.
Injection after creation
Another solution is to manually create the instances and after that let Spring handle the auto wiring. You will lose the AOP abilities with this and will get only auto wiring.
First remove the #Component annotation from your LimitTestingMethod so it won't get detected during startup.
public class LimitTestingMethod extends TestingMethod { ... }
Now in your service autowire the ApplicationContext and after creating your bean use that to inject the dependencies.
public class Service {
#Autowired
private ApplicationContext ctx;
public void yourMethod() {
LimitTestingMethod ltm3 = new LimitTestingMethod(3);
LimitTestingMethod ltm10 = new LimitTestingMethod(10);
ctx.getAutowireCapableBeanFactory().autowireBean(lmt3);
ctx.getAutowireCapableBeanFactory().autowireBean(lmt10);
}
}
Both will achieve what you want, however, now your code directly depends on the Spring API. So instead of doing this, you are probably better of with another option and that is to inject everything for the LimitTestingMethod through the constructor and pass the repository yourself.
Use constructor to create an instance
public class LimitTestingMethod extends TestingMethod {
private final GeoTestDataRepository geoTestDataRepository;
private final int limitSize;
public LimitTestingMethod(int limitSize, GeoTestDataRepository geoTestDataRepository) {
this.limitSize=limitSize;
this.geoTestDataRepository=geoTestDataRepository;
}
}
Then you can simply autowire the repository in your service class and create the instances as needed (or create a factory which contains the complexity of creating this object).
public class Service {
#Autowired
private GeoTestDataRepository repo;
public void yourMethod() {
LimitTestingMethod ltm3 = new LimitTestingMethod(3, repo);
LimitTestingMethod ltm10 = new LimitTestingMethod(10, repo);
}
}

#Autowired field null when executing production code via integration tests

Bit of a weird one that I've been scratching my head over for the past few days. I have a JPA repository that is field injected into a service class. Is works perfectly when running the server and sending a request via a client but when the code is executed via integration tests the field injected class (CustomerRepository ) is always null.
I've tried various advice via the internet but I've not found a similar scenario to mine, any help would be much appreciated
Service class
#GRpcService
public class CustomerService extends CustomerServiceGrpc.CustomerServiceImplBase {
#Autowired
private CustomerRepository repository;
#Override
public void createCustomer(CreateCustomerRequest request, StreamObserver<CreateCustomerResponse> responseObserver) {
final CustomerDao convertedDao = ProtoToDaoConverter.convertCustomerRequestProtoToCustomerDao(request);
repository.save(convertedDao);
responseObserver.onNext(CreateCustomerResponse.newBuilder().setSuccess(true).build());
responseObserver.onCompleted();
}
}
Integration test
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class CustomerServiceIT {
#Rule
private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
#Test
public void something() throws IOException {
String serverName = InProcessServerBuilder.generateName();
// Create a server, add service, start, and register for automatic graceful shutdown.
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(new CustomerService()).build().start());
customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
// Create a client channel and register for automatic graceful shutdown.
grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();
final CreateCustomerResponse response = blockingStub.createCustomer(request);
}
}
In test you invoke new CustomerService(). You create an object by itself, not via spring. I guess you should create a field in test class
#Autowired private final CustomerService customerService
and pass it in
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(customerService).build().start());
You can use Mockito or any other tests framework to mock your class dependencies (your service and your JPA Repository).
You can use these features :
#InjectMocks - Instantiates testing object instance and tries to inject fields annotated with #Mock or #Spy into private fields of testing object
#Mock - Creates mock instance of the field it annotates
In your test class your have to use #Mock to inject "a fake respository" :
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class CustomerServiceTest {
#InjectMocks
private CustomerService testingObject;
#Mock
private CustomerRepository customRepository;
#Rule
private final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
#BeforeMethod
public void initMocks(){
MockitoAnnotations.initMocks(this);
}
#Test
public void something() throws IOException {
// inside the testing method you have to define what you mocked object should return.
// it means mocking the CustomRepository methods
CustomerDao customer = new CustomerDao(); // fill it with data
Mockito.when(customRepository.save()).thenReturn(customer);
// Create a server, add service, start, and register for automatic graceful shutdown.
grpcCleanup.register(InProcessServerBuilder
.forName(serverName).directExecutor().addService(new CustomerService()).build().start());
customerServiceGrpc.CustomerServiceBlockingStub blockingStub = CustomerServiceGrpc.newBlockingStub(
// Create a client channel and register for automatic graceful shutdown.
grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
final CreateCustomerRequest request = CreateCustomerRequest.newBuilder().setFirstName("Simon").setSecondName("Brown").setRole("Product Developer").build();
final CreateCustomerResponse response = blockingStub.createCustomer(request);
}
}
As your repository and it's methods are mock you customerService should be populated with fake data for test purpose.
Note : : It nice to have a naming convention for classes and especially tests classes. A common use is to always give the a suffix of xxTest as i did in the answer
It's hard to answer without being able to read stacktraces. I would start investigating this problem by looking at the spring context configuration classes. Maybe some of them are missing at the runtime of your integration test.
If spring #Configuration or other #Component classes exist in your application, it might help to load them explicitly with your tests, along with your unexpectedly null-value CustomerRepository:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = {AppConfig.class, CustomerRepository.class })
public class CustomerServiceIT {
This might not solve it, but maybe reveals some error messages that help you to investigate the problem.

Provide external Bean before context start

I am developing a Java API for a service and I want to extract it to a library.
I am using spring 4.3.3
Right now there is a bean called ApiConfig which is simple pojo.
public class ApiConfig {
private String host;
private String username;
private String password;
}
and the values are read from a properties file.
I would like to be able to construct and provide this class before the context starts (several components have this class as #Autowired dependency).
For instance:
public class LoginService {
#Autowired
private ApiConfig apiConfig
[...]
}
Basically, I would like to do something like this:
public static MyApi get(ApiConfig apiConfig) {
//Here I want to provide this apiConfig as singleton bean that would be used everywhere
provide somehow this class as bean
// here all beans are loaded and the it fails because it cannot resolve ApiConfig
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ContextConfig.class);
MyApi myApi= context.getBean(MyApi.class);
return myApi;
}
The method MyApi.get(AppConfig) would be used by other java applications by adding dependency in pom.xml
Is there a way I can do this? Providing the ApiConfig bean and then initialize all the application?
Basically to let Spring know that there is also this bean, before starting context with new AnnotationConfigApplicationContext(ContextConfig.class)
UPDATE
The idea would be this, in any application using this library.
public static void main(String asdas[]) {
ApiConfig config = new ApiConfig();
config.setUsername("BOBTHEUSER");
//config.set etc
MyApi api = MyApi.get(config);
api.doOperation();
Actually #Autowire is enough. Make the ApiConfig a Bean and autowire it where it's is necessary. Spring resolves the proper order.
If you have two beans and one need the second to be initialized before creation use #DependsOn annotation
#Configuration
public class MainConfig {
#Autowired
private ApiConfig apiConfig
#Bean(name="apiConfig")
public ApiConfig apiConfig(){
... init the config ...
return apiConfigInstance;
}
#Bean(name="myApi")
#DependsOn("apiConfig")
public MyApi myApi(){
MyApi api = new MyApi(apiConfig);
return api;
}
}
Code from the example modified

Can spring inject sample bean twice with different parameterization?

Consider a code:
public class MyProcessor<T extends GenericData> implements ProcessorInterface<T> {
#Autowired
private List<SomeCriteria<T>> criterias;
#Override
public long calculate(T data) {
long result = 0;
for (SomeCriteria c : criterias) {
result += c.calculate(data);
}
return long;
}
}
So the difference only in SomeCriteria implementation and GenericData. E.g. for one GenericData there are several
SomeCriteria. So if there are 3 GenericData is it possible to write a code like that:
public DataService {
#Autowire
private MyProcessor<DataOne> processorOne;
#Autowire
private MyProcessor<DataTwo> processorTwo;
#Autowire
private MyProcessor<DataThree> processorThree;
}
Without writing implementation for processor each time?
Yes, it is possible. As of Spring 4.0 you can do thing such as
#Autowired
private Store<String> s1; // Injects the stringStore bean
#Autowired
private Store<Integer> s2; // Injects the integerStore bean
The example above was copied from the Spring Framework 4.0 and Java Generics blog post by Phil Webb on the Spring web site. Please read it for more details.
You can use #Qualifier annotation for create more than one bean of the same type. I hope this will helpfull to you.
public DataService {
#Qualifier
private MyProcessor<DataOne> processorOne;
#Qualifier
private MyProcessor<DataTwo> processorTwo;
}

Categories