Search code examples
javajersey-2.0hk2

Dynamicaly instanciate class from name with injector


Context

I develop, for my company a software that classifies phishing and malware containing website thanks to multiple feature extraction algorithm.

Once features are extracted we use a pool of empirical and machine learning classifiers. We choose among them thanks to election function of our own.

the code

Basically we have our classifier classes that implement the AnalysisFunction contract.

public abstract class AnalysisFunction {
    abstract public StatusType analyze(List<TokenEntity> tokens);
    abstract public double getPhishingProbability(List<TokenEntity> tokens);
}

Our pool of classifier is contained by a "pool" that implements AnalysisFunction.

public class PoolAnalysisFunction extends AnalysisFunction{
    private final List<AnalysisFunction> candidates;
    private final ChoiceFunction choice;
    private static final Logger LOG = LogManager.getLogger(PoolAnalysisFunction.class);

    public PoolAnalysisFunction(List<AnalysisFunction> candidates, ChoiceFunction choice) {
        this.candidates = candidates;
        this.choice = choice;
    }

    @Override
    public StatusType analyze(List<TokenEntity> tokens) {
        try {
            return choice.chooseAmong(candidates, tokens).analyze(tokens);
        } catch (ImpossibleChoiceException e){
            LOG.fatal("Not enough analysis function.", e);
            return StatusType.CLEAN;
        }
    }

    @Override
    public double getPhishingProbability(List<TokenEntity> tokens) {
        try {
            return choice.chooseAmong(candidates, tokens).getPhishingProbability(tokens);
        } catch (ImpossibleChoiceException e){
            LOG.fatal("Not enough analysis function.", e);
            return 0;
        }
    }
}

To ease the deployment and testing of new function, we want to make our pool fully customizable and instanciate every function by its name. To achieve this purpose we have a key in our property file that is like analysis.pool.functions=com.vadesecure.analysis.empirical.Function1,com.vadesecure.analysis.machine.AutomaticClassifier1.

I want to instantiate my functions thanks to that. My problem is that those classifiers depend on different things such as custom configuration object and machine learning model. I would like to inject those dependencies that are already bound in my hk2 injector.

import org.glassfish.hk2.api.Factory;
public class PoolFunctionFactory implements Factory<AnalysisFunction> {
    private final PoolAnalysisParameters parameters;
    private static final Logger LOG = LogManager.getLogger(PoolAnalysisFunction.class);
    @Inject
    public PoolFunctionFactory(PoolAnalysisParameters parameters) {
        this.parameters = parameters;
    }

    @Override
    public AnalysisFunction provide() {
        try {

            Class<?> choice = Class.forName(parameters.getChoiceFunctionFQDN());
            ChoiceFunction choiceFunction = new PhishingPriorityChoiceFunction(); // default choice
            if(choice.getSuperclass().isInstance(ChoiceFunction.class)){
                choiceFunction = (ChoiceFunction) choice.newInstance();
            }
            List<AnalysisFunction> analysisFunctions = new LinkedList<>();
            // I want to instantiate here
            }
            return new PoolAnalysisFunction(analysisFunctions, choiceFunction);
        } catch (ClassNotFoundException|IllegalAccessException|InstantiationException e){
            LOG.fatal(e, e);
        }

        return null;
    }

    @Override
    public void dispose(AnalysisFunction analysisFunction) {
        LOG.trace(String.format("%s end of life", analysisFunction));
    }
}

On example of model-dependant classifier is :

public class SVMF2AnalysisFunction extends AnalysisFunction {
    private final SVMContainer modelContainer;
    private double probability = 0.0;
    private double threshold = 0.9;
    @Inject // i build this model in a parallel thread
    public SVMF2AnalysisFunction(SVMContainer modelContainer) {
        this.modelContainer = modelContainer;
    }

    @Override
    public StatusType analyze(List<TokenEntity> tokens) {
        if (modelContainer.getModel() == null) {
            return null;
        }
        probability = modelContainer.getModel().analyse(tokens.stream());
        return probability >= threshold ? StatusType.PHISHING : StatusType.CLEAN;
    }

    @Override
    public double getPhishingProbability(List<TokenEntity> tokens) {
        return probability;
    }
}

How can I achieve those instanciations.

My first approach was to inject the serviceLocator but i found no documentations for doing this and a colleague said me it was not good.

He told be to document myself about proxies but it doesn't seem to be a good thing for me or perhaps I missed something.


Solution

  • You could just configure all this in your binder. This way you don't need to worry about trying to instantiate everything yourself. Just let HK2 do all the work

    @Override
    protected void configure() {
       bindAsContract(PoolAnalysisFunction.class).in(Singleton.class);
    
       bind(choiceFnClass).to(ChoiceFunction.class);
    
       for (Class<AnalysisFunction> analysisFnClass: analyisFnClasses) {
           bind(analysisFnClass).to(AnalysisFunction.class).in(Singleton.class);
       }
    }
    

    Then you can just inject everything into the PoolAnalysisFunction class, without the need to use a factory.

    @Inject
    public PoolAnalysisFunction(IterableProvider<AnalysisFunction> candidates,
                                ChoiceFunction choice) {
        this.choice = choice;
    
        this.candidates = new ArrayList<>();
        candidates.forEach(this.candidates::add);
    }
    

    Notice the IterableProvider class. This is an HK2 class for injecting multiple services bound to the same contract.

    Or if you want to use the factory, you could, and just inject the functions into the factory. That way you can make the PoolAnalysisFunction class independent of an HK2 classes (i.e. the InjectableProvider).