1

Hope the Question is self explanatory

ClassA.java

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ClassA implements InterB {
    private static int counter=0;

    private int objectid = 0;
    @Autowired
    InterA abcd;

    public ClassA() {
        super();
        this.objectid = ++counter;
    }

    @Override
    public void dododo() {
        System.out.println("instance number "+objectid++);
        abcd.doit();
    }
}

ClassB.java

@Component
@Conditional(OracleDBEngineCondition.class)
public class ClassB extends DummyParent implements InterA {
    @Autowired
    private Environment env;

    @Override
    public void doit() {
        System.out.println("hoo hoo" +" -- "+env.getProperty("DBENGINE"));
    }

}

ClassC.java

@Component("classc")
public class ClassC implements Runnable {

    @Autowired
    Provider<InterB> classAPrototypeobj;

    public void doFromAbove() {
        InterB cls = (InterB) classAPrototypeobj.get();
        InterB cls1 = (InterB) classAPrototypeobj.get();
        cls.dododo();
        cls1.dododo();
        System.out.println(cls);
        System.out.println(cls1);

    }

    @Override
    public void run() {
        this.doFromAbove();
    }
}

ClassConfig.java

@Configuration
@ComponentScan
public class ClassConfig {
}

Main Method

public static void main(String[] args) {
    ClassC obj;
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        obj = (ClassC) appctx.getBean("classc");
    }
    Thread objThread = new Thread(obj);
    objThread.start();
}

Updated Main Method(still has the same issue)

public static void main(String[] args) {
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        ClassC obj = (ClassC) appctx.getBean("classc");
        Thread objThread = new Thread(obj);
        objThread.start();
    }
}

When Executed, This Causes NoSuchBeanDefinitionException for the bean 'environment'

But when we define the following in our Config Class, the error vanishes without a trace. This is the Workaround(but Spring's supposed to inject Environment automagically we shouldn't do this). Not sure how many such beans are not injected/@Autowired

@Bean
public Environment environment(ApplicationContext context) {
    return context.getEnvironment();
}

I Suspect this is a Spring Framework Bug... Is it not?

StackTrace:

Exception in thread "Thread-1" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'classA': Unsatisfied dependency expressed through field 'abcd'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'environment' available
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:325)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory$DependencyObjectProvider.getObject(DefaultListableBeanFactory.java:1630)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory$Jsr330DependencyProvider.get(DefaultListableBeanFactory.java:1699)
    at tpt.verifypoc.ClassC.doFromAbove(ClassC.java:28)
    at tpt.verifypoc.ClassC.run(ClassC.java:39)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'classB': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'environment' available
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    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:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
    ... 14 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'environment' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:687)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1207)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$ShortcutDependencyDescriptor.resolveShortcut(AutowiredAnnotationBeanPostProcessor.java:740)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1077)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.resolvedCachedArgument(AutowiredAnnotationBeanPostProcessor.java:548)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.access$200(AutowiredAnnotationBeanPostProcessor.java:117)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:577)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
    ... 25 more

Note:

  1. Spring: 4.3.9.RELEASE
  2. JSR330: javax.inject Version 1
