wizzdi / flexicore-boot

flexicore-boot contains the bare necessity for plugin loading
Apache License 2.0
6 stars 1 forks source link

Autowire a List of Classes from diffrent Plugins #2

Closed paulsen-it closed 7 months ago

paulsen-it commented 8 months ago

Hello Asaf,

so far everything has gone great with FlexiCore. But now I have a problem again. I have 3 plugins.

Plugin A with two classes

@Component
@ComponentScan("framework.plugin")
@Extension
public class ClassA1 implements MyInterface {}

@Component
@ComponentScan("framework.plugin")
@Extension
public class ClassA2 implements MyInterface {}

Plugin B with a class

@Component
@ComponentScan("framework.plugin")
@Extension
public class ClassB1 implements MyInterface {}

Plugin C where the plugins A and B are to be autowired in a list

@Service
@ComponentScan("framework.plugin")
@Extension
@Getter
public class PluginC implements Plugin {

    @Autowired(required = false)
    private List<MyInterface> pluginList = new ArrayList<>();

    @PostConstruct
    public void init() {
        LOG.debug("This instance: " + this);
        LOG.debug("Found Classes:");
        if (pluginList != null) {
            for (MyInterface plugin : pluginList) {
                LOG.debug(plugin.getClass()
                              .getName());
            }
        }
        LOG.debug("----------");
    }

    /* For testing, but unfortunately does nothing */
    public void setPluginList(List<MyInterface> plugins) {
        this.pluginList.addAll(plugin);
    }
}

At the end, plugin C should contain 3 entries in the list "pluginList". ClassA1, ClassA2, ClassB1 Unfortunately, only the classes from the plugin that is loaded last are entered in the list. The ones from the first plugin are not in the list. Although they are loaded. The constructor is called and the init() method is also executed, but always shows only the currently loaded plugin. As if the list is always completely overwritten. I have also changed the setter, but unfortunately this does not help.

If I change the order in which the plugins are loaded. E.g. by renaming them. Then the classes are entered in the list from the last plugin. So it really seems to be the loading order.

asafbennatan commented 8 months ago

@paulsen-it your components should implement Plugin interface , is that the case?

paulsen-it commented 8 months ago

Yes, MyInterface extends Plugin

paulsen-it commented 8 months ago

And of Course PluginC

asafbennatan commented 8 months ago

can you try autowiring spring's ObjectProvider<MyInterface> instead of List<MyInterface>

paulsen-it commented 8 months ago

I will try it out and report back

paulsen-it commented 7 months ago

Sorry it took so long. I had to do a few other things before I could test this.

Now I have tried it. I get now 7 results. Unfortunately they are all hidden as proxy and stream. Here is a debug excerpt:

Name: com.wizzdi.flexicore.boot.base.init.FlexiCoreDependencyObjectProvider
Number: 7
InProxy: jdk.proxy10.$Proxy516
Unproxy: java.util.stream.ReferencePipeline$2
test: de.xyz.ClassA1
InProxy: jdk.proxy4.$Proxy382
Unproxy: java.util.stream.ReferencePipeline$2
InProxy: jdk.proxy5.$Proxy383
Unproxy: java.util.stream.ReferencePipeline$2
InProxy: jdk.proxy6.$Proxy384
Unproxy: java.util.stream.ReferencePipeline$2
InProxy: jdk.proxy7.$Proxy385
Unproxy: java.util.stream.ReferencePipeline$2
InProxy: jdk.proxy8.$Proxy381
Unproxy: java.util.stream.ReferencePipeline$2
InProxy: jdk.proxy9.$Proxy472
Unproxy: java.util.stream.ReferencePipeline$2
test: de.xyz.ClassB1

Both plugins now seem to be installed.

Unfortunately, I have not yet been able to find out what the other objects are.

paulsen-it commented 7 months ago

I have now got it solved so that it runs with as well as without proxy. But it is a bit strange. Is there a simpler solution?

for (MyInterface myInterface : this.foundPlugins.stream().toList()) {

    // Unproxy
    if (AopUtils.isAopProxy(myInterface) && myInterface instanceof Advised) {
        Object target = ((Advised) myInterface).getTargetSource().getTarget();

        // Unstreaming
        if (target instanceof Stream<?>) {
            List<?> myInterfaceObjList = ((Stream<?>) target).toList();
            for (Object myInterfaceObj : myInterfaceObjList) {
                this.pluginList.add((MyInterface) myInterfaceObj);
            }
        }
    } else {
        this.pluginList = this.foundPlugins.stream().toList();
    }
}
asafbennatan commented 7 months ago

can you attach the full code please?

paulsen-it commented 7 months ago
@Service
@ComponentScan("framework.plugin")
@Extension
@Getter
public class PluginC implements Plugin {

    @Autowired(required = false)
    @Lazy
    private ObjectProvider<MyInterface> foundPlugins;

    private List<MyInterface> pluginList = new ArrayList<>();

    @PostConstruct
    public void init() {

        for (MyInterface myInterface : this.foundPlugins.stream().toList()) {

            // Unproxy
            if (AopUtils.isAopProxy(myInterface) && myInterface instanceof Advised) {
                Object target = ((Advised) myInterface).getTargetSource().getTarget();

                // Unstreaming
                if (target instanceof Stream<?>) {
                    List<?> myInterfaceObjList = ((Stream<?>) target).toList();
                    for (Object myInterfaceObj : myInterfaceObjList) {
                        this.pluginList.add((MyInterface) myInterfaceObj);
                    }
                }
            } else {
                this.pluginList = this.foundPlugins.stream().toList();
            }
        }

        LOG.debug("Found Classes:");
        if (pluginList != null) {
            for (MyInterface plugin : pluginList) {
                LOG.debug(plugin.getClass()
                              .getName());
            }
        }
        LOG.debug("----------");
    }
}
asafbennatan commented 7 months ago

please try these two things:

  1. remove the @Lazy annotation (ObjectProvider is already lazy by definition)
  2. remove the @PostConstruct and use implements InitializingBean

instead of printing the classes which will be subjected to proxying (proxying is a normal thing) simply try to call this.foundPlugins.stream().foEach(f->f.myFunction()) but if i recall correctly there shouldnt be any issue with calling getClass() aswell.

also there is probably no reason to keep a list of found plugins , you could just use foundPlugins directly.

paulsen-it commented 7 months ago

If I use foundPlugins directly I get 7 proxy objects. Most of them are completely empty. To have only my plugins, I use the PostConstruct and filter out the empty objects.

asafbennatan commented 7 months ago

can you construct a minimal example repository and provide a link to it ?

paulsen-it commented 7 months ago

It works with the simple workaround. It is Ok for me. You can close the Issue.