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!