I'm trying to use Guice and make all the bindings with the help of an XML file. In my module (let's say "CustomModule"), I would like to load an XML file and parse it to set all the bindings.
I'm able to load the XML file and retrieve all the values needed (below is an exemple of my XML file), but I'm unable to use those values to bind(interfaceValue).to(implementationValue);
.
What I've tried so far:
- Load the XML file, retrieve all the values and use them as :
bind(Class.fromName(Ivalue)).to(Class.fromName(Value));
whereIvalue
isInterfaceFoo
andValue
isFoo
. - Load the XML file as a Properties file and use
Names.bindProperties(binder(), properties);
. - Bind manually, which is not what I want.
Results:
- Doesn't work, because Guice cannot verify if the implementation is an implementation of the interface.
- Gives an error
No implementation for interface was bound
. - Works, but it is not wanted since I have to edit my
CustomModule
to change the bindings (in the case if I wantBar
to be the implementation ofInterfaceFoo
).
I've looked at this, but with not so much success since there is not very much documentation on it. I also looked for a solution here on SO, but most of the time the questions are about properties or the use of annotation.
Is there a simple way to specify the Interfaces / Implementations in a file and give it to Guice as a "configuration"?
My XML file:
<bindings>
<binding>
<interface>interfaces.IReaderService</interface>
<implementation>implementation.MemsReaderService</implementation>
</binding>
<binding>
<interface>interfaces.IReportService </interface>
<implementation>implementation.PdfReportService</implementation>
</binding>
<binding>
<interface>interfaces.ISerializerService </interface>
<implementation>implementation.JsonSerializerService</implementation>
</binding>
<binding>
<interface>interfaces.ILoggerService </interface>
<implementation>implementation.LoggerService</implementation>
</binding>
</bindings>
CustomModule.java:
public class GuiceModule extends AbstractModule{
private HashMap<String, String> classNames = new HashMap<String, String>();
public GuiceModule(){
}
@Override
protected void configure() {
/* === Test 1 [NOK : Module doesn't know if A implements B] */
for(Entry<String, String> entry : classNames.entrySet()){
try {
Class<?> itf = Class.forName(entry.getKey());
Class<?> concrete = Class.forName(entry.getValue());
bind(itf).to(concrete);
} catch (ClassNotFoundException ex) {
Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
}
}
/* === Test 2 [NOK : Not bound] */
try{
File file = new File(getClass().getResource("guiceBindings.xml").toURI());
Properties properties = new Properties();
properties.load(new FileReader(file));
Names.bindProperties(binder(), properties);
} catch (Exception ex) {
Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
}
/* === Test 3 [OK : Manual edition] */
bind(IReaderService.class).to(MemsReaderService.class);
bind(IReportService.class).to(PdfReportService.class);
bind(ISerializerService.class).to(JsonSerializerService.class);
bind(ILoggerService.class).to(LoggerService.class);
}
}
ServiceProvider.java:
public class ServiceProvider {
// declaration of the services available [FOR NOW]
@Inject IReaderService iReaderService;
@Inject IReportService iReportService;
@Inject ISerializerService iSerializerService;
@Inject ILoggerService iLoggerService;
public ServiceProvider(){
}
// getters of the services injected
public IReaderService getReaderService() {
return iReaderService;
}
public IReportService getReportService() {
return iReportService;
}
public ISerializerService getSerializerService() {
return iSerializerService;
}
public ILoggerService getLoggerService() {
return iLoggerService;
}
}
Guice is not really designed for this.
The idea is that by doing it in classes, you get all the power and flexibility of being able to do it in a class / @Provides
methods, Provider<T>
implementations, AOP, and so forth. It does have Named.bindProperties
, as you've observed, but this is not what you're trying to do for the reasons you've stated.
However, you can actually do method #1 if you're willing to use raw types, and then check the classes yourself. It's not the cleanest code, but note that your problem is the generic typing in Class<?>
, not Guice. Here's an example, with commented out pseudocode pointing out the changes you'd need to make to make this code work in production. I figure if you've gotten this far you can figure that out yourself though. Here's the code illustrating the idea:
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
public class DynamicBinding {
static final String interfaceExample = "DynamicBinding$Foo";
static final String implementationExample = "DynamicBinding$FooBar";
public static void main(String... args) throws ClassNotFoundException {
Injector inj = Guice.createInjector(new CustomModule());
Foo blue = inj.getInstance(Foo.class);
blue.doSomething();
}
static class CustomModule extends AbstractModule {
@Override
protected void configure() {
// for (Entry<interface, implementation> : xml file) {
bindFromStrings(interfaceExample, implementationExample);
// }
}
private void bindFromStrings(String interfaceClass, String implClass) {
try {
Class fooClass = Class.forName(interfaceClass);
// I recommend defining a custom exception here with a better message
if (!fooClass.isInterface()) {
throw new Exception("fooClass must be an interface!");
}
Class fooBarClass = Class.forName(implClass);
// I recommend defining a custom exception here with a better message
if (!fooClass.isAssignableFrom(fooBarClass)) {
throw new Exception("classes must be in same inheritance hierarchy");
}
bind(fooClass).to(fooBarClass);
} catch (Exception e) {
// Logger.getLogger().log(blah);
e.printStackTrace();
}
}
}
public static interface Foo {
void doSomething();
}
public static class FooBar implements Foo {
@Override
public void doSomething() {
System.out.println(this.getClass());
}
}
}
Output:
class DynamicBinding$FooBar
I found a solution to my problem, and as durron597 stated, the problem was coming from the generic Class<?>
and not Guice directly. I managed to have something working, but it has it's limitations. Here is the code commented for understanding purposes.
CustomModule.class
@Override
protected void configure() {
// for each entry we got in the xml file
for(Entry<String, String> entry : classNames.entrySet()){
try {
// we create the interface-typed class
Class<IInterface> itf = (Class<IInterface>) Class.forName(entry.getKey());
// and the implementation-typed class
Class<IImplementation> concrete = (Class<IImplementation>) Class.forName(entry.getValue());
// to finally bind them together
bind(itf).to(concrete);
} catch (ClassNotFoundException ex) {
Logger.getLogger(GuiceModule.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
As you can see, I don't use generic types anymore : What I've done is that I've created two new interfaces (IInterface
and IImplementation
). Every Interface I want to bind with an Implementation must extend IInterface
, and every Implementation must extend IImplementation
. This solution is working and injects the correct implementations, but implies to extend/implement interfaces used only for typing purposes.
Bonus : The XML parser, just in case someone is interested by the solution
private void parseXmlBindings(){
try {
File file = new File(getClass().getResource("guiceBindings.xml").toURI());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getElementsByTagName("binding");
for(int i = 0; i < nodeList.getLength(); i++){
Node node = nodeList.item(i);
if(node.getNodeType() == Node.ELEMENT_NODE){
Element binding = (Element) node;
NodeList itfList = binding.getElementsByTagName("interface");
Element itfElement = (Element) itfList.item(0);
NodeList itfValue = itfElement.getChildNodes();
NodeList concreteList = binding.getElementsByTagName("implementation");
Element concreteElement = (Element) concreteList.item(0);
NodeList concreteValue = concreteElement.getChildNodes();
classNames.put(itfValue.item(0).getNodeValue().trim(), concreteValue.item(0).getNodeValue().trim());
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
From what I know about Guice, most of the consistency checks for bindings are made at compile time, so way #1 won't work as it is. I think you could try with just-in-time bindings (https://github.com/google/guice/wiki/JustInTimeBindings) and/or providers (https://github.com/google/guice/wiki/InjectingProviders), but in the end I don't think you'll achieve the goal. It seems to me that the biggest limitation is that you have to specify the interfaces you want to bind explicitly in the source code, and then you can (maybe) create a provider that parses your xml and returns the correct implementation via Class.forName. I don't know if this satisfies your needs, but maybe it's a starting point.
来源:https://stackoverflow.com/questions/30372936/guice-set-bindings-from-an-xml-file