Howe can I work around with singleton and component import?

I’m writing some tool class which should be a singleton. However, the functionality of the class requires dependencies on some service interfaces, i.e. PullRequestService. I’m facing the dilemma about how can I make it both singleton and correctly import those services.

When I was reading some documents of Java Bean, I realized that the Inject annotation can also be applied to a public setter. I tried writing something employing the setter, but it’s no use. Any ideas?

public class ConflictStrategy extends AbstractMergeStrategy implements IMergeStrategy {

    static public final IMergeStrategy instance = new ConflictStrategy();

    @ComponentImport
    private PullRequestService pullRequestService; // This is null in runtime

    private ConflictStrategy() {}

    @Inject
    public void setComponents(PullRequestService pullRequestService) {
        this.pullRequestService = pullRequestService;
    }

    public MergeCheckResponse inspect(MergeCheckRequest request) {
        PullRequest pullRequest = request.getPullRequest();
        if (pullRequestCanMerge(pullRequest).isConflicted()) {
            return buildResponse(false, "Pull request conflicts");
        } else {
            return buildResponse(true, "No merge conflicts");
        }
    }

    private PullRequestMergeability pullRequestCanMerge(PullRequest pullRequest) {
        final long pullRequestID = pullRequest.getId();
        final int repoID = pullRequest.getFromRef().getRepository().getId();
        return this.pullRequestService.canMerge(repoID,pullRequestID);
    }

}

Hi @tommy.zhang,
You can define your tool class as a @Component, have a look at this tutorial for how to use @Component and @Autowired. Spring scanning has been defined here. The complete tutorial could be found here.
Hope this helps.
Cheers,
Justin

1 Like

Hi @jthomas,
Thanks for your help. However, http://www.atlassian.com/schema/atlassian-scanner/atlassian-scanner.xsd is 404 now

Sincerely,
Tommy

To make it clear: that xsd file is the file listed in the given example of plugin-context.xml. And @autowired as well as @ComponentImport on setters help nothing, which implies pullRequestService is still null. I doubt the 404 of the xsd is to blame.
The following code is what I have employed now:

@Component("ConflictStrategy")
public class ConflictStrategy extends AbstractMergeStrategy implements IMergeStrategy {

    static public final IMergeStrategy instance = new ConflictStrategy();

    private PullRequestService pullRequestService;

    private ConflictStrategy() {}

    @Autowired
    public void setComponents(@ComponentImport PullRequestService pullRequestService) {
        this.pullRequestService = pullRequestService;
    }

    public MergeCheckResponse inspect(MergeCheckRequest request) {
        PullRequest pullRequest = request.getPullRequest();
        if (pullRequestCanMerge(pullRequest).isConflicted()) {
            return buildResponse(false, "Pull request conflicts");
        } else {
            return buildResponse(true, "No merge conflicts");
        }
    }

    private PullRequestMergeability pullRequestCanMerge(PullRequest pullRequest) {
        final long pullRequestID = pullRequest.getId();
        final int repoID = pullRequest.getFromRef().getRepository().getId();
        return this.pullRequestService.canMerge(repoID,pullRequestID);
    }

}

I am not sure static public final IMergeStrategy instance = new ConflictStrategy(); and setting the constructor as private is the correct way of defining a component.
Have a look at this tutorial to see how a Component can be defined and usage of TodoService could be found here.
After changes your class should look something like this:

@Component("ConflictStrategy")
public class ConflictStrategy extends AbstractMergeStrategy implements IMergeStrategy {

    private PullRequestService pullRequestService;

   @Autowired
    public ConflictStrategy(@ComponentImport PullRequestService pullRequestService) {
         this.pullRequestService = pullRequestService;
    }
....

What are you trying to accomplish with this plugin?

Hi @jthomas,

Thanks for your reply. I’m writing a plugin to merge a pull-request impersonating a designated ApplicationUser if some checks performed on that PR get satisfied. One of those checks is whether PR conflicts or not. But that’s not the main problem I’m facing.
Yes, I understand making the constructor public and add @Autowired annotation on the constructor as well as add @ComponentImport annotation on the constructor parameters is the working solution on how to inject service components.

However, according to the Javadoc of Spring, it is possible to add @Autowired on public setter methods to import the dependent service components. You may feel wired that why I want to use a setter to import components rather than directly do it on a public constructor. While that’s related to the singleton design pattern.

One common way to implement the singleton pattern in Java is declaring a private static field referring to the only instance of the class, designing a public static method called getInstance() to instantiate or retrieve the already instantiated instance. To prevent getting an instance of the singleton class without getInstance(), the constructor of the class should be private.

My version of the singleton classes is not that standard. Rather than declare the static instance field as private, I declared it as public to get rid of the getInstance() method. However, the instance still requires a correct instantiation or a dummy instantiation followed by a correct initialization. The former requires @Autowired and @ComponentImport on a public constructor, the latter requires a dummy constructor and @Autowired as well as @ComponentImport on a public setter.

But, the @Autowired and @ComponentImport on the public setter does not work.

Sincerely,
Tommy

I hope this will help:

  1. Autowiring will work only for components managed by Spring.
  2. Spring components are singletons (if not specified otherwise)

Hi @a.belostotskiy

Thanks for your reply. Yes, that’s helpful for me. I was thinking about getting rid of the static field as it initialized before those Java Beans.

Tommy