Несколько конечных точек с Resteasy

У меня есть две отдельные части REST-сервисов в одном приложении. Скажем, главная услуга "люди" и вторичная "управленческая" услуга. Я хочу показать их на разных путях на сервере. Я использую JAX-RS, RESTEasy и Spring.

Пример:

@Path("/people")
public interface PeopleService {
  // Stuff
}

@Path("/management")
public interface ManagementService {
  // Stuff
}

В web.xml В настоящее время у меня есть следующая настройка:

<listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

<listener>
    <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/public</param-value>
</context-param>

<servlet>
    <servlet-name>Resteasy</servlet-name>
    <servlet-class>
        org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Resteasy</servlet-name>
    <url-pattern>/public/*</url-pattern>
</servlet-mapping>

Реализации PeopleService и ManagementService - это просто Spring beans. Выше web.xml конфигурация выставляет их как на /public (поэтому имеет /public/people и /public/management соответственно).

Я хочу выполнить PeopleService на /public, чтобы полный путь стал /public/people и вывел ManagementService на /internal, так что его полный путь станет /internal/management.

К сожалению, я не могу изменить значение аннотации @Path.

Как мне это сделать?

Ответ 1

на самом деле вы можете. После нескольких часов отладки я придумал следующее:

1) Объявите несколько сервлетов resteasy в вашем web.xml (два в моем случае)

<servlet>
    <servlet-name>resteasy-servlet</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    <init-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/openrest</param-value>
    </init-param>       
    <init-param>
        <param-name>resteasy.resources</param-name>
        <param-value>com.mycompany.rest.PublicService</param-value>
    </init-param>
</servlet>

    <servlet>
    <servlet-name>private-resteasy-servlet</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    <init-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/protectedrest</param-value>
    </init-param>       
    <init-param>
        <param-name>resteasy.resources</param-name>
        <param-value>com.mycompany.rest.PrivateService</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>private-resteasy-servlet</servlet-name>
    <url-pattern>/protectedrest/*</url-pattern>
</servlet-mapping>      

<servlet-mapping>
    <servlet-name>resteasy-servlet</servlet-name>
    <url-pattern>/openrest/*</url-pattern>
</servlet-mapping>  

Обратите внимание на то, что мы инициализируем личные resteasy.servlet.mapping.prefix и resteasy.resources для каждого нашего сервлета. Не забывайте, что НЕ включает любые классы ботстрапа в качестве фильтров или сервлетов! Также отключите автозапуск.

2) Создайте фильтр, который очищает приложение от глобальной информации RESTeasy, которую он сохраняет в контексте:

public class ResteasyCleanupFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        request.getServletContext().setAttribute(ResteasyProviderFactory.class.getName(), null);
        request.getServletContext().setAttribute(Dispatcher.class.getName(), null);
        chain.doFilter(request, response);


    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

Зарегистрируйте его для любого запроса к вашим услугам (здесь я использовал его для всех запросов для упрощения):

<filter>
    <filter-name>CleanupFilter</filter-name>
    <filter-class>com.mycompany.ResteasyCleanupFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CleanupFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 

Вот и все! Теперь у вас есть две разные службы REST, которые лежат в разных префиксах: /openrest, которые предназначены для обслуживания всех публичных запросов и /protectedrest, которые заботятся обо всех личных вещах в приложении.

Так почему это работает (или почему это не работает иначе)?

Когда вы вызываете экземпляр openrest в первый раз, он пытается инициализировать себя и, когда это делается, сохраняет состояние в глобальном servletContext следующим образом:

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

И если вы позвоните, ваш звонок к вашему второму /protectedrest получит САМУЮ конфигурацию! Вот почему вам нужно очистить эту информацию где угодно. Вот почему мы использовали наш CleanupFilter, который опустошил контекст, поэтому новый сервлет отдыха может инициализировать себя всеми объявленными параметрами init.

Это взломать, но он делает трюк.

Это решение было протестировано для RESTEasy 2.3.6

EDITED

Работает с версией 3.0.9.final!

Ответ 2

AFAIK, вы не можете иметь несколько сервлетов для вашей реализации JAX-RS. Что вы можете сделать: map RESTEasy до '/' (или '/api', например, если ваше приложение имеет другие ресурсы для обслуживания, и вы не хотите, чтобы часть JAX-RS вмешивалась), затем укажите следующие аннотации @Path

@Path("/public/people")
public interface PeopleService {
  // Stuff
}

@Path("/internal/management")
public interface ManagementService {
  // Stuff
}