2

First, please let me introduce a minimal scene demo to explain the problem.

Let's say i have a strategy pattern interface.

public interface CollectAlgorithm<T> {
    public List<T> collect();
}

And a implementation of this strategy, the ConcreteAlgorithm.

public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

    @Resource
    QueryService queryService;

    @Override
    public List<Integer> collect() {
        // dummy ...
        return Lists.newArrayList();
    }
}

As you can see, the implementation depend on some query operation provided by a @Service component.

The ConcreteAlgorithm class will be created by new in some places, then the collect method will be called.

I've read some related link like Spring @Autowired on a class new instance, and know that the above code cannot work, since the instance created by new has a @Resource annotated member.

I'm new to Spring/Java, and i wonder if there are some ways, or different design, to make scene like above work.

I've thought about use factory method, but it seems that it will involve many unchecked type assignment since i provided a generic interface.


UPDATE

To make it more clear, i add some detail about the problem.

I provide a RPC service for some consumers, with an interface like:

public interface TemplateRecommendService {
    List<Long> recommendTemplate(TemplateRecommendDTO recommendDTO);
}

@Service
public class TemplateRecommandServiceImpl implements TemplateRecommendService {

    @Override
    public List<Long> recommendTemplate(TemplateRecommendDTO recommendDTO) {
        TemplateRecommendContext context = TemplateRecommendContextFactory.getContext(recommendDTO.getBizType());
        return context.process(recommendDTO);
    }
}

As you can see, i will create different context by a user pass field, which represent different recommendation strategy. All the context should return List<Long>, but the pipeline inside context is totally different with each other.

Generally there are three main stage of the context process pipeline. Each stage's logic might be complicated and varied. So there exists another layer of strategy pattern.

public abstract class TemplateRecommendContextImpl<CollectOut, PredictOut> implements TemplateRecommendContext {
    private CollectAlgorithm<CollectOut> collectAlgorithm;

    private PredictAlgorithm<CollectOut, PredictOut> predictAlgorithm;

    private PostProcessRule<PredictOut> postProcessRule;

    protected List<CollectOut> collect(TemplateRecommendDTO recommendDTO){
        return collectAlgorithm.collect(recommendDTO);
    }

    protected List<PredictOut> predict(TemplateRecommendDTO recommendDTO, List<CollectOut> predictIn){
        return predictAlgorithm.predict(recommendDTO, predictIn);
    }

    protected List<Long> postProcess(TemplateRecommendDTO recommendDTO, List<PredictOut> postProcessIn){
        return postProcessRule.postProcess(recommendDTO, postProcessIn);
    }

    public /*final*/ List<Long> process(TemplateRecommendDTO recommendDTO){
        // pipeline:
        // dataCollect -> CollectOut -> predict -> Precision -> postProcess -> Final
        List<CollectOut> collectOuts = collect(recommendDTO);
        List<PredictOut> predictOuts = predict(recommendDTO, collectOuts);
        return postProcess(recommendDTO, predictOuts);
    }
}

As for one specific RecommendContext, its creation likes below:

