Search code examples
javaspringdecoratormapstructspring5

Mapstruct decorator unable to inject the "delegate" bean in its generated code


I'm using the @DecoratedWith annotation of mapstruct 1.4.1Final along with spring-boot 2.7.0, but it is not working.

Describing my issue below here:

I have 2 pojos Person1 and Person2

public class Person1 {
    String id;
    String field1;
    String sameField;

    public Person1() {
    }

    public Person1(String id, String field1, String sameField) {
        this.id = id;
        this.field1 = field1;
        this.sameField = sameField;
    }

    @Override
    public String toString() {
        return "Person1{" +
                "id='" + id + '\'' +
                ", field1='" + field1 + '\'' +
                ", sameField='" + sameField + '\'' +
                '}';
    }
}

public class Person2 {
    String id;
    String field2;
    String sameField;

    public Person2() {
    }

    public Person2(String id, String field2, String sameField) {
        this.id = id;
        this.field2 = field2;
        this.sameField = sameField;
    }

    @Override
    public String toString() {
        return "Person2{" +
                "id='" + id + '\'' +
                ", field2='" + field2 + '\'' +
                ", sameField='" + sameField + '\'' +
                '}';
    }

}

I have a mapper IMapper.java as below, note I have used the @DecoratedWith with DecClass.class:

@Mapper(componentModel = "spring")
@DecoratedWith(DecClass.class)
public interface IMyMapper {

    public Person1 toPerson1(Person2 person2);

    public Person2 toPerson2(Person1 person1);

}

I have DecClass.java class as below:

public abstract class DecClass implements IMyMapper{

    @Autowired
    @Qualifier("delegate")
    IMyMapper delegate;

    @Override
    public Person2 toPerson2(Person1 person1) {
        return null;
    }
    
}

On doing a maven clean install it generated the below classes:

IMyMapperImpl.java


@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-08-25T00:02:05+0530",
    comments = "version: 1.4.1.Final, compiler: javac, environment: Java 11.0.16.1 (Amazon.com Inc.)"
)
@Component
@Primary
public class IMyMapperImpl extends DecClass implements IMyMapper {

    @Autowired
    @Qualifier("delegate")
    private IMyMapper delegate;

    @Override
    public Person1 toPerson1(Person2 person2)  {
        return delegate.toPerson1( person2 );
    }
}

IMyMapperImpl_.java

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-08-25T00:02:05+0530",
    comments = "version: 1.4.1.Final, compiler: javac, environment: Java 11.0.16.1 (Amazon.com Inc.)"
)
@Component
@Qualifier("delegate")
public class IMyMapperImpl_ implements IMyMapper {

    @Override
    public Person1 toPerson1(Person2 person2) {
        if ( person2 == null ) {
            return null;
        }

        Person1 person1 = new Person1();

        person1.setId( person2.getId() );
        person1.setSameField( person2.getSameField() );

        return person1;
    }

    @Override
    public Person2 toPerson2(Person1 person1) {
        if ( person1 == null ) {
            return null;
        }

        Person2 person2 = new Person2();

        person2.setId( person1.getId() );
        person2.setSameField( person1.getSameField() );

        return person2;
    }
}

So far it looks pretty good to me, however when I run this simple test like below it gives me a null pointer exception on the line Person1 person1 = iMyMapper.toPerson1(person2);:

@Bean
    public CommandLineRunner commandLineRunner(){
        return (arg) -> {
            try {
                IMyMapper iMyMapper = Mappers.getMapper(IMyMapper.class);

                Person2 person2 = new Person2("person2", "field2", "samefff");
                System.out.println("person2=" + person2.toString());
                Person1 person1 = iMyMapper.toPerson1(person2);
                System.out.println("person1=" + person1);

            } catch (Exception e) {
                e.printStackTrace();
            }

        };
    }

The exception is coming because in the generated class IMyMapperImpl.java, the member private IMyMapper delegate; is not getting injected.

Could some please help me understand why this is happening. Any help would be sincerely appreciated.

The above source code link is here here

Also, I'm using Java11(AWS Coretto)


Solution

  • The problem is that you are telling mapstruct that you will be using an dependency injection framework, spring in this case, while in usage you are not using that framework.

    The call Mappers.getMapper(IMyMapper.class); does not do any dependency injection actions. If you would use the spring boot framework to retrieve the mapper, then dependency injection would be used.

    For example:

    @Bean
        public CommandLineRunner commandLineRunner(IMyMapper iMyMapper){
            return (arg) -> {
                try {
                    Person2 person2 = new Person2("person2", "field2", "samefff");
                    System.out.println("person2=" + person2.toString());
                    Person1 person1 = iMyMapper.toPerson1(person2);
                    System.out.println("person1=" + person1);
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
            };
        }