Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BeanCurrentlyInCreationException for actionPerformed(AWT)

Tags:

java

spring

awt

Creating a new button I must run code in a new thread.

Usually we use new Thread(....).start(); but I am wondering why we can not use the @Async-Annotation.

This is the Code:

package net.vectorpublish.desktop.vp;

import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;

import org.springframework.scheduling.annotation.Async;

import net.vectorpublish.desktop.vp.api.history.Redo;
import net.vectorpublish.desktop.vp.api.layer.Layer;
import net.vectorpublish.desktop.vp.api.ui.Dialog;
import net.vectorpublish.desktop.vp.api.ui.KeyframeSlider;
import net.vectorpublish.desktop.vp.api.ui.ToolBar;
import net.vectorpublish.desktop.vp.api.ui.VPAbstractAction;
import net.vectorpublish.desktop.vp.api.vpd.DocumentNode;
import net.vectorpublish.desktop.vp.api.vpd.VectorPublishNode;
import net.vectorpublish.desktop.vp.gantt.AddTaskData;
import net.vectorpublish.desktop.vp.gantt.AddTaskHistoryStep;
import net.vectorpublish.desktop.vp.gantt.Priority;
import net.vectorpublish.desktop.vp.utils.SetUtils;
import net.vectorpublish.destkop.vp.gantt.rule.VetoableTaskAdder;

@SuppressWarnings("restriction")
@Named
public class AddTask extends VPAbstractAction implements NodeSelectionChangeListener {

    public AddTask() {
        super(GanttText.ADD_TASK, GanttText.ADD_TASK_TT, false);
    }

    @Inject
    private final Dialog dlg = null;

    @Inject
    private final History hist = null;

    @Inject
    private final Redo redo = null;

    @Inject
    private final Layer layer = null;

    @Inject
    private final ToolBar toolbar = null;

    @Inject
    private final KeyframeSlider slider = null;

    @Inject
    private final Set<VetoableTaskAdder> council = null;

    private DocumentNode doc;

    @Async // <----------------------------------------------- This creates the Exception!
    public void actionPerformed(ActionEvent arg0) {
        try {
            VectorPublishNode selected = layer.getSelection().iterator().next();
            Future<String> taskId = dlg.ask(GanttText.NAMESPACE, "ID", "");
            Future<String> info = dlg.ask(GanttText.NAMESPACE, "Detail", "");
            Future<Priority> prio = dlg.ask(GanttText.NAMESPACE, "Name", Priority.values());
            Future<Float> points = dlg.ask(GanttText.NAMESPACE, "Storypoints", 3f);
            Future<String> username = dlg.ask(GanttText.NAMESPACE, "User", "");
            Future<String> avatar = dlg.ask(GanttText.NAMESPACE, "Avatar-Image", "www.test.com/User.png");
            AddTaskData addTaskData = new AddTaskData(taskId.get(), info.get(), prio.get(),
                    SetUtils.nodeToImmutableIndex(selected), slider.getTime(), points.get(), username.get(),
                    load(avatar.get()));
            AddTaskHistoryStep data = new AddTaskHistoryStep(hist, addTaskData);
            redo.actionPerformed(arg0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    private BufferedImage load(String string) throws MalformedURLException {
        ImageIcon ii = new ImageIcon(new URL(string));
        return (BufferedImage) ii.getImage();
    }

    public void changedNodeSelection() {
        Set<VectorPublishNode> nodes = layer.getSelection();
        if (nodes.size() != 1) {
            setEnabled(false);
        } else {
            boolean veto = false;
            for (VetoableTaskAdder vetoableTaskAdder : council) {
                veto &= vetoableTaskAdder.hasVeto(nodes);
            }
            setEnabled(!veto);
        }
    }

    @PostConstruct
    public void setup() {
        toolbar.add(this);
    }
}

This is the Exception:

DefaultI8nImageFactory Found: Image for key net.vectorpublish:io/new/large in cache!     (DefaultI8nImageFactory > NewFile)
DefaultI8nImageFactory Found: Image for key net.vectorpublish:io/open/small in cache!    (DefaultI8nImageFactory > OpenImpl)
DefaultI8nImageFactory Found: Image for key net.vectorpublish:io/open/large in cache!    (DefaultI8nImageFactory > OpenImpl)
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'addTask': Bean with name 'addTask' has been injected into other beans [nodeSelectionChangeImpl,translation] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'addTask': Bean with name 'addTask' has been injected into other beans [nodeSelectionChangeImpl,translation] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:583)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:754)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
    at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
    at net.vectorpublish.desktop.vp.VectorPublishApplicationContext.<init>(VectorPublishApplicationContext.java:18)
    at net.vectorpublish.desktop.vp.Startup.main(Startup.java:30)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:282)
    at java.lang.Thread.run(Thread.java:745)

