Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve exception when failing to create a specific bean due to a NoClassDefFoundError [SPR-14883] #19449

Closed
spring-projects-issues opened this issue Nov 7, 2016 · 2 comments

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Nov 7, 2016

Antonio Anzivino opened SPR-14883 and commented

I am currently stuck in determining the cause of a NoClassDefFoundError that is occurring in my project.

I am reporting this ticket to request an improvement, and to discuss how it should be implemented.

My specific problem (which I can't diagnose on my own) is a NoClassDefFoundError

java.lang.NoClassDefFoundError: WebJarAssetLocator
	at java.lang.Class.getDeclaredMethods0(Native Method) ~[?:1.7.0_79]
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2615) ~[?:1.7.0_79]
	at java.lang.Class.getDeclaredMethods(Class.java:1860) ~[?:1.7.0_79]
	at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:612) ~[spring-core-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:524) ~[spring-core-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:510) ~[spring-core-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:243) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1074) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1047) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:751) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.__refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444) ~[spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.3.3.RELEASE.jar:4.3.3.RELEASE]
	at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5099) [catalina.jar:7.0.70]
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5615) [catalina.jar:7.0.70]
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) [catalina.jar:7.0.70]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571) [catalina.jar:7.0.70]
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561) [catalina.jar:7.0.70]
	at java.util.concurrent.FutureTask.run(FutureTask.java:262) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [?:1.7.0_79]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [?:1.7.0_79]
	at java.lang.Thread.run(Thread.java:745) [?:1.7.0_79]
Caused by: java.lang.ClassNotFoundException: WebJarAssetLocator
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1891) ~[catalina.jar:7.0.70]
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1734) ~[catalina.jar:7.0.70]
	... 31 more

From my understanding, some bean contains an @Autowired dependency for which the autowired bean contains a constructor dependency with WebJarAssertLocator. I am not willing to inlude WebJarAssetLocator in my classpath, I need to identify the component dependent to that class in order to remove its dependency from webjars package.

Now the problem is that this stack trace does not provide information on what bean caused the failure. I could get more information if I had either the name of the bean that contains the faulty @Autowired definition or else the name of the bean that is faulty to instantiate as an @Autowired bean.

My proposal is for Spring Framework to increase the number of catch(Throwable) statements, and log little extra contextual information when these Throwables occur.

A simple, but probably insufficient and too simplicistic, proposal for which I could create a PR easily, is to wrap the entire doGetBean method in the following:


	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {

            try{
                ---- old code
            }catch(Throwable ex){
                throw new BeansException("Unable to get bean: " + name + " of required type " + requiredType, ex);
            }

        }

Another alternate proposal, that follows a pattern I like a lot to apply, is the following (DefaultListableBeanFactory#731):

for(String beanName: beanNames) 
    try{
        ---old code
    }catch(Throwable ex){
        throw new BeansException("Unable to init singleton bean "+beanName, ex);
    }

The pattern is: when performing a loop, identify the element that caused an exception and throw its identifying information in a new exception that wraps the cause. This helps diagnosis.

This is meant to provide discussion and improvement. I will have to debug my specific error on my own, but I would like to see this (especially the second pattern) implemented in future releases


Affects: 4.3.3

Issue Links:

  • #17066 PersistenceAnnotationBeanPostProcessor.findPersistenceMetadata(PersistenceAnnotationBeanPostProcessor.java:392) too aggressive, resulting in NoClassDefFoundError
  • #18738 Improve failure metadata that is available from BeanInstantiationException

Referenced from: commits c44c607, cf479bf, b3cd1ad, b42d731, 3d2e4c3, 37f4f43

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Nov 7, 2016

Antonio Anzivino commented

For sake of completeness, I have identified a spurious controller class file (.class) being still present in my Eclipse build directory. But this does not close my request :-)

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Nov 7, 2016

Juergen Hoeller commented

I've tackled this from two angles:

  • For your specific case, there should be guards around ReflectionUtils that throw a proper exception referencing the name of the class that we were trying to introspect, i.e. a corresponding BeanCreationException thrown from AutowiredAnnotationBeanPostProcessor.

  • Beyond that, it would make sense to have a general guard in AbstractAutowireCapableBeanFactory.createBean, catching any unexpected exception (i.e. non-BeanCreationException) at the outermost level and turning into a context-aware BeanCreationException. While we do that for many specific creation steps already, there may always be an exception coming out of some gap that we can then finally catch here. In your specific case, an exception coming out of ReflectionUtils would be handled here in any case.

The former change will make it into the 4.3.4 release, for immediate availability tomorrow. The latter will only make it into 5.0 M3 since there are some subtle side effects to be expected.

As for generally catching exceptions on iteration, that's a fine point but for bean lookup/creation specifically I'd prefer the guards in the getBean / createBean code path itself, applying to individual bean lookup attempts as well. This also propagates the specific exception subclass, not wrapping all of them in the same common exception at a higher level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants