4

Simply marking a field as @Autowired in a GWT servlet does not work as intended. The code will compile and the web application will start up - which means Spring was successfully able to autowire the field, but when the servlet is actually hit by client-side code, it will yield a NullPointerException - like there's a different, uninitialized copy of the servlet being hit.

I've found several ways on the web to get this working, one is by using a base servlet class that does some Spring logic but doing this means every GWT servlet must extend this base class. The other way was by using AspectJ and the @Configurable Spring annotation. There was very little configuration involved here and it just magically worked.

My question is why doesn't just autowiring the field just work as intended? What is GWT doing that causes this to break.

icfantv
  • 4,523
  • 7
  • 36
  • 53

3 Answers3

4

The code will compile and the web application will start up - which means Spring was successfully able to autowire the field

Not necessarily. The web container can instantiate a servlet without any help from Spring. Which you could be experiencing:

but when the servlet is actually hit by client-side code, it will yield a NullPointerException - like there's a different, uninitialized copy of the servlet being hit.

try overriding Servlet's init():

@Override
public void init(ServletConfig config) throws ServletException {
    super.init(config);

    WebApplicationContextUtils.getWebApplicationContext(config.getServletContext())
        .getAutowireCapableBeanFactory().autowireBean(this);
}
milan
  • 11,872
  • 3
  • 42
  • 49
  • While you are technically correct in the first part of your answer, Spring, by default, treats Autowired annotated fields/methods as required and thus, will verify that it has constructed a bean that can match the annotated item. If not, it blows up and the web container will not start up. – icfantv Jan 20 '12 at 16:10
  • 1
    Additionally, in the second part of your answer, I saw this solution on the web, but the caveats are that 1) I would need to create a parent servlet class to have all my GWT servlet classes extend, something I was looking to avoid, and 2) this pattern messes with the exception handling and prevents proper handling of them when they are thrown. See the author's comments here: http://blog.maxmatveev.com/2011/02/simple-spring-bean-autowiring-in-gwt.html, and his solution - which was to use AspectJ. – icfantv Jan 20 '12 at 16:13
  • you *don't need* to make a base class, each servlet can override init() for itself :) – milan Jan 20 '12 at 16:44
  • 1
    True. Then you would have redundant code everywhere that just screams "base class..." - which I think was your point. – icfantv Jan 20 '12 at 23:13
1

When the RPC service is called from the client, the "server-side" looking at the called URL and the servlets mapping will find the class, will make the instance and it will serve the request. Meaning if you have @Autowired annotation, or you already have an instance of the RPC class in the spring context, it does not matter. The new instance will be created and it won't "know" about Spring.

I resolve this by implementing a class which extends RemoteServiceServlet and implements Controller (from Spring MVC) and ServletContextAware. This way you can map every RPC service by URL using the Spring MVC approach, for ex:

<bean id="publicUrlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
          <props>
            <prop key="/myFirstRpc">firstRpcServiceBeanRef</prop>
            <prop key="/mySecondRpc">secondRpcServiceRef</prop>
          </props>
        </property>
    </bean>

You also avoid the declarations for every single RPC servlet in web.xml, the mappings are clean and you have the Spring injection. You declare only a single mapping in web.xml for org.springframework.web.servlet.DispatcherServlet which will serve all RPC calls.

There are couple of examples on the web with explanation about GWT RPC and Spring MVC controller integration.

Hope this will help.

Goran Nastov
  • 995
  • 1
  • 9
  • 17
  • Ack! I'm sorry. I didn't make it clear that we are not using Spring MVC, just Spring Web. Given the information I found on the web, namely the one-controller-to-rule-them-all pattern felt really wrong to me as we'd be sucking in an entire framework component and only using a tiny piece. – icfantv Jan 20 '12 at 16:08
  • To me, the real power of Spring MVC is the ability to create any number of Controllers grouping like functionality together, tie controller methods to URIs, and add security on a method-by-method basis - and it all happens automatically with VERY little coding. – icfantv Jan 20 '12 at 16:16
  • I think you are mixing the dispacher-servlet with one-controller-to-rule-them-all. This servlet mapping gives general definition of the URLs used by the controllers, like /rpc/* . That is the idea. If you have included framework like Spring Security you still have the possibility of security on method level. – Goran Nastov Jan 23 '12 at 10:58
0

It turns out that when using Spring at least, there's a MUCH simpler way to do this such that you can use @Autowired and it doesn't involve massive configuration or base classes. The caveat is that you must also use AspectJ. Here's what you need for your GWT servlet:

@Configurable
public class MyGwtServiceImpl extends RemoteServiceServlet implements MyGwtService
{
  @Autowired
  private MyService service;

  // ...
}

And in your Spring config make sure you also have:

   <!-- enable autowiring and configuration of non-spring managed classes, requires AspectJ -->
   <context:spring-configured/>

One final note. If you are also using Spring security with your GWT application (and in your GWT servlets), you will need to make sure you define the correct mode to ensure the AspectJ weaving is done correctly (i.e., you get both @Secured annotation processing AND the @Autowired processing) you will need:

   <!-- turn on spring security for method annotations with @Secured(...) -->
   <!-- the aspectj mode is required because we autowire spring services into GWT servlets and this
        is also done via aspectj. a server 500 error will occur if this is changed or removed. -->
   <security:global-method-security secured-annotations="enabled" mode="aspectj"/>
icfantv
  • 4,523
  • 7
  • 36
  • 53