public class ConcreteContextImpl extends TemplateRecommendContextImpl<GenericTempDO, Long> {
    // collectOut, predictOut
    ConcreteContextImpl(){
        super();
        setCollectAlgorithm(new ShopDecorateCrowdCollect());
        setPredictAlgorithm(new ShopDecorateCrowdPredict());
        setPostProcessRule(new ShopDecorateCrowdPostProcess());
    }
}
user8510613
  • 1,242
  • 9
  • 27
  • "The ConcreteAlgorithm class will be created by new in some places" why? – Andrew Tobilko Aug 12 '19 at 09:52
  • 1
    Why not? Maybe he is creating console to implement such algortihms and application will create dynamically such services, based on the input from user. Is there anything wrong with creating beans in a runtime? – m.antkowicz Aug 12 '19 at 10:03
  • @m.antkowicz Yes, the idea of using `new` within a Spring-based application is wrong – Andrew Tobilko Aug 12 '19 at 10:20
  • @Andrew Tobilko Why? I don't know about aby rule like 'never use `new` in Spring' - for example how would you like to unit test your service classes without creating them? – m.antkowicz Aug 12 '19 at 10:23
  • *nd know that the above code cannot work, since the instance created by new has a @Resource annotated member.* Strictly speaking it is false. You just have to provide required instance yourself. Or do the autowiring after instance creation like shown in post you have mention. – Antoniossss Aug 12 '19 at 10:28
  • @m.antkowicz by injecting mocks, not by creating them manually – Andrew Tobilko Aug 12 '19 at 10:47
  • @Andrew Tobilko and how you will inject those mocks in **unit tests**? – m.antkowicz Aug 12 '19 at 10:54
  • @AndrewTobilko I am not familiar with Spring. If there are some better design i am glad to hear, could you explain it more specifically(in this scenario) – user8510613 Aug 12 '19 at 11:01
  • @user8510613 let's start off with "why are you using Spring"? – Andrew Tobilko Aug 12 '19 at 11:05
  • @AndrewTobilko I have to pay for my clothes, my food, my ghetto's bed and my traffic payment, that's why. – user8510613 Aug 12 '19 at 11:20
  • @user8510613 Could you show where you create it manually and where you let Spring inject it? – Andrew Tobilko Aug 12 '19 at 11:24
  • @user8510613 The whole point of using Spring is to let Spring create instances and resolve dependencies among them for you. You would never need to use `new` in a Spring app. – Andrew Tobilko Aug 12 '19 at 11:30
  • @m.antkowicz depends on the framework I chose. I don't write `new MockService` anyways. – Andrew Tobilko Aug 12 '19 at 11:32
  • @AndrewTobilko to simplify the question, you can think that i provide a RPC service interface to some consumer, whom ask for recommendation system service. To decide which recommendation context will be used, the consumer will pass a field to point it out. As for my side, i will create different context by this field. The interface always return ```List```, but the context's process pipeline may vary from each other, and the ```CollectAlgorithm``` is just a phase of the pipeline, that's why i have to declare it generically. – user8510613 Aug 12 '19 at 11:36

2 Answers2

3

Instead od using field oriented autowiring use constructor oriented one - that will force the user, creating the implementation instance, to provide proper dependency during creation with new

@Service
public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {
    private QueryService queryService;

    @Autowired // or @Inject, you cannot use @Resource on constructor
    public ConcreteAlgorithm(QueryService queryService) {
        this.queryService = queryService;
    }

    @Override
    public List<Integer> collect() {
        // dummy ...
        return Lists.newArrayList();
    }
}
m.antkowicz
  • 13,268
  • 18
  • 37
  • His problem is that he cannot use `new` instantiated service because of missing `queryService` - using my approach he won't be able to create instance without the dependency which is compliant with IOC - and Spring will still provide bean automatically during context creation – m.antkowicz Aug 12 '19 at 09:59
  • Which bean will be provided by Spring in your example? – dunni Aug 12 '19 at 10:12
  • `QueryService`, when you will autowire `ConcreteAlgorithm` somewhere else - seriously where is the problem? Did I write something misleading or wrong? – m.antkowicz Aug 12 '19 at 10:18
  • @AndrewTobilko it allows to provide dependency manually as ctor argument during creation. – Antoniossss Aug 12 '19 at 10:28
  • 1
    @m.antkowicz By xxx version of Spring, ctor annotation can be ommited if it is the only contructor defined. – Antoniossss Aug 12 '19 at 10:31
  • So the user of ```ConcreteAlgorithm``` can simply ```new``` it? Will this method have any performance problem? – user8510613 Aug 12 '19 at 11:04
  • That depends on your application - if service creation for some reason will take long (let say it need to get some huge data from `queryService` and build some kind od cache) it will definitely cause performance problem - handling this by Spring (usually, if it is singleton) means that it will be performed during app startup – m.antkowicz Aug 12 '19 at 11:07
  • @m.antkowicz well, the method of ```queryService``` is only used in ```collect``` method. From your last sentence, do you mean that it will only slow the startup phase performance but not every time when the instance of ```ConcreteAlgorithm``` is created? – user8510613 Aug 12 '19 at 11:26
  • @m.antkowicz ehhh, how should i create a ```ConcreteAlgorithm``` instance, still by ```new ConcreteAlgorithm```? What about the argument? – user8510613 Aug 13 '19 at 02:32
  • **that depends on your application** if you need to create it by `new` you can do this as I described. The argument you van autowire outside ot create instancje and pass it - again, I don't know because this is specific to your case – m.antkowicz Aug 13 '19 at 06:09
