JAX-RS(Jersey 2 实现)内容协商,带有 URL 扩展名 .xml 或 .json

Posted

技术标签:

【中文标题】JAX-RS(Jersey 2 实现)内容协商,带有 URL 扩展名 .xml 或 .json【英文标题】:JAX-RS (Jersey 2 implementation) content negotiation with URL extension .xml or .json 【发布时间】:2018-05-08 20:05:14 【问题描述】:

我见过一个 Java RESTFUL 网络服务,它允许在 URL 中请求内容类型,并在末尾带有扩展名,例如

.xml .json

这是我在自己的 Web 服务中努力实现的内容协商风格。

我知道@Produces 注释,并且一个方法可以使用(value = ) 语法解析多种类型,方法是添加一个Accept 标头,例如使用Chrome 扩展程序Postman。

但我不确定如何在一种方法中有效地提取信息,并委托给另一种方法。

我假设 REGEX 可以与 @Path@PathParam 一起使用,但我这样做的尝试尚未取得成果。

谁能举个例子?


这是我迄今为止的尝试:

package com.extratechnology.caaews;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.extratechnology.caaews.model.Log;


@Path("et")
@Produces(MediaType.APPLICATION_JSON)
public class CAAEWS 


    @GET
    @Path("\\.format")
    @Produces(value = MediaType.APPLICATION_JSON, MediaType.TEXT_XML)
    public Log getLog(
            @PathParam("format") String format
    )
        Log result = null;
        switch (format) 
        case "json":
            result = this.getJSON();
        case "xml":
            result = this.getXML();
        
        return result;
    

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Log getJSON() 
        return new Log("JSON!");
    

    @GET
    @Produces(MediaType.TEXT_XML)
    public Log getXML() 
        return new Log("XML!");
    



package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Log 
    private String log;

    public Log(String log) 
        this.log = log;
    

    public String getLog() 
        return log;
    

    public void setLog(String log) 
        this.log = log;
    



可以通过 Spring Tool Suite/Eclipse 设置项目,方法是使用以下命令创建一个 Maven 项目(类似,但比 here circa 4:50 更新):

org.glassfish.jersey.archetypes jersey.quickstart.webapp 2.26

然后取消注释为启用 JSON 支持而提供的 pom.xml 部分,这有效地为您的 WAR 添加了更多 JARS。

我发现我也有一些令人讨厌的 BCEL 错误,并且不得不在 catalina.properties 文件的键下附加一些条目:

tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
....
javax.json-api-1.1.jar, javax.json.bind-api-1.0.jar, javax.json-1.1.jar, \
yasson-1.0.jar

http://localhost:18080/caaews/webapi/et

产量:

"log":"JSON!"

http://localhost:18080/caaews/webapi/et.xml 或

http://localhost:18080/caaews/webapi/et.json

产量:

HTTP Status 404 - Not Found

我也想知道是否有某种 HTTP 拦截器类型的方法来解决这个问题。我的 Java 有点生疏了,但它是 servlet 过滤器,还是类似于 AOP 之前的建议。


感谢@user1803551,我在 switch 语句中添加了中断。

感谢@callmepills,我稍微调整了代码。

类级别的@Path 注解现在有了这个。 @Produces(value = MediaType.APPLICATION_JSON, MediaType.TEXT_XML)

getLog @Path 注解是“.format”。

为了调用和委托 getLog,您必须对 URL 使用以下语法:

http://localhost:18080/caaews/webapi/et

http://localhost:18080/caaews/webapi/et/.xml

http://localhost:18080/caaews/webapi/et/.json

在路径中需要一个“/”不是我所追求的,所以我想我可能不得不解决 servlet 过滤器而不是 @PathParam 方法..

【问题讨论】:

抱歉跑题了,但是恕我直言……对 xml 和 json 的支持通常是浪费时间。此外,为此目的在 URL 中使用某种后缀是更大的错误。 提供示例输入和您的尝试。见minimal reproducible example。本网站不是编码服务。 @user1803551 - 你没有给我机会!无论如何要去! 没给你机会?您应该在发布之前准备好问题,而不是通过编辑不断地添加细节。 注意: case 没有break 的语句。启用标准 Java 编译器警告并注意它们,您将不会再犯此错误。 【参考方案1】:

当我搜索“带有 jax-rs 示例的 servlet 过滤器”时,this 位于列表顶部。粗略扫描一下代码,我认为这符合我的需求。


