Spring Boot / MapStruct: Parameter 1 of constructor required a bean... Consider defining a bean of type - spring

I don't know so much about Java, but I thought it should be enough to manage this little task...
I'm building a microservice, which provides Songs and list of songs via several Rest-Endpoints. But it doesn't just return a song when called, it also has to contact another service and enhance the song object with additional information. For this I implemented a Dto-Class and I use mapstruct to handle logic behind. I also did this in other projects with no problems. But now I'm struggling, because of this error, which I don't know how to solve - it says:
Parameter 1 of constructor in
mk.microservices.songsservice.services.SongServiceImpl required a bean
of type 'mk.microservices.songsservice.web.mapper.SongMapper' that
could not be found.
Action:
Consider defining a bean of type
'mk.microservices.songsservice.web.mapper.SongMapper' in your
configuration.
Here are excerpts from my code:
SongServiceImpl
import lombok.RequiredArgsConstructor;
import mk.microservices.songsservice.domain.Song;
import mk.microservices.songsservice.repositories.SongRepository;
import mk.microservices.songsservice.web.mapper.SongMapper;
import mk.microservices.songsservice.web.model.SongDto;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
#RequiredArgsConstructor
#Service
public class SongServiceImpl implements SongService {
private final SongRepository songRepository;
private final SongMapper songMapper;
#Override
public SongDto getSongById(Integer id) {
return songMapper.songToSongDto(songRepository.findById(id));
}
#Override
public List<Song> getAllSongs() {
return songRepository.findAll();
}
....
}
SongMapper
import org.mapstruct.DecoratedWith;
import java.util.Optional;
#MapStruct
#DecoratedWith(SongMapperDecorator.class)
public interface SongMapper {
SongDto songToSongDto(Optional<Song> song);
SongDto songToSongDtoWithSongInfo(Song song);
Song songDtoToSong(SongDto songDto);
}
SongMapperDecorator
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
public class SongMapperDecorator implements SongMapper {
private SongInfoService songInfoService;
private SongMapper mapper;
#Autowired
public void setMapper(SongMapper songMapper) { this.mapper = songMapper; }
#Override
public SongDto songToSongDto(Optional<Song> song) {
return mapper.songToSongDto(song);
}
#Override
public SongDto songToSongDtoWithSongInfo(Song song) {
SongDto songDto = mapper.songToSongDto(Optional.ofNullable(song));
SongInfo songInfo = songInfoService.getSongInfoBySongId(song.getId());
songDto.setDescription(songInfo.getDescription());
return songDto;
}
#Override
public Song songDtoToSong(SongDto songDto) {
return mapper.songDtoToSong(songDto);
}
}
Also did a clean, validate and compile without any errors. But when I did the verify, I got this:
[ERROR] Failed to execute goal
org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile
(default-compile) on project songs-service: Resolution of
annotationProcessorPath dependencies failed: Missing: [ERROR]
---------- [ERROR] 1) org.mapstruct:mapstruct-processor:jar:1.4.2
My POM looks like this:
The dependency for mapstruct:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
And the plugin for enabling mapstruct and lombok working together:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.defaultComponentModel=spring
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
Would be very glad, if someone could help me solving this. Already googled a lot and didn't find anything useful yet.
Br,
Mic

As it is clear from the error the SongServiceImpl requires a bean SongMapper but Mapstruct do not generate a spring bean by default. i,e it will not add #Component annotation in the generated class, so we need to explicitly mention to generate a class that can be used to create spring bean.
so instead of #MapStruct use #Mapper(componentModel = "spring") in the mapper interface.

Solved it finally, changed the mapstruct version in my POM to 1.4.2.Final (instead of just 1.4.2). Now it works...
Got the hint frome Mapstruct-Documentation here...

Solved this finally. I used this configuration (mapstruct + lombok)
Also installed m2e-apt plugin in eclipse.
pom.xml
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</dependency>
------------------------------------------------------------
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
<compilerArgs>
<compilerArg>
-Amapstruct.defaultComponentModel=spring
</compilerArg>
</compilerArgs>
</configuration>

Related

Springboot with both aspectj and Spring AOP

