2

Note This was previously marked answered but as my understanding of the underlying components has improved, I see that our initial answer simply addressed the null pointer, but did not address the root cause. The root cause presently appears to be a lack of visibility of the context on both the dispatchListener and the dwrServlet attached to the container.

Update A solution continues to be elusive, but I've decided to abandon DWR at this time for a few reasons: it's long since the last update to DWR, Spring and Jquery provide a suitable replacement and are more recently maintained, removing our DWR dependencies will simplify our project. This question remains open for academic purposes only at this point.

Obligatory mention of the hours I've spent researching this. Have mercy.

I believe my setup is correct but clearly I'm missing something crucial and I think the time has come to seek out a second set of eyes on this.

Problem Summary

My beans are null when accessing them via a controller method which is being called via DWR.

Detailed Problem Description

Using Spring version 4.3.0.RELEASE and Direct Web Remoting (org.directwebremoting.dwr) version 3.0.2-RELEASE;

  • a given bean defined in package com.mytest.beans which contains only a field containing a string (beanName),

  • established in the SpringWebConfig with the @Bean notation (using all Java notation for Srping, no xml there - only using xml with dwr),

  • and autowired with @Autowired in a controller defined in com.mytest.controller which is annotated as @RemoteProxy with the name "SController",

  • the annotation @Controller and the annotation @RequestMapping with the value "/dwr/*" containing a method annotated @RemoteMethod named getBeanName which calls the autowired instance sbean.beanName

  • throws a null exception for the autowired instance.

Things Tried

I eliminated the possibility of an errant instantiation of the bean in this example, which is a known problem with @Autowired. I also was careful to ensure that the bean is annotated as @Component, the other possible reason a bean would fail to appear. I set the load order on dwr to 2 so that the application context would load first, hopefully ensuring things are working properly, which does allow me to access the dwr index.html for testing and debugging purposes.

I also have tried adding the following line to the AppInitializer:

dwr.setInitParameter("classes","com.mytest.bean.SBean, com.mytest.controller.SController");

But this did not help.

Things Omitted in this Example

This is not the complete project. The pom, project structure and a few .jsp's are omitted. In my testing, my authentication is functioning properly and the adUser attribute added to the HttpSession is accessible if I add a dwr @RemoteMethod to inject the session and then ask for it. As those things are working in the live app (from which the example is derived), I have omitted them from the example because I don't suspect the problem is with the working components. The logging configuration is also probably not relevant and is omitted. That said, if there is anything you want to see in addition to the below parts, please let me know and I will update this question.

Configuration

In my package com.mytest.config I have three classes: AppInitializer, RootConfig, and SpringWebConfig. RootConfig is just empty.

AppInitializer is as follows. I am using a home brew Active Directory authentication module which works well in other Spring applications, hence the UserLoginServlet.

package com.mytest.config;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class AppInitializer implements WebApplicationInitializer {

    private Logger logger = LoggerFactory.getLogger(AppInitializer.class);

    public void onStartup(ServletContext container) throws ServletException {

        try {

            AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
            ctx.register(SpringWebConfig.class);
            ctx.setServletContext(container);
            container.addListener(new ContextLoaderListener(ctx));
            container.addListener(new RequestContextListener());
            logger.info("Created AnnotationConfigWebApplicationContext");

            ServletRegistration.Dynamic dispatcher = container.addServlet("spring-mvc-dispatcher", new DispatcherServlet(ctx));
            dispatcher.setLoadOnStartup(1);
            dispatcher.addMapping("/");
            logger.info("DispatcherServlet added to AnnotationConfigWebApplicationContext");

            ServletRegistration.Dynamic servlet = container.addServlet("login", new com.mycompany.ad.UserLoginServlet());

            servlet.setLoadOnStartup(1);
            servlet.addMapping("/login/*");
            logger.info("UserLoginServlet added to AnnotationConfigWebApplicationContext");

            ServletRegistration.Dynamic dwr = container.addServlet("dwr", new org.directwebremoting.servlet.DwrServlet());
            dwr.setInitParameter("debug", "true");
            dwr.setLoadOnStartup(2);
            dwr.addMapping("/dwr/*");
            logger.info("DWR Servlet Mapping Created");
        } catch (Exception e) {
            logger.error(e.getLocalizedMessage(), e);
        }

    }

}

SpringWebConfig is defined as follows.

