Spring MVC XML-less 配置异常:需要 ServletContext 来配置默认的 servlet 处理

Spring MVC XML-less configuration exception : A ServletContext is required to configure default servlet handling

我是 Spring 框架的新手,正在尝试 Spring MVC xml-less(没有 web.xml 或 mvc-dispatcher-servlet.xml) 配置,因为这是项目所需的。

当我尝试 运行 它时出现以下错误(使用 Jetty Local,再次是项目要求)

以下是我用于替换 web.xml:

的配置

(也试过扩展 AbstractAnnotationConfigDispatcherServletInitializer 但运气不好)

public class WebConfig implements WebApplicationInitializer {
@Override
public void onStartup( ServletContext servletContext ) throws ServletException {
    WebApplicationContext rootContext = getContext(servletContext);
    servletContext.addListener(new ContextLoaderListener(rootContext));

    ServletRegistration.Dynamic dispatcher = servletContext.addServlet("mvc", new DispatcherServlet(rootContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
}

private AnnotationConfigWebApplicationContext getContext(ServletContext ctx) {
    AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    //Tried various combination of un/commenting following options.
    context.register( MvcServletConfig.class );
    //context.setConfigLocation("com.xyz.myapp.configuration");
    context.setServletContext( ctx );
    //context.refresh();
    //context.scan( "com.xyz.someapp.*" );
    return context;
}
}

这是我的 MVC/Servlet 配置替换 mvc-dispatcher-servlet.xml:

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = {"com.xyz.myapp"})
public class MvcServletConfig extends WebMvcConfigurerAdapter {

public MvcServletConfig() { super(); }

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
}

@Override
public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("forward:/login.jsp");
}

@Bean
public ViewResolver viewResolver() {
    InternalResourceViewResolver bean = new InternalResourceViewResolver();

    bean.setViewClass(JstlView.class);
    bean.setPrefix("/WEB-INF/pages/");
    bean.setSuffix(".jsp");

    return bean;
}
}

这是我的 AppConfig:

@Configuration
@ComponentScan(basePackages = {"com.xyz.myapp.*"})
@Import( { HibernateConfig.class } )
public class AppConfig {}

这是我的 HibernateConfig,其中我有一个访问上下文的静态方法:

@Configuration
@EnableTransactionManagement
@ComponentScan( {"com.obsm.visensia.configuration"} )
@PropertySources( value ={@PropertySource("classpath:/application.properties")} )
public class HibernateConfig {
@Transactional
public static void InitialiseDummyData()
{
    AbstractApplicationContext context = new AnnotationConfigApplicationContext( AppConfig.class);
    AccountService accountService = (AccountService) context.getBean("accountService");
    if ( accountService != null )
    {
        Role admin = new AdminRole();
        Role serverAdmin = new ServerAdminRole();
        Role normal = new NormalRole();

        User adminUser = new User();
        adminUser.setFirstName( "Kunal" );
        adminUser.setLastName( "Patel" );
        adminUser.setSex( Sex.Male );
        adminUser.setUsername( "kp" );
        adminUser.setPassword( "kp" );
        adminUser.addRole( admin );


        accountService.addUser( adminUser );

        User serverAdminUser = new User();
        serverAdminUser.setFirstName( "Server" );
        serverAdminUser.setLastName( "Admin" );
        serverAdminUser.setSex( Sex.Male );
        serverAdminUser.setUsername( "serveradmin" );
        serverAdminUser.setPassword( "serveradmin" );
        serverAdminUser.addRole( serverAdmin );

        accountService.addUser( serverAdminUser );

        User normalUser = new User();
        normalUser.setFirstName( "Normal" );
        normalUser.setLastName( "User" );
        normalUser.setSex( Sex.Undefined );
        normalUser.setUsername( "normal" );
        normalUser.setPassword( "normal" );
        normalUser.addRole( normal );

        accountService.addUser( normalUser );

        context.close();
    }
}
@Autowired
private Environment environment;

@Bean
public LocalSessionFactoryBean sessionFactory()
{
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource( dataSource() );
    sessionFactory.setPackagesToScan( new String[] { "com.obsm.visensia.model" } );
    sessionFactory.setHibernateProperties( hibernateProperties() );
    return sessionFactory;
}

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName( environment.getRequiredProperty( "jdbc.driverClassName" ) );
    dataSource.setUrl( environment.getRequiredProperty( "jdbc.url" ) );
    dataSource.setUsername( environment.getRequiredProperty( "jdbc.username" ) );
    dataSource.setPassword( environment.getRequiredProperty( "jdbc.password" ) );
    return dataSource;
}

private Properties hibernateProperties() {
    Properties properties = new Properties();
    properties.put( "hibernate.dialect", environment.getRequiredProperty( "hibernate.dialect" ) );
    properties.put( "hibernate.show_sql", environment.getRequiredProperty( "hibernate.show_sql" ) );
    properties.put( "hibernate.hbm2ddl.auto", environment.getRequiredProperty( "hibernate.hbm2ddl.auto" ) );
    properties.put( "hibernate.format_sql", environment.getRequiredProperty( "hibernate.format_sql" ) );
    return properties;
}