1

There are 4 (+1 Bonus) possible approaches I can think of, depending on your "taste" and on your requirements.


1. Pass the service in the constructor.

When you create instances of your ConcreteAlgorithm class you provide the instance of the QueryService. Your ConcreteAlgorithm may need to extend a base class.

    CollectAlgorithm<Integer> myalg = new ConcreteAlgorithm(queryService);
    ...

This works when the algorithm is a stateful object that needs to be created every time or, with some variations, when you actually don't know the algorithm at all as it comes from another library (in which case you might have a factory or, in rare cases which most likely don't fit your scenario, create the object through reflection).

2. Turn your algorithm into a @Component

Annotate your ConcreteAlgorithm with the @Component annotation and then reference it wherever you want. Spring will take care of injecting the service dependency when the bean is created.

    @Component
    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        @Resource
        QueryService queryService;

        ....

    }

This is the standard and usually preferred way in Spring. It works when you know ahead of time what all the possible algorithms are and such algorithms are stateless. This is the typical scenario. I don't know if it fits your needs but I would expect most people to be looking for this particular option.

Note that in the above scenario the recommendation is to use constructor-based injection. In other words, I would modify your implementation as follows:

    @Component
    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        final QueryService queryService;

        @Autowired
        public ConcreteAlgorithm(QueryService queryService) {
            this.queryService = queryService;
        }

        @Override
        public List<Integer> collect() {
            // dummy ...
            return Lists.newArrayList();
        }
    }

On the most recent versions of Spring you can even omit the @Autowired annotation.

3. Implement and call a setter

Add a setter for the QueryService and call it as needed.

    CollectAlgorithm<Integer> myalg = new ConcreteAlgorithm();
    myalg.setQueryService(queryService);
    ...

This works in scenarios like those of (1), but lifts you from the need of passing parameters to the constructor, which "may" help getting rid of reflection in some cases. I don't endorse this particular solution however as it forces to know that you have to call the setQueryService method prior to invoking other methods. Quite error-prone.

4. Pass the QueryService directly to your collect method.

Possibly the easiest solution.

    public interface CollectAlgorithm<T> {
        public List<T> collect(QueryService queryService);
    }

    public class ConcreteAlgorithm implements CollectAlgorithm<Integer> {

        @Override
        public List<Integer> collect(QueryService queryService) {
            // dummy ...
            return Lists.newArrayList();
        }
    }

This works well if you want your interface to be a functional one, to be used in collections.

Bonus: Spring's SCOPE_PROTOTYPE

Spring doesn't only allow to instantiate singleton beans but also prototype beans. This effectively means it will act as a factory for you.

I will leave this to an external example, at the following URL:

https://www.boraji.com/spring-prototype-scope-example-using-scope-annotation

This "can" be useful in specific scenarios but I don't feel comfortable recommending it straight away as it's significantly more cumbersome.

Filippo Possenti
  • 1,300
  • 8
  • 18
  • Thanks for your advice. And what about the performance problem when instantiate ```ConcreteAlgorithm``` each time? – user8510613 Aug 12 '19 at 13:18
  • For option (2) the performance hit is moved on application startup as the algorithm objects are singletons. For option (3) and (4) the performance hit depends on the constructor of your algorithm (keep it as simple as possible). For option (1) it depends on the constructor and, if you end up using it, on the reflection overhead (which is probably the biggest hit). The first rule of optimisation however is "don't optimise". This to say that if your code is simple and well written (no fancy stuff like reflection) you can postpone thinking about performance till there is an actual problem. – Filippo Possenti Aug 12 '19 at 13:25
  • For option(2), how should i create a ```ConcreteAlgorithm```? Still by ```new ConcreteAlgorithm()```? – user8510613 Aug 13 '19 at 01:51
  • No, with option (2) you then inject your algorithm like you did for your service (that is: with ```@Resource``` or ```@Autowired```, depending on your needs. You might need to check also the ```@Qualifier``` annotation. Of course option (2) works only if you can share your algorithm instances (that is: they are stateless). – Filippo Possenti Aug 13 '19 at 07:35