I am trying to get a springboot (2.6.2) project to work with both AspectJ and Spring AOP.
I have the following sample classes:
#Entity
public class Item {
#Id #Getter private String uuid = UUID.randomUUID().toString();
private String name;
#Verify.Access
public String getName() {
return name;
}
}
public #interface Verify {
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#interface Access {}
}
#Aspect
#Slf4j
public class MyAspect {
#Before("#annotation(Verify.Access)")
public void beforeAnnotation(JoinPoint joinPoint) {
log.error("BEFORE ANNOTATION");
}
}
#Aspect
#Service
public class OtherAspect {
#Autowired private MyUtility myUtility;
#Around("#annotation(SystemCall)")
public Object run(#NonNull final ProceedingJoinPoint join) throws Throwable {
return myUtility.getInfo();
}
}
#Service
#Data
public class MyUtility {
Object info;
}
My pom.xml file has the following plugins defined:
<plugin>
<groupId>com.nickwongdev</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.12.6</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<proc>none</proc>
<complianceLevel>${java.version}</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
<forceAjcCompile>true</forceAjcCompile>
<sources/>
<weaveDirectories>
<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
</weaveDirectories>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
I have also defined a src/main/resources/org/aspectj/aop.xml:
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="mypackage..*" />
<include within="org.springframework.boot..*" />
</weaver>
<aspects>
<aspect name="mypackage.MyAspect" />
</aspects>
</aspectj>
It seems to compile okay and I see the info messages that the join points are being advised.
However, in the OtherAspect the autowired MyUtility is not getting set.
From what I could find I would expect Spring to recognize OtherAspect as a Component and Autowire in MyUtility but instead I get a NullPointerException.
Any thoughts?
Thanks!
OK, I had a little bit of time and prepared the MCVE which actually would have been your job to provide. I made the following assumptions:
You need native AspectJ, because you want to weave a target class which is not a Spring bean.
You want to use compile-time, not load-time weaaving. Therefore, you would use AspectJ Maven Plugin.
You want to use Spring dependency injection for wiring Spring beans into native AspectJ aspects, as described in the Spring manual, i.e. using an aspectOf factory method for the native aspect in Spring.
You absolutely insist on combining Lombok and native AspectJ, even though they are incompatible out of the box. I.e., you need a workaround in Maven, either binary weaving (e.g. if Lombok is only used for your non-aspect classes) or a "delombok" build step (e.g. if your aspects also use Lombok, which unfortunately they do, using the #Slf4j Lombok annotation in MyAspect.
What I changed in your setup:
I removed the dependency on Spring Data JPA to make things easier, because I was too lazy to set up a dummy in-memory database. It is not relevant for the solution here. I.e., I also commented out the #Entity and #Id annotations in class Item.
You already configured a "delombok" build step, which I wanted to stick with, because it seems to be your preference. Hence, your sample code only compiles with AspectJ Maven when using ${project.build.directory}/generated-sources/delombok as the source directory. Your idea to use a <weaveDirectory> does not work, because the aspect with the Lombok annotation does not compile that way, as it refers to the Lombok-generated static log field.
I removed the #Service annotation from the native AspectJ aspect, because that would lead to problems when wiring the application. Instead, I added a #Bean factory method to OtherAspect, so we can use #Autowired MyUtility myUtility there. In the same aspect, I also switched from #annotation(SystemCall) (due to missing code in your example) to #annotation(Verify.Access) in order to have something to test against.
I removed the superfluous aop.xml file.
I added a little Spring Boot driver application.
I switched from the no longer maintained com.nickwongdev AspectJ Maven plugin to the current dev.aspectj plugin which has more features and supports Java 17+, too.
The whole application looks like this:
<?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>
<groupId>org.example</groupId>
<artifactId>SO_AJ_SpringAutowireBeanNativeAspect_74661663</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<aspectj.version>1.9.9.1</aspectj.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>dev.aspectj</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.13.1</version>
<configuration>
<complianceLevel>${maven.compiler.target}</complianceLevel>
<proc>none</proc>
<showWeaveInfo>true</showWeaveInfo>
<forceAjcCompile>true</forceAjcCompile>
<sources>
<source>
<basedir>${project.build.directory}/generated-sources/delombok</basedir>
</source>
</sources>
<!--
<weaveDirectories>
<weaveDirectory>${project.build.directory}/classes</weaveDirectory>
</weaveDirectories>
-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.20.0</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<sourceDirectory>src/main/java</sourceDirectory>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.6.2</version>
<scope>compile</scope>
</dependency>
-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
</project>
package org.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public #interface Verify {
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#interface Access {}
}
package org.example;
import lombok.Data;
import org.springframework.stereotype.Service;
#Service
#Data
public class MyUtility {
Object info;
}
package org.example;
import lombok.Getter;
//import javax.persistence.Entity;
//import javax.persistence.Id;
import java.util.UUID;
//#Entity
public class Item {
// #Id
#Getter
private String uuid = UUID.randomUUID().toString();
private String name;
public Item(String name) {
this.name = name;
}
#Verify.Access
public String getName() {
return name;
}
}
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
#Slf4j
public class MyAspect {
#Before("#annotation(Verify.Access)")
public void beforeAnnotation(JoinPoint joinPoint) {
log.error("BEFORE ANNOTATION");
}
}
package org.example;
import lombok.NonNull;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
#Aspect
public class OtherAspect {
#Autowired
private MyUtility myUtility;
// #Around("#annotation(SystemCall)")
#Around("#annotation(Verify.Access)")
public Object run(#NonNull final ProceedingJoinPoint join) throws Throwable {
return myUtility.getInfo();
// return join.proceed();
}
}
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.Aspects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#SpringBootApplication
#Configuration
#Slf4j
public class Main {
#Bean
public OtherAspect otherAspect() {
return Aspects.aspectOf(OtherAspect.class);
}
public static void main(String[] args) {
try (ConfigurableApplicationContext appContext = SpringApplication.run(Main.class, args)) {
doStuff(appContext);
}
}
private static void doStuff(ConfigurableApplicationContext appContext) {
MyUtility myUtility = appContext.getBean(MyUtility.class);
myUtility.setInfo("my info");
Item item = new Item("my name");
log.info(item.getName());
}
}
If you run the Spring Boot application, you will see the following on the console (timestamps removed):
ERROR 20680 --- [ main] org.example.MyAspect : BEFORE ANNOTATION
INFO 20680 --- [ main] org.example.Main : my info
As you can see, both aspects kick in, the first one logging an ERROR and the other one changing the return value from "my name" to "my info".
The advantage of the "delombok" variant is that within the same Maven module, you can weave aspects into the Lombok-generated source code. The disadvantage is, that in your IDE you might not be able to compile the project imported from Maven because of the very unusual custom configuration. In IntelliJ IDEA, I had to delegate the build to Maven, but still the source code editor shows squiggly lines.
As an alternative, you could create one module with Lombok compilation (no "delombok") and a second module using binary weaving in order to weave aspects into the Lombok-enhanced class files, as described here. It would all be much easier without Lombok, though. The third alternative is compilation with Lombok and native AspectJ load-time weaving configured for Spring Boot instead of compile-time or binary weaving during build time. I cannot explain and show every variant in detail here, it is a long answer already.