@Bean
@Autowired
public HibernateTransactionManager transactionManager ( SessionFactory sessFact )
{
    HibernateTransactionManager transactionManager = new HibernateTransactionManager();
    transactionManager.setSessionFactory( sessFact );
    return transactionManager;
}
}

我的登录控制器:

@Controller
@RequestMapping("/")
public class LoginController {

    @Autowired
    private AccountService accountService;

    @RequestMapping(method = RequestMethod.GET)
    public String login(ModelMap model) {
        HibernateConfig.InitialiseDummyData();
        model.addAttribute( "user", new User() );
        return "login";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String verifyUser(@ModelAttribute("user") User user, Model model)
    {
        if ( accountService.verifyUser( user.getUsername(), user.getPassword() ) ) {
            User usr = accountService.findUserByUsername( user.getUsername() );
            model.addAttribute( "message", "Welcome, " + usr.getFirstName() );
            return "hello";
        }
        else
            return "login";
    }
}

我的 HelloController:

@Controller
@RequestMapping("/")//Doesn't work even if I change it to '/hello'
public class HelloController {
    @RequestMapping(method = RequestMethod.GET)
    public String printWelcome(ModelMap model) {
        model.addAttribute("message", "Hello world!");
        return "hello";
    }
}

请帮忙,我已经在这里卡了 3 天了:'(

更新

Jetty 9.2.4v20141103,这是堆栈跟踪:

Jetty 9.2.4v20141103 and following is the stacktrace:
`java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling
    at org.springframework.util.Assert.notNull(Assert.java:112)
    at org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.<init>(DefaultServletHandlerConfigurer.java:54)
    at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.defaultServletHandlerMapping(WebMvcConfigurationSupport.java:416)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$b833a897.CGLIB$defaultServletHandlerMapping(<generated>)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$b833a897$$FastClassBySpringCGLIB$ff08f3.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
    at org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration$$EnhancerBySpringCGLIB$$b833a897.defaultServletHandlerMapping(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:590)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1113)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1008)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:505)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:229)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:725)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
    at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
    at com.obsm.visensia.configuration.HibernateConfig.InitialiseDummyData(HibernateConfig.java:32)
    at com.obsm.visensia.controller.LoginController.login(LoginController.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:781)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:721)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:800)
    at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:587)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
    at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
    at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
    at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1125)
    at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
    at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
    at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1059)
    at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
    at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:215)
    at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:110)
    at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
    at org.eclipse.jetty.server.Server.handle(Server.java:497)
    at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
    at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:248)
    at org.eclipse.jetty.io.AbstractConnection.run(AbstractConnection.java:540)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:620)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.run(QueuedThreadPool.java:540)
    at java.lang.Thread.run(Thread.java:722)`

添加了 class 以在我的上下文刷新后立即初始化我的数据库,如下所示:

@Component
public class DbInitialiserOnStartUp implements ApplicationListener<ContextRefreshedEvent> {

@Autowired
private AccountService accountService;

@Transactional
@Override
public void onApplicationEvent( ContextRefreshedEvent contextRefreshedEvent ) {
    if ( accountService != null )
    {
        //inserts etc...
    }
}
}

我测试了它,它工作正常...试试这个:

// You named it WebConfig
public class WebAppInitializer implements WebApplicationInitializer { // web.xml replacement
  @Override
  public void onStartup(ServletContext servletContext) throws ServletException {
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();

    // Here you need to change "io.shido.config" to your config location
    applicationContext.setConfigLocation("io.shido.config");

    ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher",
        new DispatcherServlet(applicationContext)); // Register and map the dispatcher servlet

    //servletContext.setInitParameter("spring.profiles.default", "development");
    servletContext.addListener(new ContextLoaderListener(applicationContext));
    //applicationContext.register(AppConfig.class); // Manage the lifecycle of the root application context
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/web/*"); // ...and here also change the context
  }
}

请注意,我也在使用 /web 作为我的上下文...因此请相应地更改它。有示例项目 here in case you want to take a look; and this 是 ZeroTurnaround 家伙写的关于该主题的非常非常好的文章。

此外,我的 Maven POM 文件中有:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.6</version>
  <configuration>
    <!--<packagingExcludes>WEB-INF/web.xml</packagingExcludes>-->
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>

更新

您的 Spring 配置工作正常(但您确实可以进一步改进它);当您呼叫 HibernateConfig.InitialiseDummyData(); 时,问题出在 LoginController 线上。评论那一行,一切都会好起来的。

调用该方法时,您正在启动另一个 Spring 上下文。我无法修复数据库的问题,因为我没有您的架构...在进行 概念验证 时最好使用内存数据库。尝试 improve/fix 那段代码,你应该可以开始了。

import javax.servlet.MultipartConfigElement;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {



    @Override
    protected Class<?>[] getRootConfigClasses() {
        // TODO Auto-generated method stub
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        // TODO Auto-generated method stub
        return new Class<?>[] { WebConfig2.class };
    }

    @Override
    protected String[] getServletMappings() {
        // TODO Auto-generated method stub

        return new String[] { "/" };
    }
}