Repository not injected in service class when initialized through a factory class

huangapple go评论55阅读模式
英文:

Repository not injected in service class when initialized through a factory class

问题

我有一个方法的多个实现,所以我决定使用工厂设计模式。这是工厂类的实现:

@Component 
public class ProcessFileFactory {

    private static ApplicationContext applicationContext;

    public ProcessFileFactory() {
        applicationContext = new AnnotationConfigApplicationContext(
                ProcessDoctorFile.class,
                ProcessPatientFile.class
        );
    }

    public static ProcessImportFiles getInstance(String fileType) {
        if (ImportTypes.DOCTORS.toString().equals(fileType)) {
            return applicationContext.getBean("processDoctorFile", ProcessImportFiles.class);
        }
        if (ImportTypes.PATIENTS.toString().equals(fileType)) {
            return applicationContext.getBean("processPatientFile", ProcessImportFiles.class);
        }

        return null;
    }
}

ProcessImportFiles是接口,我在接口中定义了需要不同实现的方法。这是接口中方法的一个实现:

@Service 
public class ProcessDoctorFile implements ProcessImportFiles {
    @Autowired
    private DoctorRepository doctorRepository;

    @Override
    public void processImportFile(String fileName, Sheet sheet) {
        System.out.println("doctors");
    }
}

这是在rest控制器中调用方法的方式:

ProcessFileFactory.getInstance(fileType).processImportFile(file.getName(), sheet);

但我收到了这个错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'processFileFactory' defined in file [C:\Workspace\MedicalStatistics\target\classes\app\one\MedicalStatistics\importService\ProcessFileFactory.class]: Failed to instantiate [app.one.MedicalStatistics.importService.ProcessFileFactory]: Constructor threw exception
...
...
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'processDoctorFile': Unsatisfied dependency expressed through field 'doctorRepository': No qualifying bean of type 'app.one.MedicalStatistics.repository.DoctorRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
...
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'app.one.MedicalStatistics.repository.DoctorRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
...
...

如果我注释掉DoctorRepository的声明,应用程序会启动,设计模式按预期工作。问题出现在我为更新DB中的数据定义了服务的repo时。我尝试创建一个初始化repository的构造函数,还尝试了多个注解,但都不起作用。有什么办法可以修复这个问题吗?

英文:

I have multiple implementations for a method, so I decided to use Factory design pattern for it. Here is the implementation for factory class:

@Component
public class ProcessFileFactory {

    private static ApplicationContext applicationContext;

    public ProcessFileFactory() {
        applicationContext = new AnnotationConfigApplicationContext(
                ProcessDoctorFile.class,
                ProcessPatientFile.class
        );
    }

    public static ProcessImportFiles getInstance(String fileType) {
        if (ImportTypes.DOCTORS.toString().equals(fileType)) {
            return applicationContext.getBean("processDoctorFile", ProcessImportFiles.class);
        }
        if (ImportTypes.PATIENTS.toString().equals(fileType)) {
            return applicationContext.getBean("processPatientFile", ProcessImportFiles.class);
        }

        return null;
    }
}

ProcessImportFiles is the interface where I defined the method which has to be implemented differently. And here is one implementation of the method from the interface:

@Service
public class ProcessDoctorFile implements ProcessImportFiles {
    @Autowired
    private DoctorRepository doctorRepository;

    @Override
    public void processImportFile(String fileName, Sheet sheet) {
        System.out.println("doctors");
    }
}

Also, this is how I call the the method in the rest controller:

 ProcessFileFactory.getInstance(fileType).processImportFile(file.getName(), sheet);

But I receive this error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'processFileFactory' defined in file [C:\Workspace\MedicalStatistics\target\classes\app\one\MedicalStatistics\importService\ProcessFileFactory.class]: Failed to instantiate [app.one.MedicalStatistics.importService.ProcessFileFactory]: Constructor threw exception
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1306) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1198) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:561) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[spring-context-6.0.6.jar:6.0.6]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.6.jar:6.0.6]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.0.4.jar:3.0.4]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:732) ~[spring-boot-3.0.4.jar:3.0.4]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-3.0.4.jar:3.0.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) ~[spring-boot-3.0.4.jar:3.0.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1304) ~[spring-boot-3.0.4.jar:3.0.4]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1293) ~[spring-boot-3.0.4.jar:3.0.4]
at app.one.MedicalStatistics.MedicalStatisticsApplication.main(MedicalStatisticsApplication.java:16) ~[classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [app.one.MedicalStatistics.importService.ProcessFileFactory]: Constructor threw exception
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:223) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:87) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1300) ~[spring-beans-6.0.6.jar:6.0.6]
... 17 common frames omitted
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'processDoctorFile': Unsatisfied dependency expressed through field 'doctorRepository': No qualifying bean of type 'app.one.MedicalStatistics.repository.DoctorRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:712) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:692) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:133) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:481) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1408) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:961) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917) ~[spring-context-6.0.6.jar:6.0.6]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:584) ~[spring-context-6.0.6.jar:6.0.6]
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93) ~[spring-context-6.0.6.jar:6.0.6]
at app.one.MedicalStatistics.importService.ProcessFileFactory.<init>(ProcessFileFactory.java:24) ~[classes/:na]
at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500) ~[na:na]
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:484) ~[na:na]
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:197) ~[spring-beans-6.0.6.jar:6.0.6]
... 19 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'app.one.MedicalStatistics.repository.DoctorRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1812) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1371) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1325) ~[spring-beans-6.0.6.jar:6.0.6]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:709) ~[spring-beans-6.0.6.jar:6.0.6]
... 38 common frames omitted

