Spring MVC is widely adopted across many teams at Meituan Dianping for web development. In projects built upon Spring MVC, annotations are extensively used throughout various modules — from standard Java annotations like @Override and @Deprecated, to Spring-specific ones such as @Controller, @Service, and @Autowired, and even custom-defined annotations. While annotations serve as markers and can simplify configuration, making development faster, they should be applied thoughtfully. I often used them liberally, enjoying their convenience until an unexpected NullPointerException arose due to a @Service annotation that forced me to reevaluate my practices and led to deeper exploration!
Initial Incident
We received a business requirement to encapsulate an upstream HTTP interface for internal system support. After successfully implementing and testing locally, I deployed it to the test environment. However, immediately upon testing, a NullPointerException occurred, with the stack trace showing:
ERROR [qtp384587033-86] 2015-12-21 16:29:00.905 com.meituan.trip.mobile.hermes.common.utils.HttpClientUtils.doRequest(HttpClientUtils.java:359) HttpClientUtils.doRequest invoke get error, url:nullmt/api/test/v1/query?id=123456
org.apache.http.client.ClientProtocolException
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:186) ~[httpclient-4.3.5.jar:4.3.5]
…
Caused by: org.apache.http.ProtocolException: Target host is not specified
...
The exception clearly indicated a malformed URL issue. However, this was puzzling since I had configured the URL value via XML, and local unit tests passed. Why did the property injection fail when the @Service annotation was added?
Diagnosis
With the @Service annotation present, re-deploying and starting the application revealed in the logs that the implementation bean had been overridden:
INFO [main] 2015-12-21 16:28:47.078 org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:665) Overriding bean definition for bean 'queryPartnerImpl': replacing [Generic bean: class [com.meituan.trip.mobile.hermes.sal.meilv.impl.QueryPartnerImpl]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/hanzhankang/hermes/hermes-sal/target/classes/com/meituan/trip/mobile/hermes/sal/meilv/impl/QueryPartnerImpl.class]] with [Generic bean: class [com.meituan.trip.mobile.hermes.sal.meilv.impl.QueryPartnerImpl]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [sal/service-outer.xml]]
This override happened because two beans with the same name were being registered within the same WebApplicationContext. The final bean was the one defined in sal/service-outer.xml, which properly configured its properties. Despite this replacement, the program still seemed to run correctly.
However, the problem surfaced again whenever the @Service annotation was present.
Investigation and Resolution
Encountering such an unusual issue, especially when unsure if it was environment-specific, I turned to analyzing object instances. Using jmap, I checked how many instances of QueryPartnerImpl existed:
$ jmap -histo:live 20881 | grep QueryPartnerImpl
1354: 2 80 com.meituan.trip.mobile.hermes.sal.meilv.impl.QueryPartnerImpl
Two instances were found, contradicting the singleton behavior expected from Spring. I dumped the heap memory for detailed analysis:
$ jmap -dump:format=b,file=/tmp/heap.bin 20881
Dumping heap to /private/tmp/dump.data ...
Heap dump file created
Using MAT and Jhat tools, I analyzed the heap dump and identified the two instances:
com.meituan.trip.mobile.hermes.sal.meilv.impl.QueryPartnerImpl@0x6c41b6f80: Properties successfully injected.com.meituan.trip.mobile.hermes.sal.meilv.impl.QueryPartnerImpl@0x7aeafac20: Properties failed to inject.
Further investigation showed that the first instance was referenced by ContextLoaderListener, whereas the second was referenced by DispatcherServlet. This meant that different parts of the application were using different instances of the same bean, causing the failure.
Root Cause Analysis
Upon closer inspection through MAT, it became evident:
QueryPartnerImpl@0x6c41b6f80was managed byXmlWebApplicationContext@0x6c358f810, which was linked toContextLoaderListener.QueryPartnerImpl@0x7aeafac20was managed byXmlWebApplicationContext@0x7ae9ca338, which was linked toDispatcherServlet.
This setup indicates that DispatcherServlet and ContextLoaderListener each maintain their own WebApplicationContext, leading to two separate bean instances.
Solution
The core issue stemmed from overlapping component scanning configurations in both applicationContext.xml and spring-servlet.xml. To fix it, we needed to ensure only one valid instance of the bean existed. Options included:
- Removing the @Service annotation – not ideal as it would disable other useful annotations.
- Scanning isolation – configuring
<context:component-scan>filters to exclude certain annotations in specific contexts.
Example of adjusted spring-servlet.xml:
<context:component-scan base-package="com.meituan.trip.mobile.hermes" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
Summary
- Annotations enhance productivity but must be used correctly.
- Component scanning is powerful but requires careful configuration.
- Unit tests may pass due to single context loading, masking multi-context issues.
- Adhering to best practices prevents obscure bugs.
Further Reading
For more insights into Spring's architecture and container management, exploring the source code of ContextLoaderListener and DispatcherServlet reveals how parent-child WebApplicationContext relationships are established and maintained.