Spring autowiring parameterized generic types

2019-08-01 05:38发布

问题:

Spring 4 included major enhancements to generic type resolution, but I'm having trouble with autowiring a generic type when the type argument is parameterized on the containing bean class.

I am needing to track the status of jobs submitted to an outside service, and I want to create an entry for each job when it starts and clear or update it when I receive postbacks. I generally try to keep my persistence strategy separated from the service interface, so I have an interface JobStatus and a Spring Data Mongo class MongoJobStatus implements JobStatus. Since a job might fail before the outside service gets a chance to assign it an ID (e.g., an HTTP 502), I need to pass the JobStatus back to the service to identify for updates:

interface JobStatusService<S extends JobStatus> {
    S beginJob(...);
    S updateJobStatus(S targetJob, Status newStatus);
    void finishJob(S targetJob);
}

Accordingly, my Spring controller that handles firing off the jobs and recording the postbacks looks like this; the controller class carries a type parameter so that I can store the new status object and pass it back to the service:

@Controller
public class JobController<JS extends JobStatus> {
    @Autowired JobStatusService<JS> jobService;

    ... handler method ...
    JS status = jobService.createJob(info, goes, here);
    // submit job via HTTP
    jobService.updateJobStatus(status, Status.PROCESSING);
    ...
}

My MongoDB-backed implementation looks like this:

public class MongoJobStatusService implements JobStatusService<MongoJobStatus> {
    MongoJobStatus beginJob(...) {...}
    MongoJobStatus updateJobStatus(MongoJobStatus job, Status newStatus) {...}
}

When I try to launch, the Spring context fails with NoSuchBeanDefinitionException for JobStatusService. I have confirmed that if I set required=false, the MongoJobStatusService bean is properly component-scanned and installed in the context, but Spring doesn't seem to be able to understand that the class implements that parameterized generic interface.

Is there any way to specify to Spring that I need a bean implementing a generic interface with a type argument that is parameterized at the containing bean level instead of embedded as a literal type argument on the field level?

回答1:

Try to add:

 @Autowired 
 @Qualifier("mongoJobStatusService")
 JobStatusService<JS> jobService;

And specify the name of MongoJobStatusService bean.



回答2:

The Spring container does not appear to be able to solve for acceptable type parameters on the controller class, but it can solve for the injected field if those type parameters are supplied explicitly. I have been able to suboptimally work around the problem by subclassing the controller class with a literal type:

@Controller
public class MongoJobController<MongoJobStatus> {}

I have opened a JIRA issue for this and have a minimal example on GitHub.



回答3:

In fact Spring doesn't found matching Bean, maybe cause JobStatusService is to high level.

Try to put an other interface between MongoJobStatusService and JobStatusService, like this

public class MongoJobStatusService implements IMongoJobStatusService  {
..}

public interface IMongoJobStatusService  implements  JobStatusService<MongoJobStatus> {
...
}

I'had the same problem (Spring autowire trouble with generic parameter), and I solved it like that ...