package com.mytest.config;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.directwebremoting.annotations.DataTransferObject;
import org.directwebremoting.annotations.GlobalFilter;
import org.directwebremoting.annotations.RemoteProxy;
import org.directwebremoting.extend.Configurator;
import org.directwebremoting.spring.DwrClassPathBeanDefinitionScanner;
import org.directwebremoting.spring.DwrController;
import org.directwebremoting.spring.DwrHandlerMapping;
import org.directwebremoting.spring.SpringConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.mytest"})
@PropertySource(value = { "classpath:application.properties" })
public class SpringWebConfig extends WebMvcConfigurerAdapter {

    private Logger logger = LoggerFactory.getLogger(SpringWebConfig.class);

    @Bean
    public DwrController dwrController(ApplicationContext applicationContext){
        logger.info("Starting dwrController Bean");
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry)applicationContext.getAutowireCapableBeanFactory();
        Map<String,String> configParam = new HashMap<String, String>();


        logger.info("Configuring scanners for DWR Bean");

        ClassPathBeanDefinitionScanner scanner = new DwrClassPathBeanDefinitionScanner(beanDefinitionRegistry);
        scanner.addIncludeFilter(new AnnotationTypeFilter(RemoteProxy.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(DataTransferObject.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(GlobalFilter.class));
        scanner.scan("com.mytest.bean");


        logger.info("Instantiating DwrController instance");
        DwrController dwrController = new DwrController();
        dwrController.setDebug(true);
        dwrController.setConfigParams(configParam);

        logger.info("Setting up SpringConfigurator for dwrController");
        SpringConfigurator springConfigurator = new SpringConfigurator();
        List<Configurator> configurators = new ArrayList<Configurator>();
        configurators.add(springConfigurator);
        dwrController.setConfigurators(configurators);

        logger.info("dwrController ready.");
        return dwrController;
    }

    @Bean
    public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping(){
        logger.info("Setting up beanNameUrlHandlerMapping");
        BeanNameUrlHandlerMapping beanNameUrlHandlerMapping = new BeanNameUrlHandlerMapping();
        logger.info("beanNameUrlHandlerMapping ready.");
        return beanNameUrlHandlerMapping;
    }

    @Bean
    public DwrHandlerMapping dwrHandlerMapping(DwrController dwrController){
        logger.info("Setting up dwrHandlerMapping");
        Map<String,DwrController> urlMap = new HashMap<String, DwrController>();
        urlMap.put("/dwr/**/*",dwrController);

        DwrHandlerMapping dwrHandlerMapping = new DwrHandlerMapping();
        dwrHandlerMapping.setAlwaysUseFullPath(true);
        dwrHandlerMapping.setUrlMap(urlMap);
        logger.info("dwrHandlerMappying ready.");
        return dwrHandlerMapping;
    }

    @Bean(name="sBean") 
    public SBean sBean() {
        logger.info("SBean starting");
        return new SBean();
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
        logger.info("DefaultServletHandlerConfigurer enabled");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry){          
        // not using an interceptor
    }


}

These are the most complex parts of the example. The following simpler parts are as spare as possible for the sake of illustration.

The SController Class:

package com.mytest.controller;

import javax.servlet.http.HttpSession;

import org.directwebremoting.annotations.RemoteMethod;
import org.directwebremoting.annotations.RemoteProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.mytest.bean.SBean;


@RemoteProxy(name="SController")
@Controller
@RequestMapping("/dwr/*")
public class SController {

    private static final Logger logger = LoggerFactory.getLogger(SController.class);

    @Autowired
    SBean sbean;

    @RemoteMethod
    @RequestMapping("getBeanName")
    @ResponseBody public String getBeanName() {
        try {
            return sbean.beanName;
        }
        catch(Exception e) {
            logger.error(e.getLocalizedMessage(),e);
            return "Error!";
        }

    }
}

The SBean Class

package com.mytest.bean;

import org.springframework.stereotype.Component;

@Component
public class SBean {
    public String beanName="Sean";
}

The dwr.xml file

<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 3.0//EN" "dwr30.dtd">
<dwr>
  <allow>

    <create creator="new" javascript="SController" scope="script">
      <param name="class" value="com.mytest.controller.SController"/>
    </create>

  <convert converter="bean" match="java.lang.Throwable"/>
  <convert converter="bean" match="java.lang.StackTraceElement"/>

  <convert match="com.mytest.bean.SBean" converter="bean"/>