这是我的解决方案(到目前为止...见脚注警告)

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- This web.xml file is not required when using Servlet 3.0 container,
     see implementation details http://jersey.java.net/nonav/documentation/latest/jax-rs.html -->
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <filter>
        <filter-name>accept-filter</filter-name>
        <filter-class>com.extratechnology.filters.AcceptFilter</filter-class>
        <init-param>
            <param-name>xml</param-name>
            <param-value>text/xml</param-value>
        </init-param>
        <init-param>
            <param-name>json</param-name>
            <param-value>application/json</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>accept-filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>Jersey Web Application</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.extratechnology.caaews</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Jersey Web Application</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
</web-app>

AcceptFilter.java

package com.extratechnology.filters;
import java.io.IOException;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AcceptFilter implements Filter  
    private final Map<String,String> extensions = new HashMap<String,String>();

    public void init(FilterConfig config) throws ServletException 
        Enumeration<String> exts = config.getInitParameterNames();
        while (exts.hasMoreElements()) 
            String ext = exts.nextElement();
            if (ext != null && !ext.isEmpty()) 
                this.extensions.put(ext.toLowerCase(), config.getInitParameter(ext));
            
        
    

    public void destroy() 

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        String uri = httpRequest.getRequestURI();
        String ext = this.getExtension(uri);
        String accept = this.extensions.get(ext);

        if (accept == null) 
            accept = httpRequest.getHeader("accept");
            if (accept != null && accept.indexOf("text/html") > 0) 
                // patch WebKit-style Accept headers by elevating "text/html"
                accept = "text/html,"+accept;
                request = new RequestWrapper(httpRequest, uri, accept);
            
         else 
            // remove extension and remap the Accept header
            uri = uri.substring(0, uri.length() - ext.length()-1);
            request = new RequestWrapper(httpRequest, uri, accept);
        

        // add "Vary: accept" to the response headers
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        httpResponse.addHeader("Vary", "accept");

        chain.doFilter(request, response);
    

    private String getExtension(String path) 
        String result = "";
        int index = path.lastIndexOf('.');
        if (!(index < 0 || path.lastIndexOf('/') > index)) 
            result =  path.substring(index+1).toLowerCase();
        
        return result;
    

    private static class RequestWrapper extends HttpServletRequestWrapper 

        private final String uri;
        private final String accept;

        public RequestWrapper(HttpServletRequest request, String uri, String accept) 
            super(request);

            this.uri = uri;
            this.accept = accept;
        

        @Override
        public String getRequestURI() 
            return this.uri;
        
        @Override
        public Enumeration<String> getHeaders(String name) 
            Enumeration<String> result;
            if ("accept".equalsIgnoreCase(name)) 
                Vector<String> values = new Vector<String>(1);
                values.add(this.accept);
                result = values.elements();
             else 
                result =  super.getHeaders(name);       
            
            return result;
        
    


CAAEWS.java

package com.extratechnology.caaews;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.extratechnology.caaews.model.Log;


@Path("et")
@Produces(value = MediaType.APPLICATION_JSON, MediaType.TEXT_XML)
public class CAAEWS 

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Log getJSON() 
        return new Log("JSON!");
    

    @GET
    @Produces(MediaType.TEXT_XML)
    public Log getXML() 
        return new Log("XML!");
    



Log.java

package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Log 
    private String log;

    public Log(String log) 
        this.log = log;
    

    public String getLog() 
        return log;
    

    public void setLog(String log) 
        this.log = log;
    



唯一让我感兴趣的是 HTTP 有两种 XML 内容类型。

文本/xml 应用程序/xml

它可以在 web.xml 中配置,但我必须调整注释。为什么是两个?

--

脚注:

写完这篇文章后,我发现我收到了 HTTP 500 错误。 当您在 Eclipse 中运行服务器时,日志似乎位于某个不起眼的文件夹中:

Documents\workspace-sts-3.8.3.RELEASE\.metadata\.plugins\org.eclipse.wst.server.core\tmp1\logs

我把这个写到日志中:

0:0:0:0:0:0:0:1 - - [25/Nov/2017:16:56:00 +0000] "GET /caaews/webapi/et.xml HTTP/1.1" 500 1082

有没有人知道如何获取更合理的日志信息?或者我需要做什么来捕获更有意义的堆栈跟踪?


看来 Log 类需要一个无参数构造函数来克服这个问题。但我承认,@peeskillet 的答案远没有那么繁琐,而且使用了 Jersey 的内置功能。


查看示例here...


根据此问题的其他相关答案/cmets,我最终实现了一个异常处理程序,因此您可以获得有关 Jersey 中 HTTP 500 消息的更多信息..

这里的代码有助于指向需要无参数构造函数的 Log.java..

错误消息

