I am trying to develop a dynamic Factory Class, where Factory Class does not know the factories, I will put the code so that the question is clearer.
App.java:
package br.com.factory;
public class App {
public static void main(String[] args) {
Product product = ProductFactory.createProduct("Xiaomi", "MI XX");
if (product != null) {
System.out.println(product.getName());
}
}
}
Product.java:
package br.com.factory;
public interface Product {
public String getName();
}
ProductFactory.java:
package br.com.factory;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ProductFactory {
private static HashMap<String, Map<String, Supplier<Product>>> registries = new HashMap<>();
private ProductFactory(){}
static {
ClassLoaderInitializer.initialize(ProductSupplier.class);
}
public static Product createProduct(String manufacturer, String model) {
Map<String, Supplier<Product>> manufacturers = registries.getOrDefault(manufacturer, null);
if (manufacturers != null){
Supplier<Product> suppliers = manufacturers.getOrDefault(model, null);
if (suppliers != null) {
return suppliers.get();
}
}
return null;
}
public static void registerFactory(String manufacturer, String model, Supplier<Product> supplier) {
registries
.computeIfAbsent(manufacturer, p -> new HashMap<>())
.putIfAbsent(model, supplier);
}
}
ProductSupplier.java:
package br.com.factory;
import java.util.function.Supplier;
public interface ProductSupplier extends Supplier<Product> {
}
XiaomiFactory.java:
package br.com.factory.xiaomi;
import br.com.factory.Product;
import br.com.factory.ProductFactory;
import br.com.factory.ProductSupplier;
public class XiaomiFactory implements ProductSupplier {
static {
ProductFactory.registerFactory("Xiaomi", "MI XX", XiamoMiXX::new);
}
private XiaomiFactory() {
}
@Override
public Product get() {
return new XiamoMiXX();
}
}
class XiamoMiXX implements Product {
@Override
public String getName() {
return "Xiaomi Mi XX";
}
}
ClassLoaderInitializer.java:
package br.com.factory;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
public class ClassLoaderInitializer {
private ClassLoaderInitializer() {
}
public static void initialize(Class<?> parentClass) {
try {
Enumeration<URL> resources = ClassLoaderInitializer.class.getClassLoader().getResources("");
while (resources.hasMoreElements()) {
URL nextElement = resources.nextElement();
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { nextElement });) {
URL[] urLs = urlClassLoader.getURLs();
for (URL u : urLs) {
try {
File file = new File(u.toURI());
initializeClass(file, file, urlClassLoader, parentClass);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void initializeClass(File root, File file, ClassLoader loader, Class<?> parentClass) {
if (file.isDirectory()) {
File[] listFiles = file.listFiles();
for (File f : listFiles) {
initializeClass(root, f, loader, parentClass);
}
} else {
if (file.getName().toUpperCase().endsWith(".class".toUpperCase())) {
try {
String fileName = file.toString();
String className = fileName.substring(root.toString().length() + 1,
fileName.toUpperCase().lastIndexOf(".class".toUpperCase())).replace(File.separator, ".");
Class<?> clazz = Class.forName(className, false, loader);
if (clazz.isAssignableFrom(parentClass)) {
Class.forName(className, true, loader);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
The problem occurs in the initializeClass method,
more precisely in:
Class <?> Clazz = Class.forName (className, false, loader);
if (clazz.isAssignableFrom (parentClass)) {
Class.forName (className, true, loader);
}
the idea of the ClassLoaderInitializer class is to initialize the classes that inherit from a given class "Class <?> parentClass"
but when I call the method
Class.forName (className, true, loader);
for the second time, passing true in the second parameter, the class is not initialized.
if I call:
Class.forName (className, true, loader);
directly, the initializeClass method will initialize all classes, what I would not like to happen.
is there any outside of me explicitly initializing (forcing) the class specifies?
ServiceLoader? I'm of the advice it's much better than trying to implement it yourself. All you have to do to register a factory is to create aMETA-INF/services/br.com.factory.ProductSupplierand add each factory supplier full class name on one line.if (clazz.isAssignableFrom (parentClass))the classjava.lang.Classinherits fromObject, nothing else. I don't think this is going to do what you want it to.