MapStruct #SuperBuilder Return type error not cast

BaseDto:
#Data
#NoArgsConstructor
#SuperBuilder
public class BaseDto{
// Some fields
}
TestDto:
#Data
#NoArgsConstructor
#SuperBuilder
public class TestDto extends BaseDto {
// Some fields
}
Base Mapper:
#MapperConfig(
componentModel = "spring"
)
public interface BaseMapper<E extends BaseEntity, DTO extends BaseDto> {
DTO toDto(E entity);
....
}
Mapper Generate Impl:
#Component
public class TestMapperImpl implements BaseMapper {
#Override
public TestDto toDTO(Test entity) {
BaseDtoBuilder<?, ?> testDto= BaseDto.builder();
if ( entity != null ) {
if ( entity.getId() != null ) {
testDto.id(entity.getId() );
}
}
return testDto.build();
}
}
Mapper create Impl class automatically. Problem is return type.
BaseDtoBuilder<?, ?> testDto= BaseDto.builder();
return testDto.build();
Intelij give error return type, it must be cast as TestDto. Because of mapper return BaseDto. How can i solve this problem?
Note: if use #Builder, there is no error working fine, this time I can't access parent properties.
I solve problem with this configuration: MapStruct and Lombok not working together
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<!-- This is needed when using Lombok 1.18.16 and above -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<!-- Mapstruct should follow the lombok path(s) -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
thanks for your help xerx593

