问题
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:
Is there some other way I could use the container to achive dynamic lazy initalization depending on the user interaction?
Thank you.
回答1:
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!
回答2:
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.
回答3:
Sounds like a user can choose 1..N graphs of Objects and you only want to load the one that the user selects at runtime. If the graphs are known at design time but the user just chooses the one they want then it sounds to me like what you have is a bunch of ApplicationContexts and you only want to load the one ApplicationContext that the user selects at runtime. So why not just define the set of ApplicationContexts and then just instantiate the right one at runtime. Since Spring supports Java Config it might make sense to define these configs as Java classes so you can get inheritance and avoid cutting/pasting any code.
来源:https://stackoverflow.com/questions/10429572/spring-dynamic-injection-factory-like-pattern