4

I am using Spring Boot and trying to implement factory design pattern in it. The issue is that when the objects of QuarterLevelStudentInternship and QuarterLevelMou are created, the autowired dependencies declared inside those classes are set to null, so it throws me NullPointerException.

FormOperation

I created an interface.

public interface FormOperation {

    public  Map<String, Object> fetchLevelsInfo(long levelId);

    public  Object fetchCurrentTargetLevel(long levelId);
}

QuarterLevelStudentInternship

This class implements FormOperation interface

@Component
public class QuarterLevelStudentInternship implements FormOperation {

    @Autowired  /*@Lazy*/
    StudentInternshipService internshipService;

    @Autowired  /*@Lazy*/
    StudentInternshipRecordService internshipRecordService;

     // these two objects are showing null at the time of object generation and cause null pointer exception

    @Override
    public Map<String, Object> fetchLevelsInfo(long levelId) {
        HashMap<String, Object> levels = new HashMap<>();
        levels.put("internshipDetails", internshipService.fetchStudentInternship(levelId));

        List<Map<String, Object>> internshipRecord = internshipRecordService.fetchStudentInternshipRecords(levelId);

        levels.put("internshipRecord", internshipRecord);
        levels.put("internshipGraph", internshipRecordService.fetchInternshipRecordsGroupbyOrganization(levelId));
        levels.put("currentTargetLevel", internshipRecord.size());

        return levels;
    }

    @Override
    public Object fetchCurrentTargetLevel(long levelId) {
        List<Map<String, Object>> internshipRecord = internshipRecordService.fetchStudentInternshipRecords(levelId);
        return internshipRecord.size();
    }

}

QuarterLevelMou

This class implements FormOperation interface

@Component
public class QuarterLevelMou implements FormOperation  {

    @Autowired  /*@Lazy*/
    MouServices mouService;

    @Override
    public Map<String, Object> fetchLevelsInfo(long levelId) {
        HashMap<String, Object> levels = new HashMap<>();
        List<Map<String, Object>> mouRecord = mouService.fetchMouResult(levelId);

        levels.put("mouDetails", mouRecord);
        levels.put("currentTargetLevel", mouRecord.size());

        return levels;
    }

    @Override
    public Object fetchCurrentTargetLevel(long levelId) {
        List<Map<String, Object>> mouRecord = mouService.fetchMouResult(levelId);
        return mouRecord.size();
    }

}

FormOperationFactory

It's a factory class which generate object based on evidanceForm

@Component
public class FormOperationFactory {

    public FormOperation createFormOperation(String evidanceForm) {
        if (evidanceForm.equals("Student Internship Form")) 
             return new QuarterLevelStudentInternship();

        else if (evidanceForm.equals("MOUS")) 
             return new QuarterLevelMou();

        return null;
    }
}

QuarterLevelOperations

It's my service class

@Service("quarterLevelOperations")
@Transactional
public class QuarterLevelOperations {

    @Autowired  @Lazy
    QuarterLevelResultService resultService;

public List<Map<String, Object>> fetchLevelsInfoForForms(
            List<Map<String, Object>> quarterLevels, String evidanceForm, 
            String year, boolean direction, Long quarterId) {

        FormOperationFactory formOperationFactory = new FormOperationFactory();
        for(Map<String, Object> levels :quarterLevels) {
        //quarterLevels.forEach(levels -> {
            long levelId = Long.parseLong(levels.get("id").toString());
            if (evidanceForm == null) { 
                levels.put("evidance", resultService.fetchQuaterLevelResultEvidance(levelId));
            }
            else if (evidanceForm.equals("Student Internship Form")) {
                FormOperation operation = formOperationFactory.createFormOperation(evidanceForm);
                levels.putAll(operation.fetchLevelsInfo(levelId));
            }
            else if (evidanceForm.equals("MOUS")) {
                FormOperation operation = formOperationFactory.createFormOperation(evidanceForm);
                levels.putAll(operation.fetchLevelsInfo(levelId));
            }

} //);
        return quarterLevels;

    }
}
nikiforovpizza
  • 487
  • 1
  • 7
  • 13
dhyanandra singh
  • 1,071
  • 2
  • 18
  • 38

2 Answers2

4

The FormOperation instances you are created in the FormOperationFactory class are not Spring Beans but only Java objects created with the new operator.
Besides, these classes (QuarterLevelMou and QuarterLevelStudentInternship) define Spring dependencies but are not defined as Spring beans either.

All that matter as the Spring dependency autowiring is designed to work with Spring beans.