package com.extratechnology.caaews.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class ErrorMessage 
    private String errorMessage;
    private String errorStackTrace;
    private String cause;
    private String causeStackTrace;
    private int    errorCode;

    public ErrorMessage() 
    

    public ErrorMessage(
        String errorMessage, 
        String errorStackTrace, 
        String cause,
        String causeStackTrace,
        int errorCode
    ) 
        this.errorMessage = errorMessage;
        this.errorStackTrace = errorStackTrace;
        this.cause = cause;
        this.causeStackTrace = causeStackTrace;
        this.errorCode = errorCode;
    

    public String getErrorMessage() 
        return errorMessage;
    

    public void setErrorMessage(String errorMessage) 
        this.errorMessage = errorMessage;
    

    public String getErrorStackTrace() 
        return errorStackTrace;
    

    public void setErrorStackTrace(String errorStackTrace) 
        this.errorStackTrace = errorStackTrace;
    

    public String getCause() 
        return cause;
    

    public void setCause(String cause) 
        this.cause = cause;
    

    public String getCauseStackTrace() 
        return causeStackTrace;
    

    public void setCauseStackTrace(String causeStackTrace) 
        this.causeStackTrace = causeStackTrace;
    

    public int getErrorCode() 
        return errorCode;
    

    public void setErrorCode(int errorCode) 
        this.errorCode = errorCode;
    



GenericExceptionMapper.java

package com.extratechnology.caaews.exception;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import com.extratechnology.caaews.model.ErrorMessage;

@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable>

    @Override
    public Response toResponse(Throwable ex) 
        System.out.println("Stack Trace:");
        ex.printStackTrace();
        System.out.println("Cause:");
        Throwable cause = ex.getCause();
        if (cause != null) 
            cause.printStackTrace();    
        
        ErrorMessage message = new ErrorMessage(
                ex.getMessage(),
                GenericExceptionMapper.getStackTrack(ex),
                cause.getMessage(),
                GenericExceptionMapper.getStackTrack(cause),
                Status.INTERNAL_SERVER_ERROR.getStatusCode()
                );
        return Response
                .status(Status.INTERNAL_SERVER_ERROR)
                .entity(message)
                .build();
    

    private static String getStackTrack(Throwable ex) 
        StringBuilder sb = new StringBuilder();
        String ls = System.lineSeparator();
        if (ex != null) 
            StackTraceElement[] steAll = ex.getStackTrace();
            for (StackTraceElement ste : steAll) 
                sb.append(ste.toString());
                sb.append(ls);
               
        
        return sb.toString();
    

system.out.println 会在调试时提供控制台消息,并且您也会在出现错误时在 Web 浏览器中返回有效负载。

例如:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<errorMessage>
<cause>1 counts of IllegalAnnotationExceptions</cause>
<causeStackTrace>
com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException$Builder.check(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl.<init>(Unknown Source) com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(Unknown Source) com.sun.xml.internal.bind.v2.ContextFactory.createContext(Unknown Source) sun.reflect.GeneratedMethodAccessor20.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.newInstance(Unknown Source) javax.xml.bind.ContextFinder.find(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) javax.xml.bind.JAXBContext.newInstance(Unknown Source) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getStoredJaxbContext(AbstractJaxbProvider.java:312) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getJAXBContext(AbstractJaxbProvider.java:297) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:264) org.glassfish.jersey.jaxb.internal.AbstractJaxbProvider.getMarshaller(AbstractJaxbProvider.java:231) org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:175) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors$1.call(Errors.java:272) org.glassfish.jersey.internal.Errors$1.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</causeStackTrace>
<errorCode>500</errorCode>
<errorMessage>HTTP 500 Internal Server Error</errorMessage>
<errorStackTrace>
org.glassfish.jersey.jaxb.internal.AbstractRootElementJaxbProvider.writeTo(AbstractRootElementJaxbProvider.java:183) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.invokeWriteTo(WriterInterceptorExecutor.java:266) org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:251) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1135) org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:662) org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:395) org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:385) org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:280) org.glassfish.jersey.internal.Errors$1.call(Errors.java:272) org.glassfish.jersey.internal.Errors$1.call(Errors.java:268) org.glassfish.jersey.internal.Errors.process(Errors.java:316) org.glassfish.jersey.internal.Errors.process(Errors.java:298) org.glassfish.jersey.internal.Errors.process(Errors.java:268) org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:289) org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:256) org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:703) org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:416) org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:370) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:389) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:342) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:229) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:217) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518) org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091) org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) java.lang.Thread.run(Unknown Source)
</errorStackTrace>
</errorMessage>

