0
votes

This is a followup question of Spring + Jackson + joda time: how to specify the serialization/deserialization format?.

When I was writing the final version of the code, for the first time I wrote it as follows: (only relevant portion is shown)

@Configuration
public class WebMvcConfiguration
{
    @Bean
    public WebMvcConfigurerAdapter apiWebMvcConfiguration()
    {
        return new ApiWebMvcConfiguration();
    }

    public class ApiWebMvcConfiguration extends WebMvcConfigurerAdapter
    {
        public ApiWebMvcConfiguration()
        {
            log.debug("### ApiWebMvcConfiguration");
        }

        @Bean
        public UserInterceptor userInterceptor()
        {
            return new UserInterceptor(false);
        }

        @Override
        public void addInterceptors(InterceptorRegistry registry)
        {
            log.debug("### addInterceptors");
            registry.addInterceptor(userInterceptor())
                .addPathPatterns("/api/user/**");
        }
    }

    private static final Log log =
        LogFactory.getLog(WebMvcConfiguration.class);
}

There is no @EnableWebMvc, since the default @EnableWebMvc class of Spring Boot is used.
Notice that userInterceptor bean is in the WebMvcConfigurerAdapter class, which is also a bean.

When I've run the application, following error occured:
(class pathnames of my classes were replaced to '...' by myself)

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name '...WebMvcConfiguration$ApiWebMvcConfiguration': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [...WebMvcConfiguration$ApiWebMvcConfiguration]: No default constructor found; nested exception is java.lang.NoSuchMethodException: ...WebMvcConfiguration$ApiWebMvcConfiguration.<init>()
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1076)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1021)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:700)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:124)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:609)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:321)
    at ...Application.main(Application.java:17)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [...WebMvcConfiguration$ApiWebMvcConfiguration]: No default constructor found; nested exception is java.lang.NoSuchMethodException: ...WebMvcConfiguration$ApiWebMvcConfiguration.<init>()
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:85)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1069)
    ... 14 more
Caused by: java.lang.NoSuchMethodException: ...WebMvcConfiguration$ApiWebMvcConfiguration.<init>()
    at java.lang.Class.getConstructor0(Class.java:2810)
    at java.lang.Class.getDeclaredConstructor(Class.java:2053)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:80)
    ... 15 more

Then I changed ApiWebMvcConfiguration class to a static inner class.

Application was started 'normally', but ApiWebMvcConfiguration class was instantiated twice. That is, "### ApiWebMvcConfiguration" was printed twice. And accordingly, "### addInterceptors" was printed twice.

Then, when UserIntercepter's code ran, it failed because of null @Autowired JdbcTemplate. That is, @Autowired was not working for the object. (JdbcTemplate was successfully @Autowired in other objects)

So, I changed the code to the final version as shown in Spring + Jackson + joda time: how to specify the serialization/deserialization format?, that is, UserIntercepter bean was pulled out of ApiWebMvcConfiguration, the problem was gone.

Is this correct behaviour?
Should @Bean not be nested?

2

2 Answers

0
votes

Spring tries to instantiate the ApiWebMvcConfiguration itself. That does not work because non-static inner classes cannot be instantiated like normal classes. They need a reference to an instance of the outer class. Hence the error message "No default constructor found".

When you change the inner class to a static one, instantiation works, but as you noticed, it still happens twice.

The problem is the @Beanannotation for userInterceptor(). You tell Spring that it can get a bean from this method. But in order to get one, Spring needs an instance of ApiWebMvcConfiguration. So it creates one itself. But then another one is created by the apiWebMvcConfiguration() method.

0
votes

A nested class inside a @Configuration class is always interpreted as a @Bean. So you have registered it twice by adding your own explicit @Bean definition.