Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring dynamic injection, factory-like pattern

A continuation from Dependency injection, delayed injection praxis. I have the Main class:

package test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Scanner;

@Component
public class Main {
    @Autowired
    private StringValidator stringValidator;

    @Autowired
    private StringService stringService;

    @Autowired
    private ValidationService validationService;

    public void main() {
        scanKeyboardCreateLists();

        stringValidator.validate();

        final List<String> validatedList = stringValidator.getValidatedList();
        for (String currentValid : validatedList) {
            System.out.println(currentValid);
        }
    }

    private void scanKeyboardCreateLists() {
        //Let's presume the user interacts with the GUI, dynamically changing the object graph...
        //Needless to say, this is past container initialization...
        Scanner scanner = new Scanner(System.in);
        int choice = scanner.nextInt();

        //Delayed creation, dynamic
        if (choice == 0) {
            stringService.createList();
            validationService.createList();
        } else {
            stringService.createSecondList();
            validationService.createSecondList();
        }
    }

    public static void main(String[] args) {
        ApplicationContext container = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml");
        container.getBean(Main.class).main();
    }
}

And the object graph is dynamically created, depending on the user interaction. I solved the application coupling, allowing me to test this very simply. Also, since the lists are maintained by the container, the dynamic nature of this application(and every other) is irrelevant, since they can be requested any time the application needs them, maintaining their elements.

The rest of the code is here:

package test;

import java.util.List;

public interface Stringable {
    List<String> getStringList();
}

package test;

import org.springframework.stereotype.Component;

import java.util.ArrayList;

@Component
public class StringList extends ArrayList<String> {
}

package test;

import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

@Component
public class StringService implements Stringable {

    private List<String> stringList;

    @Inject
    public StringService(final ArrayList<String> stringList) {
        this.stringList = stringList;
    }

    //Simplified
    public void createList() {
        stringList.add("FILE1.txt");
        stringList.add("FILE1.dat");
        stringList.add("FILE1.pdf");
        stringList.add("FILE1.rdf");
    }

    public void createSecondList() {
        stringList.add("FILE2.txt");
        stringList.add("FILE2.dat");
        stringList.add("FILE3.pdf");
        stringList.add("FILE3.rdf");
    }

    @Override
    public List<String> getStringList() {
        return stringList;
    }
}

package test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class StringValidator {
    private List<String> stringList;
    private List<String> validationList;

    private final List<String> validatedList = new ArrayList<String>();

    @Autowired
    public StringValidator(final ArrayList<String> stringList,
                           final ArrayList<String> validationList) {
        this.stringList = stringList;
        this.validationList = validationList;
    }

    public void validate() {
        for (String currentString : stringList) {
            for (String currentValidation : validationList) {
                if (currentString.equalsIgnoreCase(currentValidation)) {
                    validatedList.add(currentString);
                }
            }
        }
    }

    public List<String> getValidatedList() {
        return validatedList;
    }
}

package test;

import java.util.List;

public interface Validateable {
    List<String> getValidationList();
}

package test;

import org.springframework.stereotype.Component;

import java.util.ArrayList;

@Component
public class ValidationList extends ArrayList<String> {
}

package test;

import org.springframework.stereotype.Component;

import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;

@Component
public class ValidationService implements Validateable {

    private List<String> validationList;

    @Inject
    public ValidationService(final ArrayList<String> validationList) {
        this.validationList = validationList;
    }

    //Simplified...
    public void createList() {
        validationList.add("FILE1.txt");
        validationList.add("FILE2.txt");
        validationList.add("FILE3.txt");
        validationList.add("FILE4.txt");
    }

    public void createSecondList() {
        validationList.add("FILE5.txt");
        validationList.add("FILE6.txt");
        validationList.add("FILE7.txt");
        validationList.add("FILE8.txt");
    }

    @Override
    public List<String> getValidationList() {
        return validationList;
    }
}

Does anyone know how would I solve the method call createList() or createSecondList() - without using the constructor which pretty much forces the design. I was thinking of a factory, but a factory for every class in a project of a bigger magnitude doesn't seem like a good idea.

Something like:

<bean ... factory-method="..." depends-on="..." lazy-init="..."/>

And in the factory method instantiate the class and call the method createList(). Or call it like this, from some method - which again looks bad, forcing the method to have the responsibility to instantiate the object graph.

The picture of the runtime dependencies that I want to resolve in runtime is bellow:

enter image description here

Is there some other way I could use the container to achive dynamic lazy initalization depending on the user interaction?

Thank you.

like image 666
pfh Avatar asked May 03 '12 10:05

pfh


2 Answers

If you want some member of your class to be dynamically initialized\populated on every call to the corresponding getter, you can try the Lookup Method Injection. Read pp. 3.3.4.1 here.

So even if the class that contains the dynamic member was created in scope=singletone (the default for spring bean container) every time you will accessthe field that has a lookup method assigned, you will get an appropriate object according to the business logic implemented inside the lookup method. In your case the list is an interface so you can easily implement the validation inside your lookup method and return a validated list.

Edit:

I found better example in Spring documentation - I think it is very clear. Take a look at "3.4.6.1 Lookup method injection"

When you configure the Main class assign a lookup method to its List member - it will be called whenever you need a new instance of the List bean.

Good luck!

like image 87
aviad Avatar answered Oct 09 '22 00:10

aviad


Spring is designed for re-usable component injection, not for business data manipulation and injection.

Indeed some data are used in dependency injection, but only to configure components behavior, not to create business data holder.

By the way, the following option may be used in your case: thanks a BeanFactory with BeanFactoryAware interface and the use of scope="prototype", you can generate a bean by invoking getBean() like in that example or from that other question: creating bean on demand.

An alternative option if you have a limited number of beans to prepare is to use generic bean creation the same way lacking beans are mocked

Now consider that Spring never garbage collects beans in its Context. So it is risky for memory consumption to create Spring beans to hold business data.

If your aim is different (I hope so), maybe you are trying to implement by your own a multi-tenant support. Spring provides tenancy in case you have different business context to implement with specific components or behaviors.

like image 45
Yves Martin Avatar answered Oct 08 '22 23:10

Yves Martin