【讨论】:

虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review 嗯,我正在努力适应它。我会发布答案。因此,删除和否决是不合适的。解决问题后,我将在此处发布带有代码的解决方案。所以我很感激人们在那之前没有删除答案或进一步投票。谢谢! 正如我在问题中解释的那样,您应该在准备好而不是之前发布答案。根据当前状态判断答案是否删除和投票。正确的做法是删除此答案并在完成后发布一个。 好吧。我从这个角度来看,那里有一个解决方案。无需其他人花费精力试图回答。我会尽快回复,很明显过滤器,而不是 @Path 和 @PathParams 是要走的路。【参考方案2】:

您的 JAX-RS 代码存在几个问题:

@Path 中的正则表达式

@Path 注释的value 仅在参数模板内和: 字符之后解析正则表达式。您正在尝试在参数模板 "\\.format" 之外使用正则表达式,因此它不会将其解析为正则表达式。

路径分辨率

方法的路径包括类路径段,后面跟着它自己的路径段。当您尝试调用 /et.format 时,您的代码会建议路径 /et/.format/et,这在任何地方都没有定义,因此是 404。


这是一个可以根据您的代码工作的示例:

@Path("et")
public class Resource 

    private static final String JSON = "json";
    private static final String XML = "xml";

    @GET
    @Path(".format:(" + JSON + "|" + XML + ")")
    @Produces(value =  MediaType.APPLICATION_JSON, MediaType.TEXT_XML ) // not XML?
    public String getLog(@PathParam("format") String format) 
        switch (format) 
            case JSON:
                this.getJSON();
                break;
            case XML:
                this.getXML();
        
        return format;
    

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public void getJSON() 
        System.out.println("in JSON");
    

    @GET
    @Path("otherPath")
    @Produces(MediaType.APPLICATION_XML)
    public void getXML() 
        System.out.println("in XML");
    

您的有效请求现在将是:

http://localhost:18080/caaews/webapi/et(JSON 到 getJSON) http://localhost:18080/caaews/webapi/et/otherPath(XML 到 getXML) http://localhost:18080/caaews/webapi/et/.xml(XML 到 getLog) http://localhost:18080/caaews/webapi/et/.json(JSON 到 getLog

根据您的需要更改路径。我将"otherPath" 用于XML 方法,因为它不能与空路径JSON 方法冲突。我不推荐这种约定。

注意事项:

switch 语句中使用break。 为减少出现错误的机会,可将常量用于可重用字符串等,就像我对自定义格式类型所做的那样。 enum 会更好。

编辑:

请求现在有一个路径/et/&lt;something&gt;.format。如果我们将路径参数的范围扩大到包括整个段&lt;something&gt;.format,然后以编程方式提取格式,则可以实现这一点:

@GET
@Path("segment:[a-zA-Z0-9_]*\\.(" + JSON + "|" + XML + ")")
@Produces(value =  MediaType.APPLICATION_JSON, MediaType.TEXT_XML )
public String getLog(@PathParam("segment") String segment) 
    String format = segment.substring(segment.indexOf('.') + 1);
    switch (format) 
        case JSON:
            this.getJSON();
            break;
        case XML:
            this.getXML();
    
    return format;

正则表达式[a-zA-Z0-9_]* 表示任何字母数字或下划线一次或多次。您可以用您想要的任何限制替换该部分。有关允许的字符,请参阅 URL 规范。

【讨论】:

感谢正则表达式的评论。我不确定以正则表达式格式“以”结尾的方式。我想这是涉及插入符号或美元符号的东西,并转义了点。今天在我的待办事项清单上进行试验。我还添加了关于可能的 servlet 过滤器的评论,因此它可以有效地用于所有 Web 服务调用并在一个地方进行编码。 @JGFMK 查看我的编辑。下次请明确说明想要的结果。 您好!引用:“我见过一个 Java RESTFUL 网络服务,它允许在 URL 中请求内容类型,并在末尾带有扩展名,例如 .xml .json” @JGFMK 这并不具体,我的初始代码就是这样做的,而这并不是您想要的。请参阅How to Ask 和minimal reproducible example。无论如何,现在答案解决了您发布的问题。 很好的答案,除了你对生产/消费有误。他没有派人去;资源方法可以生成 XML 或 JSON。 @Produces 实际上是这里的正确注解。【参考方案3】:

即使您没有标记jersey,您的问题也表明您正在使用泽西岛,因此我将发布针对泽西岛的解决方案。 Jersey 提供的是property that you can use to set the media type mappings

ServerPropeties.MEDIA_TYPE_MAPPINGS