  </allow>
</dwr>

As presently shown, taking Angelo's initial comment into account, dwr is running at /dwr and dwr/index.html is working. Spring is also working, so simply hitting /getBeanName now returns "Sean" instead of trying to redirect to /Sean as it had been earlier. However, executing the test call from the auto generated dwr test page still produces an error indicating a null at the line in the Controller where sBean is accessed.

Thank you for your time spent reviewing this. Assistance resolving this problem is of course greatly appreciated!

J E Carter II
  • 1,436
  • 1
  • 22
  • 39
  • 1
    What i see is that you are using the DWRServlet. In this case you are not using Spring servlet and spring context is not directly injected in this servlet. Try to give a look here http://directwebremoting.org/dwr/documentation/server/integration/spring.html – Angelo Immediata Apr 10 '18 at 14:08
  • Thanks for the suggestion @Angelo, I commented out the DWR Servlet, added the /dwr/* mapping before the global mapping on the dispatcher, and added @RequestMapping("getBeanName") to the controller method declaration and now if I call mytest/dwr/dwr/getBeanName it returns 404 dwr/dwr/Sean not found, which shows the null pointer issue has been resolved but I have some other issue to resolve with the standard Spring dispatcher. Feel free to add an answer that I can accept. – J E Carter II Apr 10 '18 at 14:50
  • I'm not giving up yet on getting the servlets to coexist and share a context, but a workaround that works but I do not like is to make DWR entry point not try to connect to the Autowired bean, but instead make a Web Request to the Spring Controller. It doubles every DWR call in terms of HTTP traffic, but half of it is enclosed within the server. It doesn't perform poorly, actually very well. As I said, this works, but it's not the right answer. – J E Carter II Apr 11 '18 at 15:06

1 Answers1

1

As I told you the point is that you were using the DWR Servlet and not the spring servlet. In order to solve the first issue you had to use the spring support provided by DWR as you can see in this link http://directwebremoting.org/dwr/documentation/server/integration/spring.html

Regarding the second issue (404 error) I think it's related to the fact that you now have just /dwr/Sean path since you deleted the dwr servlet. Try to use the /dwr/Sean and not the dwr/dwr/Sean path

Angelo

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
  • "Sean" is the value returned in the bean property beanName, so the application is processing the request, just interpreting the result incorrectly and turning it into a path redirect instead of sending back a typical dwr response object. – J E Carter II Apr 10 '18 at 14:56
  • maybe the issue is about the `/dwr/dwr/Sean` (the double dwr). Try with `/dwr/Sean` – Angelo Immediata Apr 10 '18 at 14:57
  • I think that is part of the problem as well. /dwr/getBeanName should result in a dwr json object with "Sean" as a value but it was just going to 404. I'm playing around with the setup now, took the dwr request mapping off of the controller (that plus the mapping on the dispatcher were doubling up). I probably need to give some more attention to what I am asking Spring to do with this request. – J E Carter II Apr 10 '18 at 15:02
  • Try to put spring log level to TRACE. This gives you a lot of information – Angelo Immediata Apr 10 '18 at 15:09
  • I just realized that Spring is treating this as it should, but dwr is not at all actually functioning. Only by virtue of the dwr mapping does this url return anything. I will try your TRACE suggestion but I suspect DWR is also not working really at the moment. – J E Carter II Apr 10 '18 at 15:13
  • Unfortunately it seems I may have rushed to call this resolved. There is indeed a context issue, but dwr does not run at all without the DwrServlet. I wish there was a way to indicate an answer is a clue in the right direction, but for now this is not the full answer. I suspect the correct answer will illustrate how to connect the context to both the dispatchListener and the dwrServlet that are being added to the container. – J E Carter II Apr 10 '18 at 18:13
  • Yes, and there is an xml based solution presented there for Spring 3. Using Spring 4 with only (xml free) setup there is not a clear way to provide a context parameter to the DwrServlet shown above in the AppInitializer. – J E Carter II Apr 10 '18 at 18:16
  • This is as close as I can get at the moment: http://directwebremoting.org/dwr/documentation/server/integration/spring.html#springMVC – J E Carter II Apr 10 '18 at 18:17
  • Strange I’ll give it a look as soon as I come back home now I’m curious :) not for the points but for understanding – Angelo Immediata Apr 10 '18 at 18:18
  • Thanks - I wanted to give you the points, but I want us to arrive at the correct answer more. :-) – J E Carter II Apr 10 '18 at 18:19
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/168678/discussion-between-j-e-carter-ii-and-angelo-immediata). – J E Carter II Apr 10 '18 at 19:44