mvn spring-boot:run "No primary or single public constructor found...TemplateLineItemInput" but only from commandline?

I've got an up-the-middle Spring Boot + Lombok project that works like a champ from the IDE but errors strangely when I run it from the command line through mvn spring-boot:run. Its a pretty recent version Spring Boot...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Lombok is unremarkable and came from the Spring Initializr thing (https://start.spring.io/).
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
The JavaBean its complaining about is equally boring...
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
The API of this Boot project is GraphQL but when I execute the following mutation from a mvn spring-boot:run invocation it always comes back as an error (nothing on the console...the framework is kicking it out somehow).
Request...
mutation createTemplateLineItem($tbomId: ID!) {
createTemplateLineItem(
tbomId: $tbomId
input: {
partMasterId: "2"
partMasterIdPath: "808863036.1"
actionType: "ADD"
actionQuantity: "2"
}) {
...TBomFrag
}
}
...
{
"partMasterId": "5025489768",
"tbomId": "a4688d22-9d99-41a2-9777-6acc75b2aab9",
"lineItemId": "9e460199-34fb-432c-b971-8cd8321d3283"
}
Response...
{
"errors": [
{
"message": "No primary or single public constructor found for class aero.blue.ems.boa.domain.TemplateLineItemInput - and no default constructor found either",
"locations": [
{
"line": 20,
"column": 3
}
],
"path": [
"createTemplateLineItem"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"createTemplateLineItem": null
}
}
My spring-boot-maven-plugin is configured like...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.4</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<id>repackage</id>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
<execution>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
...also from Spring Initializr
When I run the Application from the IDE directly, no issue. The mutation works fine.
Is there something missing from my spring-boot:run config in the pom.xml or something? Did I have to clue the plugin into annotation processing? This is really confusing.
Please,
Check the following config on section plugins at you pom.xml project, as following here:
<project>
<modelVersion>4.0.0</modelVersion>
<artifactId>getting-started</artifactId>
<!-- ... -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
For more information about, check this link: https://docs.spring.io/spring-boot/docs/2.5.4/maven-plugin/reference/htmlsingle/
Ultimately this comes down to Lombok misconfiguration of my beans. The fix is to add the #AllArgsConstructor to the bean definition
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.math.BigDecimal;
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
public class TemplateLineItemInput {
private final String partMasterId;
private final String partMasterIdPath;
private final String actionType;
private final BigDecimal actionQuantity;
}
How we figured this out was to "Delombok" the Bean and look at the resulting code. This observation matched the error message; there was no public constructor.
...
TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Somehow (I still don't fully get why), the #Builder(toBuilder=true) annotation had Lombok producing a package private constructor. Jackson needed something public.
Adding the #AllArgsConstructor annotation made that constructor public and all is well.
public TemplateLineItemInput(String partMasterId, String partMasterIdPath, String actionType, BigDecimal actionQuantity) {
this.partMasterId = partMasterId;
this.partMasterIdPath = partMasterIdPath;
this.actionType = actionType;
this.actionQuantity = actionQuantity;
}
Delombok was the key.

How Inyect dependency by interface mapper

I try to use mapstruct in java Spring to mapper object DTO to Object normal
I try to call the interface mapper since the service, but I have NullPointerException, seems the interface is not injected in service I used autowired and I quit this
Service
#Service
public class FollowService implements IFollowService{
#Autowired
IFollowRepository iFollowRepository;
private IUserMapper iUserMapper;
#Override
public UserDTOCount countFollowers(int userId) throws UserIdNotFoundException, UserNotFollowException {
return iUserMapper.toUserCount(iFollowRepository.getUserById(userId));
}
Mapper
#Mapper(componentModel = "spring")
public interface IUserMapper {
#Mappings({
#Mapping(source = "id" , target = "id"),
#Mapping(source = "name", target = "name"),
#Mapping(source = "followers", target = "followers", qualifiedByName = "followers")
})
UserDTOCount toUserCount(User user);
Error
processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.reto1.demo.Service.FollowService.countFollowers(FollowService.java:54) ~[classes/:na]
I try to debug and I see the iUserMapper is Null, I dont know How call since service
Thanks!
The reason why the iUserMapper is null in your FollowService is because you are not injecting your mapper.
You need to add #Autowired in your service.
e.g.
#Service
public class FollowService implements IFollowService{
#Autowired
IFollowRepository iFollowRepository;
#Autowired
private IUserMapper iUserMapper;
#Override
public UserDTOCount countFollowers(int userId) throws UserIdNotFoundException, UserNotFollowException {
return iUserMapper.toUserCount(iFollowRepository.getUserById(userId));
}
}
Small remark: One small digression note from me. I would suggest not prefixing the interfaces with I. The IDEs can clearly show what is a class and what is an interface and it is also easier to see them in your tree structure as not everything is under "I"
After see many questions, I resolve the error this form
-First, add plugin in pom.xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Second I added lombok
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.1.Final</version>
</path>
And finally, I add autowired
#Service
public class FollowService implements IFollowService{
#Autowired
IFollowRepository iFollowRepository;
#Autowired
IUserMapper iUserMapper;

Spring-Boot + Camel + producerTemplate = thousands of threads

---UPDATE---
As it turns out the heap is getting emptied after some time. However the number of threads just grows without end. On my mac with 8Gb of RAM I am fine, but on a production machine with 1Gb I am getting:
Exception in thread "Thread-341" java.lang.OutOfMemoryError: unable to create new native thread
I did write a simple app using Spring Boot (1.2.7.RELEASE) and Apache Camel (2.15.0). The app is simple and has only 1 route: a timer will invoke a method on a bean every 1s. The method invoked will use ProducerTemplate to ssh into a remote machine, execute a small script, and print out the output to the console. Simple, right?
However, when profiling this, I can see the number of threads, and heap go through the roof! It seems like any threads created for the ssh are never killed, but parked instead. Because of that I run OOM pretty quickly.
Let me show you some profiler output:
As you can see the threads/heap go up and up very quickly.
The app code is minimal, so I will provide it all here for reference.
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>
<groupId>tests</groupId>
<artifactId>camel-producer-template-testing</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<start-class>app.Application</start-class>
<camel.version>2.15.0</camel.version>
<spring-boot.version>1.2.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring-boot</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ftp</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-ssh</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Application.java:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.TimeZone;
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application {
public static void main(String[] args) {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
SpringApplication application = new SpringApplication(Application.class);
application.run(args);
}
}
MyAppContext.java:
import org.apache.camel.CamelContext;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.spring.SpringCamelContext;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#PropertySource("application.properties")
public class MyAppContext {
private final String sshKeyPath = "/Users/gruszd/.ssh/id_rsa";
#Autowired
private ApplicationContext applicationContext;
#Bean
public CamelContext camelContext() {
return new SpringCamelContext(applicationContext);
}
#Bean
FileKeyPairProvider keyPairProvider() {
return new FileKeyPairProvider(new String[]{sshKeyPath});
}
#Bean
RoutesBuilder myRouter() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("timer://foo?period=1000").to("bean:sftpStager?method=stage");
}
};
}
}
SftpStager.java:
import org.apache.camel.ProducerTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class SftpStager {
#Autowired
private ProducerTemplate producerTemplate;
public void stage() throws Exception {
String response = producerTemplate.requestBody(
"ssh://_remote.machine.url.here_?username=_username_&keyPairProvider=#keyPairProvider",
"/home/_username_/some_temp_script.sh",
String.class);
System.out.println("----");
System.out.println(response);
System.out.println("----");
}
}
As you can see the app is very minimal, and it works (I can see the output of the remote script in my console where the app is running). But like I said, it eats up memory like fresh cookies!
Now I did read this . However, in my app the ProducerTemplate is a bean instantiated by the Camelcontext itself. Therefore I can't producerTemplate.stop() because the next trigger would throw an exception saying the template is not started...
So my main question is: am I using the ProducerTemplate in a wrong way? And if I do, how should I use it?
If I am not doing anything wrong, is that a bug? Should I report it?
As noted by the original poster:
Turns out it is a bug in Apache Camel itself, should be [and was] fixed in 2.16.2: Jira Issue here
You must stop / clear the state of the producerTemplate.
There are in-built methods like producerTemplate.stop() or in your case, since you had autowired the Producer template, you could try producerTemplate.cleanUp()

Resources