BeanCurrentlyInCreationException for actionPerformed(AWT)
Asked Answered
P

3

6

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.
Peshawar answered 2/4, 2017 at 1:47 Comment(1)
Do see the edit to my answer for update with lazy initialization ..Urethra
P
2

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

Peshawar answered 9/4, 2017 at 10:57 Comment(0)
U
4

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;
Urethra answered 4/4, 2017 at 10:15 Comment(6)
The reason why it works w/o @Async is due to associated proxies not created in absence of it. Apart from removing final in this case the default eager initialization is to be switched off either via annotating class with @Lazy or by specifying default-lazy-init="true" in config xml as <beans default-lazy-init="true" xmlns="...." ...>Urethra
The exception states too that there is a circular reference somewhere.Luker
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 RawInjectionDespiteWrappingLuker
The @Lazy should be placed along @Inject e.g. @Inject @Lazy private Dialog dlg;. AddTask class doesn't require @Lazy.Urethra
\@Lazy on AddTask allows to break the circular reference. Why \@Async does not execute in a new thread may be explained if AddTask is injected not proxified. You may be able to check if it proxified if the instance on the caller look like a cglib proxy. You may tried like Bond suggests to move the lazy on the other side of the referenceLuker
If I make the bean lazy, the @Async does not execute in a new thread.Peshawar
L
3

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.

Luker answered 4/4, 2017 at 14:38 Comment(4)
It is perfectly acceptable to keep the final keyword. What is not correct in my point of view is the assigment = null with Spring that modify later the field. A final field at null should always be null and not modifiable.Luker
Once assigned, a final field cannot be changed, in respect to the java spec your class violate the spec. Circular reference create issues like yours. And generally circular references tends to show bad design (no offense).Luker
Spring is very flexible and allow different use case. One of this use case may be a legacy class with final field that should be injected into. I do not understand your statement because CDI allows constructor injection. About the cyclic reference, there is no strict design principle behind this, this is a valid construct which should be manipulated with caution. With circular support set to off, 99% of the time I do not have any issue, the 1% remaining could be solved with a third class. With circular support set to on, I have met this type and lost a bunch of time solving it.Luker
If I make the bean lazy, the @Async does not execute in a new thread.Peshawar
P
2

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

Peshawar answered 9/4, 2017 at 10:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.