If I comment the declaration for DoctorRepository the application starts and design pattern works as expected. The problem appeared when I defined a repo in my service for updating data in DB. I tried to create a constructor to initialize the repository, also multiple annotations, but nothing works. Any idea how to fix it?

答案1

得分: 1

I rewrote your code and structured it to improve readability. Here's the translated code:

首先,这是结构图:

Repository not injected in service class when initialized through a factory class

假设这是您的ProcessDoctorFile服务:

@Service
public class ProcessDoctorFileService implements ProcessImportFiles {
    @Autowired
    private DoctorRepository doctorRepository;

    @Override
    public void processImportFile(String fileName, Sheet sheet) {
        System.out.println(fileName + " > ProcessDoctorFileService");
    }
}

患者服务也是同样的方式:

@Service
public class ProcessPatientFileService implements ProcessImportFiles {
    @Autowired
    private PatientRepository doctorRepository;

    @Override
    public void processImportFile(String fileName, Sheet sheet) {
        System.out.println(fileName + " > ProcessPatientFileService");
    }
}

现在是工厂部分:

public class ProcessFileFactory   {
    public static ProcessImportFiles getInstance(String fileType) {
        if (ImportTypes.DOCTORS.toString().equals(fileType)) {
            return ApplicationContextProvider.getApplicationContext().getBean("processDoctorFileService", ProcessImportFiles.class);
        }
        if (ImportTypes.PATIENTS.toString().equals(fileType)) {
            return ApplicationContextProvider.getApplicationContext().getBean("processPatientFileService", ProcessImportFiles.class);
        }

        return null;
    }
}

为了确保您不会获得空的应用程序上下文,您应该在另一个类中获取它,然后将其注入到您的工厂中,使用ApplicationContextAware接口让Spring Boot为您提供应用程序上下文:

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        ApplicationContextProvider.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

让我们测试一下我们到目前为止所做的,使用CommandLineRunner:

@SpringBootApplication
public class StackoverflowApplication {

    public static void main(String[] args) {
        SpringApplication.run(StackoverflowApplication.class, args);
    }

    @Bean
    CommandLineRunner commandLineRunner(){
        return args -> {
            Sheet sheet = new Sheet();
            ProcessFileFactory.getInstance(ImportTypes.DOCTORS.name()).processImportFile("医生文件名", sheet);
            ProcessFileFactory.getInstance(ImportTypes.PATIENTS.name()).processImportFile("患者文件名", sheet);
        };
    }
}

在控制台中的结果:

Repository not injected in service class when initialized through a factory class

英文:

I rewrote your code and struct it again to look more readable, this is how I proceeded:

First, this is the structure:

Repository not injected in service class when initialized through a factory class

Assuming this is your ProcessDoctorFile service:

@Service
public class ProcessDoctorFileService implements ProcessImportFiles {
@Autowired
private DoctorRepository doctorRepository;
@Override
public void processImportFile(String fileName, Sheet sheet) {
System.out.println(fileName + " > ProcessDoctorFileService");
}
}

same thing for the patient service:

@Service
public class ProcessPatientFileService implements ProcessImportFiles {
@Autowired
private PatientRepository doctorRepository;
@Override
public void processImportFile(String fileName, Sheet sheet) {
System.out.println(fileName + " > ProcessPatientFileService");
}
}

Now the factory:

public class ProcessFileFactory   {
public static ProcessImportFiles getInstance(String fileType) {
if (ImportTypes.DOCTORS.toString().equals(fileType)) {
return ApplicationContextProvider.getApplicationContext().getBean("processDoctorFileService", ProcessImportFiles.class);
}
if (ImportTypes.PATIENTS.toString().equals(fileType)) {
return ApplicationContextProvider.getApplicationContext().getBean("processPatientFileService", ProcessImportFiles.class);
}
return null;
}
}

To be sure that you don't get a null application context, you should get it in another class and then inject it into your factory, use ApplicationContextAware interface to make spring boot provide the application context for you:

@Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ApplicationContextProvider.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}

let's test what we did until now using a CommandLineRunner:

@SpringBootApplication
public class StackoverflowApplication {
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
@Bean
CommandLineRunner commandLineRunner(){
return args -> {
Sheet sheet = new Sheet();
ProcessFileFactory.getInstance(ImportTypes.DOCTORS.name()).processImportFile("file name for doctors",sheet);
ProcessFileFactory.getInstance(ImportTypes.PATIENTS.name()).processImportFile("file name for patients",sheet);
};
}
}