About your requirement, you should note that the FormOperation subclasses are immutable. I don't see why you need to create a new instance at each invocation of the factory method.
Instead, you could not make them singleton.

So I advise you to make the factory and the classes that it instances as Spring singletons.
Then in FormOperationFactory, inject the two instances that the factory has to create. At last in the factory method return the one or the other instance according to the parameter passed by the client.

@Component
public class FormOperationFactory {

    @Autowired
    private QuarterLevelMou quarterLevelMou;
    @Autowired
    private QuarterLevelStudentInternship quarterLevelStudentInternship ;

    public FormOperation createFormOperation(String evidanceForm) {
        if (evidanceForm.equals("Student Internship Form")) 
             return quarterLevelStudentInternship;

        else if (evidanceForm.equals("MOUS")) 
             return quarterLevelMou;

        return null;
    }
}

And :

@Component
public class QuarterLevelMou implements FormOperation  { ...}

And :

@Component
public class QuarterLevelStudentInternship implements FormOperation {

Additionally, as you really need to inject dependencies in an object that is not a Spring bean, you can as proposed by @Simon Berthiaume to inject a AutowireCapableBeanFactory instance in your factory bean and to use it to inject dependencies.

For example :

@Component
public class FormOperationFactory {

    @Autowired
    private AutowireCapableBeanFactory beanFactory;

    public FormOperation createFormOperation(String evidanceForm) {
        FormOperation formOperation = null;

        if (evidanceForm.equals("Student Internship Form")) 
              formOperation = new QuarterLevelStudentInternship();

        else if (evidanceForm.equals("MOUS")) 
             formOperation = new QuarterLevelMou();

        if (formOperation != null){
            beanFactory.autowireBean(formOperation);            
        }

        return formOperation;
    }
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • really sorry, @Component exists in my solution i forgot to mention it. – dhyanandra singh Mar 10 '18 at 06:34
  • @dhyanandra singh It doesn' matter. The rest of the content is applicable. – davidxxx Mar 10 '18 at 06:35
  • I agree that the OP should consider whether or not he really needs to instantiate those classes himself or if it's just something he did out of limited Spring understanding (we have all been there, no judgement). If he truly needs to create new instances himself and needs to have injection handled for him, he should take a look at [AutowireCapableBeanFactory.autowireBean(Object)](https://stackoverflow.com/a/3813725/4660500) – Simon Berthiaume Mar 10 '18 at 11:35
  • @Simon Berthiaume Excellent reference. Thanks a lot as I looked it but I didn't manage to find ! I updated. – davidxxx Mar 10 '18 at 13:11
  • @davidxxx i tried ur suggested implementation but still facing same issue because @ Autowired private AutowireCapableBeanFactory beanFactory; set to null and cause NullPointerException as other dependency. – dhyanandra singh Mar 10 '18 at 18:34
  • @davidxxx: as you suggested i tried spring way of work and it's work but still i can't understand why it's not working in my old solution. – dhyanandra singh Mar 10 '18 at 18:36
0

As @davidxxx suggested i implemented this problem in spring way.. and it's working.

@Component("quarterLevelStudentInternship")
        public class QuarterLevelStudentInternship implements FormOperation {....}

        @Component("quarterLevelMou")
        public class QuarterLevelMou implements FormOperation  {.....}

        @Service("quarterLevelOperations")
        @Transactional
        public class QuarterLevelOperations {

            @Autowired  /*@Lazy*/
            @Qualifier("quarterLevelStudentInternship")
            FormOperation internshipOperation;

            @Autowired  /*@Lazy*/
            @Qualifier("quarterLevelMou")
            FormOperation mouOperation;

            @Autowired  @Lazy
            QuarterLevelResultService resultService;

            public List<Map<String, Object>> fetchLevelsInfoForForms(
                 List<Map<String, Object>> quarterLevels, String evidanceForm, 
                                String year, boolean direction, Long quarterId) {

                  for(Map<String, Object> levels :quarterLevels) {
                     long levelId = Long.parseLong(levels.get("id").toString());
                     if (evidanceForm == null) { 
                           levels.put("evidance", resultService.fetchQuaterLevelResultEvidance(levelId));
                          }
                          else if (evidanceForm.equals("Student Internship Form")) {
                                levels.putAll(internshipOperation.fetchLevelsInfo(levelId));
                          }
                          else if (evidanceForm.equals("MOUS")) {
                                levels.putAll(mouOperation.fetchLevelsInfo(levelId));
                          }
                      } 

            return quarterLevels;
         }
    }
dhyanandra singh
  • 1,071
  • 2
  • 18
  • 38