public static final String MEDIA_TYPE_MAPPINGS

定义 URI 扩展到媒体类型的映射。该属性由UriConnegFilter 使用。有关媒体类型映射的更多信息,请参阅它的 javadoc。

属性值必须是StringString[]Map&lt;String, MediaType&gt; 的实例。每个 String 实例代表一个或多个 uri-extension-to-media-type 映射条目,由逗号 (",") 分隔。每个映射条目都是由冒号 (":") 分隔的键值对。下面是一个可接受的字符串值映射 txt 扩展名到 text/plain 和 xml 扩展名到 application/xml 的示例:

txt : text/plain, xml : application/xml

未设置默认值。

配置属性的名称是“jersey.config.server.mediaTypeMappings”。

Java 配置示例

final Map<String, MediaType> mediaTypeMappings = new HashMap<>();
mediaTypeMappings.put("xml", MediaType.APPLICATION_XML_TYPE);
mediaTypeMappings.put("json", MediaType.APPLICATION_JSON_TYPE);

final ResourceConfig rc = new ResourceConfig()
        .packages("com.example.jersey")
        .property(ServerProperties.MEDIA_TYPE_MAPPINGS, mediaTypeMappings);

web.xml 配置示例

<servlet>
    <servlet-name>JerseyApplication</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.example</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.mediaTypeMappings</param-name>
        <param-value>xml:application/xml, json:application/json</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

【讨论】:

这是一个比我的方法更简洁的解决方案,但由于某种原因,它会产生类似的 HTTP 500 错误:0:0:0:0:0:0:0:1 - - [26/Nov/2017:07:57:12 +0000] "GET /caaews/webapi/et HTTP/1.1" 500 1082 0:0:0:0:0:0:0:1 - - [26/Nov/2017:07:58:26 +0000] "GET /caaews/webapi/et.xml HTTP/1.1" 500 1082 0:0:0:0:0:0:0:1 - - [26/Nov/2017:07:58:39 +0000] "GET /caaews/webapi/et.json HTTP/1.1" 200 15 即。只有 .JSON 有效... 我还尝试将 getXML 和 getJSON 合并到一个 getLog 方法中,而不是各有一个,并且在方法级别有双重 @Produces 注释。但这也没有什么不同。 我正在尝试将更好的错误处理集成到解决方案中。我正在为任何追随我脚步的人查看这些线程.. ***.com/questions/31289470/… ***.com/questions/25420697/jersey-2-exception-handing ***.com/questions/31501110/… 您的 Log 类中需要一个无参数构造函数。这对于带有 JAXB 的 XML 是必需的。你可能会遇到一个被吞噬的错误 这确实是正确的。那解决了它。我想我唯一剩下的问题是“默认”是 xml,如何将其更改为 JSON?【参考方案4】:

您是否尝试过在班级级别摆脱 @Path 注释?您的方法级别注释将是:

@Path("et.format")

我认为您当前的实施正在创建一个与以下路径匹配的子资源:

/et/format

【讨论】:

您对子资源的看法可能是对的......但是当我尝试这个并在 getXML 和 getJSON 方法上使用 @Path("et") 时,我突然得到了 404 ... 我还注意到我从未在 @Produces 的班级级别提供双重内容类型。当我这样做的时候。如果我使用该格式,它确实可以正确委托。 localhost:18080/caaews/webapi/et/.json 或 localhost:18080/caaews/webapi/et/.xml。所以这就解释了为什么代码不起作用(我还删除了路径中的 \\ 并只使用了“.format”。所以我猜@Path/@PathParam 方法毕竟不是我需要的URL 的扩展样式。我会更多地研究 servlet 过滤器。 为什么需要单独的方法?假设 Log 是可编组和可序列化的,您应该只需要根据您支持的内容(例如 application/json、application/xml、text/xml 等)更新 @Produces 注释。跨度> 如果需要单独的方法,那么为什么不为每个@Path("et.xml")@Path("et.json") 明确定义路径?

以上是关于JAX-RS(Jersey 2 实现)内容协商,带有 URL 扩展名 .xml 或 .json的主要内容,如果未能解决你的问题,请参考以下文章

Jersey 与 jax-rs 有啥区别

是否可以定义一个与其实现分离的 jax-rs 服务接口(使用 eclipse 和 jersey)?

JAX-RS (Jersey 2) - 使用 JSR 250 注释的授权

Jersey 框架如何在 REST 中实现 JAX-RS API?

使用 JAX-RS Jersey 进行身份验证和授权的简便方法

使用 JAX-RS Jersey 进行身份验证和授权的简便方法