Result in the console:
Repository not injected in service class when initialized through a factory class

答案2

得分: 0

Some details that could be important are missing in your question (packages of the classes involved in your problem, for example) but I guess that, for any reason, the ApplicationContext you instantiate in your ProcessFileFactory does not discover the DoctorRepository class.

I suggest to change your factory this way:

@Component
public class ProcessFileFactory implements org.springframework.context.ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static ProcessImportFiles getInstance(String fileType) {
        if (ImportTypes.DOCTORS.toString().equals(fileType)) {
            return applicationContext.getBean("processDoctorFile", ProcessImportFiles.class);
        }
        if (ImportTypes.PATIENTS.toString().equals(fileType)) {
            return applicationContext.getBean("processPatientFile", ProcessImportFiles.class);
        }

        return null;
    }

    @Override
    public void setApplicationContext(ApplicationContext context)
        throws BeansException {
        this.applicationContext = context;
    }
}

When you implement ApplicationContextAware interface, you ask Spring to inject into your class the existing application context.

But, to be honest, there is a solution that, I think, would be cleaner as it would just use Dependency Injection instead of getting beans from the application context.

Change your ProcessImportFiles this way:

public interface ProcessImportFiles {

    String getManagedFileType();

    void processImportFile(String fileName, Sheet sheet);

}

Implementation example of the interface for ProcessPatientFile:

@Service
public class ProcessPatientFile implements ProcessImportFiles {

    @Override
    public String getManagedFileType() {
        return ImportTypes.PATIENTS.toString();
    }

    @Override
    public void processImportFile(String fileName, Sheet sheet) {

        System.out.println("I'm processing a PATIENTS file");

    }
}

Then change your ProcessFileFactory class this way:

@Component
public class ProcessFileFactory {

    private Map<String, ProcessImportFiles> importFileProcessorsByType;

    public ProcessFileFactory(Collection<ProcessImportFiles> importFileProcessors) {

        importFileProcessorsByType = importFileProcessors.stream().collect(Collectors.toMap(
            ProcessImportFiles::getManagedFileType,
            processor -> processor
        ));

    }

    public ProcessImportFiles getInstance(String fileType) {
        return importFileProcessorsByType.get(fileType);
    }

}

Spring will automatically inject all beans implementing your interface into your factory. The internal map of your factory will retrieve the expected implementation according to the fileType.

By the way, if one day, you need to code new implementations of the ProcessImportFiles interface, your factory will work without any change.

英文:

Some details that could be important are missing in your question (packages of the classes involved in your problem, for example) but I guess that, for any reason, the ApplicationContext you instantiate in your ProcessFileFactory does not discover the DoctorRepository class.

I suggest to change your factory this way :

    @Component
public class ProcessFileFactory implements org.springframework.context.ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ProcessImportFiles getInstance(String fileType) {
if (ImportTypes.DOCTORS.toString().equals(fileType)) {
return applicationContext.getBean(&quot;processDoctorFile&quot;, ProcessImportFiles.class);
}
if (ImportTypes.PATIENTS.toString().equals(fileType)) {
return applicationContext.getBean(&quot;processPatientFile&quot;, ProcessImportFiles.class);
}
return null;
}
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.applicationContext = context;
}
}

When you implement ApplicationContextAware interface, you ask Spring to inject into your class the existing application context.

But, to be honest, there is a solution that, I think, would be cleaner as it would just use Dependency Injection instead of getting beans from the application context.

Change your ProcessImportFiles this way :

    public interface ProcessImportFiles {
String getManagedFileType();
void processImportFile(String fileName, Sheet sheet);
}

Implementation example of the interface for ProcessPatientFile:

@Service
public class ProcessPatientFile implements ProcessImportFiles {
@Override
public String getManagedFileType() {
return ImportTypes.PATIENTS.toString();
}
@Override
public void processImportFile(String fileName, Sheet sheet) {
System.out.println(&quot;I&#39;m processing a PATIENTS file&quot;);
}
}

Then change your ProcessFileFactory class this way :

    @Component
public class ProcessFileFactory {
private Map&lt;String, ProcessImportFiles&gt; importFileProcessorsByType;
public ProcessFileFactory(Collection&lt;ProcessImportFiles&gt; importFileProcessors) {
importFileProcessorsByType = importFileProcessors.stream().collect(Collectors.toMap(
ProcessImportFiles::getManagedFileType,
processor -&gt; processor
));
}
public ProcessImportFiles getInstance(String fileType) {
return importFileProcessorsByType.get(fileType);
}
}

Spring will automatically inject all beans implementing your interface into your factory. The internal map of your factory will retrieve the expected implementation according to the fileType.

By the way, if one day, you need to code new implementations of the ProcessImportFiles interface, your factory will work without any change.

huangapple
  • 本文由 发表于 2023年5月22日 04:37:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76301832.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定