Integrating spring-boot with RESTEasy

2019-05-26 06:32发布

I am trying to prototype a Spring Boot application. I'm coming from a Guice JAX-RS application, so I prefer the standard JAX-RS annotations to Spring MVC. I've gotten Jetty up and serving:

@Configuration
@Import({ResteasyBootstrap.class, SpringBeanProcessorServletAware.class, HttpServletDispatcher.class})
public class EmbeddedJetty {
    @Bean
    @Singleton
    public EmbeddedServletContainerFactory servletContainer() {
        JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
        factory.setPort(9000);
        factory.setSessionTimeout(10, TimeUnit.MINUTES);
        return factory;
    }
}

However, I just can't figure out how to get RESTEasy hooked up correctly. With the above SpringBeanProcessorServletAware it bails, seemingly the ServletContext is not injected through ServletContextAware before it ends up being used:

java.lang.NullPointerException: null
    at org.jboss.resteasy.plugins.spring.SpringBeanProcessorServletAware.getRegistry(SpringBeanProcessorServletAware.java:30)
    at org.jboss.resteasy.plugins.spring.SpringBeanProcessor.postProcessBeanFactory(SpringBeanProcessor.java:247)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:284)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:174)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:680)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:522)
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)

I also tried using the SpringContextLoaderListener, but that seems to conflict with the spring-boot AnnotationConfigEmbeddedWebApplicationContext class.

I'm using spring-boot 1.3.3 and spring-framework 4.3.0.rc1

3条回答
我命由我不由天
2楼-- · 2019-05-26 07:12

Here is fully working example.

  1. First, a sample JAX-RS endpoint:

    @Path("/api")
    public class SampleResource {
        @GET
        @Path("/sample")
        @Produces(MediaType.APPLICATION_JSON)
        public String getSample() {
            return "Some JSON";
        }
    }
    
  2. Next, a JAX-RS configuration class that loads all endpoints.

    import javax.ws.rs.core.Application;
    
    public class RestEasyConfig extends Application {
        @Override
        public Set<Class<?>> getClasses() {
            Set<Class<?>> classes = new HashSet<>();
            classes.add(SampleRest.class);
            return classes;
        }
    }
    
  3. Finally, in your Spring configuration, initialize RESTEast filter and inform the framework about its existence.

    import org.springframework.boot.context.embedded.FilterRegistrationBean;
    import org.jboss.resteasy.plugins.server.servlet.FilterDispatcher;
    ...
    
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        Map<String, String> initParams = new HashMap<>();
        initParams.put("javax.ws.rs.Application", RestEasyConfig.class.getCanonicalName());
    
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new FilterDispatcher());
        registrationBean.setInitParameters(initParams);
        return registrationBean;
    } 
    

    Your endpoint should be up and running. If you are missing the FilterDispatcher class on your class path, add the resteasy-jaxrs library to your build descriptor.

查看更多
一夜七次
3楼-- · 2019-05-26 07:20

The other answer won't have your resources as spring beans, this autoconfiguration will integrate them properly:

The Configuration class:

@Configuration
@ConditionalOnWebApplication
public class RestEasyAutoConfigurer {


    private Environment environment;   

    @Bean(name = "resteasyDispatcher")
    public ServletRegistrationBean resteasyServletRegistration() {
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HttpServletDispatcher(), getPrefix()
                + "/*");
        registrationBean.setInitParameters(ImmutableMap.of("resteasy.servlet.mapping.prefix", "/rs/")); // set prefix here
        registrationBean.setLoadOnStartup(1);
        return registrationBean;
    }

    @Bean(destroyMethod = "cleanup")
    public static RestEasySpringInitializer restEasySpringInitializer() {
        return new RestEasySpringInitializer();
    }    

    @Bean
    // use Spring Boot configured Jackson
    public CustomResteasyJackson2Provider jackson2Provider(ObjectMapper mapper) {
        return new CustomResteasyJackson2Provider(mapper); 
    }

    public static class RestEasySpringInitializer
            implements
                ServletContextInitializer,
                ApplicationContextAware,
                BeanFactoryPostProcessor {

        private ResteasyDeployment deployment;

        private ConfigurableApplicationContext applicationContext;

        private ConfigurableListableBeanFactory beanFactory;

        public void cleanup() {
            deployment.stop();
        }

        @Override
        public void onStartup(ServletContext servletContext) throws ServletException {
            ListenerBootstrap config = new ListenerBootstrap(servletContext);
            deployment = config.createDeployment();
            deployment.start();

            servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory());
            servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher());
            servletContext.setAttribute(Registry.class.getName(), deployment.getRegistry());

            SpringBeanProcessor processor = new SpringBeanProcessor(deployment.getDispatcher(),
                    deployment.getRegistry(), deployment.getProviderFactory());
            processor.postProcessBeanFactory(beanFactory);
            applicationContext.addApplicationListener(processor);
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            this.beanFactory = beanFactory;
        }

        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = (ConfigurableApplicationContext) applicationContext;
        }
    }
}

And the Jackson provider:

@Provider
@Consumes({"application/*+json", "text/json"})
@Produces({"application/*+json", "text/json"})
public class CustomResteasyJackson2Provider extends ResteasyJackson2Provider {
    private ObjectMapper mapper;

    public CustomResteasyJackson2Provider(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public ObjectMapper locateMapper(Class<?> type, MediaType mediaType) {
        return Optional.ofNullable(_mapperConfig.getConfiguredMapper()).orElse(mapper);
    }    
}

NOTE: this is a working configuration for Spring Boot 1.3.3 / RESTEasy 3.0.16

查看更多
Summer. ? 凉城
4楼-- · 2019-05-26 07:26

You can use RESTEasy Spring Boot starter. Here is how you do it:

Adding POM dependency

Add the Maven dependency below to your Spring Boot application pom file.

<dependency>
   <groupId>com.paypal.springboot</groupId>
   <artifactId>resteasy-spring-boot-starter</artifactId>
   <version>2.1.1-RELEASE</version>
   <scope>runtime</scope>
</dependency>

Registering JAX-RS application classes

Just define your JAX-RS application class (a subclass of Application) as a Spring bean, and it will be automatically registered. See the example below. See section JAX-RS application registration methods in How to use RESTEasy Spring Boot Starter for further information.

package com.test;

import org.springframework.stereotype.Component;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@Component
@ApplicationPath("/sample-app/")
public class JaxrsApplication extends Application {
}

Registering JAX-RS resources and providers

Just define them as Spring beans, and they will be automatically registered. Notice that JAX-RS resources can be singleton or request scoped, while JAX-RS providers must be singletons.

Further information at the project GitHub page.

查看更多
登录 后发表回答