Edit

Because of some higher decisions I must respect:

  1. I must have the field final and
  2. can not autowire using the constructor.
like image 709
Grim Avatar asked Apr 02 '17 01:04

Grim


3 Answers

The root cause of the BeanCurrentlyInCreationException in this case is due to use of @Inject (or its Spring's equivalent @Autowired) on final fields.

To understand the behavior the bean life-cycle should to be considered.

  • Spring first construct the object with its fields having their default value i.e. null.
  • Post constructing the object Spring uses reflection to inject i.e. initialize the fields' actual value

Thus second step contradicts the final declaration on the fields which dictates that field can have one and only one value which should be assigned at the construction time.

Thus to resolve the issue either remove final declaration from field(s) or use constructor injection instead (former is advisable in this particular case given the number of dependencies)

Let know in comments if any more information is required.

Hope this helps!

P.S.: Although I could not find explicit mention of this behavior in any of the official documentation but here it is subtly explained in example wherein the field is marked final only in case of constructor injection.

EDIT :-
Introducing @Async force Spring to create and use bean proxies instead, which leads to the BeanCurrentlyInCreationException if there are circular references.

This occurs because Spring initially injects raw version of bean and tries applying aspects to it and fails because RawInjectionDespiteWrapping is disabled by default as pointed out by Nicolas Labrot.

To overcome this either

  • Break the circular reference (though recommended but will require redesign and thus lot of efforts)
  • Use lazy initialization (as explained below)

Lazy Bean Initialization
If using xml configuration provide default-lazy-init="true" as below in root element

<beans default-lazy-init="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns="http://www.springframework.org/schema/beans"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context.xsd">
   <!-- Other configuration(s) -->
</beans>

For Java configuration use below

@Configuration
@Lazy // For all Beans to load lazily (equivalent to default-lazy-init="true")
public class SomeConfig {

    @Bean
    // @Lazy - Only if particular bean should load lazily
    public SomeBean someBean() {
        return new SomeBean();
    }
}

Also ensure that the fields marked @Inject should accompany @Lazy in case component scan is used (either via Java or xml configuration) e.g. refer below

 @Inject
 @Lazy
 private Dialog dlg;
like image 131
Bond - Java Bond Avatar answered Oct 25 '22 13:10

Bond - Java Bond


If you look at the code that throws the exception (org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean) the exception is thrown because you have a circular references and a proxy and your configuration disallow RawInjectionDespiteWrapping.

It seems there is a circular reference according to your exception. I will call Foo the bean on the other side of the circular ref AddTask <=> Foo.

  • Spring initializes Foo. Foo requires an instance of AddTask
    • Spring first instantiates AddTask and inject into Foo.
  • Then AddTask is initialized, it is already instantiated (and injected) by Foo. Because of the @Async annotation it has to be proxified. By consequence the instance injected into Foo instance will be different from the proxified one.

This cause the exception. Fix the circular reference, and it should fix the exception. As a matter of good practice, I recommend to disallow circular reference.

Note: In my point of view the final field is not necessary, error prone and it impedes unit testing/mock. It may be better if you remove it or keep it but use constructor injection.

like image 3
Nicolas Labrot Avatar answered Oct 25 '22 12:10

Nicolas Labrot


A VPAbstractAction is autowired eager always and circular. You can not async a eager bean.

like image 2
Grim Avatar answered Oct 25 '22 14:10

Grim