springspring-bootmodelmapper

Spring can't find implementation


Here is my folder structure:

enter image description here

In my IAppUserMapper I have a method to convert every AppUser entity instance to Data Transfer Object Model. Here is the code in IAppUserMapper interface:


import com.server.ecommerceapp.dto.AppUserDTO;
import com.server.ecommerceapp.model.AppUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface IAppUserMapper {

    IAppUserMapper appUserMapper = Mappers.getMapper(IAppUserMapper.class);

    @Mapping(target = "username")
    @Mapping(target = "email")
    @Mapping(target = "password")
    @Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
     AppUserDTO toAppUserDTO(AppUser appUser);

    default String getRoleName(Role role) {
        return role.getRoleName();
    }
}  


And here is the MapperConfiguration class code where I configure both Product and user mappers:


import com.server.ecommerceapp.mapper.IAppUserMapper;
import com.server.ecommerceapp.mapper.IProductMapper;
import org.mapstruct.factory.Mappers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MapperConfiguration {
    @Bean
    public IAppUserMapper appUserMapper() {
        return Mappers.getMapper(IAppUserMapper.class);
    }

    @Bean
    public IProductMapper productMapper() {
        return Mappers.getMapper(IProductMapper.class);
    }
}

The error I get:

Error creating bean with name 'appUserMapper' defined in class path resource [com/server/ecommerceapp/configuration/MapperConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.server.ecommerceapp.mapper.IAppUserMapper]: Factory method 'appUserMapper' threw exception; nested exception is java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.server.ecommerceapp.mapper.IAppUserMapper

I was told I should make META-INF package in resources, with service package and the com.server.ecommerceapp.mapper.AppUserMapper txt with the content same as the name of the file, so that Spring can scan and find the package following the path:

src/main/resources/META-INF/service/com.server.ecommerceapp.mapper.AppUserMapper

but it didnt work. Any ideas how to solve this, and by the way, is it bad practise to start interface names with capital I cause Im coming from ASP?

Edit:

I added @Mapper(componentModel = "spring") to my interfaces and implemented them as DI with Autowired. I dont know if its related to that problem that I had but now I get error that it cant find collectors. Im trying to map a collection of Roles from AppUser to AppUserDTO. Here are both AppUser and AppUserDTO classes:

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Data
public class AppUser {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    private Long id;

    @Column(name = "username", nullable = false, unique = true)
    private String username;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @Column(name = "password", nullable = false)
    private String password;

    @ManyToMany(fetch = EAGER)
    private Collection<Role> roles;
}

And DTO:

@NoArgsConstructor
@AllArgsConstructor
@Data
public class AppUserDTO {

    private String username;

    private String email;

    private String password;

    private Collection<String> roles;
}

Solution

  • So you're using Spring, but you are trying to not use Spring.

    You should make your mappers use Spring component model:

    @Mapper(componentModel = "spring")
    public interface MyMapper {
      Target map(Source source);
    }
    

    Check docs for dependency injection: https://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection

    Or do it with shared configuration: https://mapstruct.org/documentation/stable/reference/html/#shared-configurations

    After that you can just @Autowired MyMapper myMapper; as any other Spring bean. No need to create instance in interface (the "Mappers.getMapper" thing) and no need to create mappers in java configuration, bean creation will be handled by framework.