Crystal Paladin
  • 579
  • 5
  • 26
  • Remove the try with resources. You are closing the context with this and thus cleaning several post processors. – M. Deinum Jul 13 '17 at 11:58
  • @M.Deinum, Is that a good programming practice? to leave the resources opened? Coz Eclipse reports "Resource leak: 'appctx' is never closed"... And if any memory leak should happen, should we implement some other memory leak fixing code? anyway... when we manually include the Environment bean in Configuration class, it's injected properly... why so much Variation?... If that's a bean and another component waits for it to get autowired to a field, then it should be made available at any cost... that's the whole purpose of DI and IOC right? – Crystal Paladin Jul 13 '17 at 12:06
  • Yes BUT **you** are closing the DI container right after you got an object. Then start new threads to work with objects from said container (which is now closed or closing). Compared to JDBC you are closing the connection and afterwards try to execute a query on said connection. I don't say you shouldn't close the context, but the problem here is you are closing it before you are done with it. – M. Deinum Jul 13 '17 at 12:08
  • @M.Deinum, Even If I create the thread inside the try with resources, block, the same issue is popped up... – Crystal Paladin Jul 13 '17 at 12:11
  • Because it is still closed. The thread runs in the background so your main thread isn't going to wait. Context is still closing or closed at the time you are using it. – M. Deinum Jul 13 '17 at 12:11
  • @M.Deinum, So what you're saying is, the code closes the spring resources... The java Config Class is initiated by Spring and yet the bean defined in the config class(environment bean) seems to live and the Spring Framework itself lets its own beans die in the process? – Crystal Paladin Jul 13 '17 at 12:15
  • The instance of `ClassC` still lives and a proxy for the `Provider` field. Everything else will be closed as that is what you ordered it to do. I still could be wrong but if you put a `System.in.read()` in there and wait for a while before pressing a key the context should still be open. Should be easy enough to test. – M. Deinum Jul 13 '17 at 12:17
  • @M.Deinum, Thanks for the reply.. That is true... if the main thread lives, the Environment will be available as it is similar to not implement the Thread at all... one can't intentionally halt the program execution at any point right? placing a `System.in.read()` or `sleep(indefinitely)` for that matter. – Crystal Paladin Jul 13 '17 at 12:23
  • @M.Deinum, my point is... Spring Framework is responsible for creating Environment beans as well as the beans defined in the Java Config Classes... So why being so picky at closing the resources? If Spring defined the Environment bean, it closes and if its defined in java config, it won't? So Is there a List of beans that Spring will close automatically and won't inject it when using try with resources and threading? So the use of Spring Defined beans is inconsistent and we must define all the beans even if it's already created by Spring? – Crystal Paladin Jul 13 '17 at 12:26
  • As stated try to relate it to JDBC. You are closing the `Connection` and afterwards try to execute a SQL query on it. You would still expect it to work? You have shutdown your application, yes your application context IS your application do you still expect it to work? You have created an invalid situation, by closing the context you have destroyed your application. So no I wouldn't expect Spring to handle this case I would suspect that it should blow up right in your face, because there is no more context, context, application alive... – M. Deinum Jul 13 '17 at 12:30

1 Answers1

0

I would say this isn't a bug in Spring looking at your code before you start to use the objects you are closing the ApplicationContext. Or actually the try-with-resources block you have does that.

public static void main(String[] args) {
    ClassC obj;
    try(AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        obj = (ClassC) appctx.getBean("classc");
    }
    Thread objThread = new Thread(obj);
    objThread.start();
}

Is basically the same as

public static void main(String[] args) {
    ClassC obj;
    AbstractApplicationContext appctx = new AnnotationConfigApplicationContext(ClassConfig.class)) {
        obj = (ClassC) appctx.getBean("classc");

    appctx.close();

    Thread objThread = new Thread(obj);
    objThread.start();
}

You are closing the ApplicationContext before you are using the objects. What remains is an instanceof ClassC with a half backed proxy of Provided<ClassB> which needs to context to lookup certain beans. However it has no-way of looking up those beans as you pulled the registry from underneath it.

When you are closing the ApplicationContext you are shutting down your application. I would expect that your objects that still live are now invalid, because the container the belong to is destroyed.

Compare the code to JDBC code. You are opening a Connection, close the connection and afterwards try to prepare a statement on a closed connection. JDBC won't allow this because the Connection is closed and thus invalid.

Compare it to Microsoft Excel, you open it, work in a Sheet, write a macro. Close excel and expect the macro still to run.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • So `AnnotationConfigApplicationContext` of Spring Framework creates the Spring framework's Environment bean and the Beans defined in Java Config Classes... Then During Close operation, it should've closed all beans if it's the Standard Operating Procedure... then why does it leave the Environment bean defined in the Java Config Classes Opened? – Crystal Paladin Jul 13 '17 at 12:30
  • 1
    The `Environment` isn't a bean. It is a special infrastructure object which you can auto wire, there are more of those special infrastructure objects you can get hold of which aren't beans. – M. Deinum Jul 13 '17 at 12:31
  • So, At this juncture, Either Spring shouldn't close the Environment "Infrastructure Object" or I shouldn't implement Java's Threading Functionality(which is not possible and not cool by spring if it suggests me that) or I shouldn't Use Try with Resources Java Functionality(not cool) Or I Should leave the Resource Unclosed and Expect a Memory Leak(not so cool at all) in the upcoming unpredictable future... are these my options? :p I wish I can get a solution to ease my mind... :p – Crystal Paladin Jul 13 '17 at 12:40
  • Spring isn't closing anything YOU are closing the context. You should close it when your application is done and ready to close else don't close the context. As mentioned the `ApplicationContext` IS your application. So as long as you need your application your `ApplicationContext` should be alive. Your issue is basically due to the fact of premature optimization and only reacting to warnings from your IDE instead of throughly investigating them. – M. Deinum Jul 13 '17 at 12:41
  • Sorry my bad... the sentence should've been like this "Either Spring shouldn't close the Environment "Infrastructure Object" when the try with resource block closes the resource".. So Spring identifies the bean in config classes as non-closable resource and the "Infrastructure Object" as closable... I'm feeling dizzy here... (@_@) – Crystal Paladin Jul 13 '17 at 12:45
  • Regardless I find it actually unexpected that it works when defined as a `@Bean` I would expect it to blow up in either case. Why you might think, because you have created an invalid situation (closing the context and try to use it afterwards). Basically what you have done here is actually creating a memory leak instead of preventing one, as Spring cannot properly close the context, objects keep hanging around instead of properly getting cleaned up. – M. Deinum Jul 13 '17 at 12:48
  • Environment bean defined in config class is created by spring and they are spring-managed... If you agree with that statement, and still consider that I'm responsible for creating a memory leak, please modify the code so as to not to induce a memory leak...(with using thread)... If you consider not to use try with resources, then does spring emphasize on not using java's core Functionality?... (@_@) – Crystal Paladin Jul 13 '17 at 12:59
  • Are you actually reading my comments and understanding them? I'm going to repeat again you are closing the context and afterwards are trying to use it, that is an invalid situation. Yes you should close the context but only when you are done with it, you aren't done with it you still need it as a provider for your prototype scoped beans. So you shouldn't close it right there. You should close it as soon as you are done not before not after. The memory leak you created is due to the fact that the proxy for the provider prevents the cleanup of the remainder of the objects. – M. Deinum Jul 13 '17 at 13:02
  • Put all your code inside the try-with-resources block and wait for your thread to finish. Then and only then leave the try-with-resources block to close your context. As stated don't close it until you don't need it anymore. – M. Deinum Jul 13 '17 at 13:04
  • I m also handling the same problem.In production i can't just copy & paste my entire code inside Try-with-resource block (Provider is in Third Level Deep -hierarchy).Please do not argue kindly give a solution in the form of code. – Sriram S Jul 13 '17 at 13:11
  • I won't give code as that is specific to the situation. As stated Don't close the context until you are done with it and don't need it anymore. Generally you shouldn't even create a context and you are doing things wrong already. (Also don't hijack questions from other people, or if you are the same person then why do you have multiple accounts). – M. Deinum Jul 13 '17 at 13:13
  • Also don't hijack questions from other people, or if you are the same person then why do you have multiple accounts....What ?... – Sriram S Jul 13 '17 at 13:20
  • Just wondering if you are the same person as @CrystalPaladin under a different account. – M. Deinum Jul 13 '17 at 13:22
  • @CrystalPaladin, If you solve the problem please let me know and share the code. – Sriram S Jul 13 '17 at 13:24
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/149131/discussion-between-sriram-s-and-m-deinum). – Sriram S Jul 13 '17 at 13:25