木心

毕竟几人真得鹿,不知终日梦为鱼

导航

springboot+jersey+swagger,swagger界面接口自定义排序,全局异常处理

1、springboot 2.6.3 + jersey

  依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jersey</artifactId>
</dependency>

<!-- 使用JAXB 在Jersey中将对象转换为XML -->
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-jaxb</artifactId>
</dependency>

  jersey 配置类

/**
 * 配置 jersey rest api 资源路径, Spring Boot建议在使用
 * ResourceConfig添加资源类的时候,不要使用packages方法去自动扫描,建议还是使用register添加。
 * 
 * 注解ApplicationPath,默认为/*
 * 
 */
@Component
@ApplicationPath("/jersey")
public class JerseyConfig extends ResourceConfig  {
    
    public JerseyConfig() {
        //packages("com.oy"); // 扫描 com.oy 包,使其识别 JAX-RS 注解
        register(UserController.class);
    }
    
}

  模型类

package com.oy.model;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "user") // 将该类转化成XML时,说明这个是XML的根节点
public class User {
    private String name;
    private String email;
    
    public User() {}
    
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    @XmlAttribute // 属性
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @XmlElement // 节点
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
View Code

  资源类

@Component
@Path("/users")
public class UserController {

    /**
     * http://localhost:8080/jersey/users/json
     */
    @GET
    @Path("/json")
    @Produces(MediaType.APPLICATION_JSON)
    public User getJson() {
        List<User> users = new ArrayList<>();
        User user = new User("xxx", "xxx@163.com");
        users.add(user);
        return users.get(0);
    }

    /**
     * 使用 JAXB 在 Jersey 中将对象转换为 XML
     */
    @GET
    @Path("/xml")
    @Produces(MediaType.APPLICATION_XML)
    public User getXml() {
        List<User> users = new ArrayList<>();
        User user = new User("xxx", "xxx@163.com");
        users.add(user);
        return users.get(0);
    }

    // 返回多节点xml
    @GET
    @Path("/getUsers")
    @Produces(MediaType.APPLICATION_XML)
    public List<User> getAllUser() {
        List<User> users = new ArrayList<User>();
        users.add(new User("001", "HuiJia"));
        users.add(new User("002", "Andy"));
        users.add(new User("003", "BoWen"));
        return users;
    }

    // 返回单节点xml
    @GET
    @Produces(MediaType.APPLICATION_XML)
    @Path("/getUser")
    public User getUser() {
        User user = new User("004", "Lucy");
        return user;
    }

}

 

问题:jersey:MessageBodyWriter not found for media type=application/xml

<!-- 使用JAXB 在Jersey中将对象转换为XML -->
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-jaxb</artifactId>
</dependency>

 

2、Jersey&Jackson返回json对象时隐藏null字段

https://www.jianshu.com/p/1cc610e9e93d

 

3、jersey 集成 swagger,swagger界面接口自定义排序

  /resources/static.apidocs 目录的资源文件可以从gitee下载(搜springboot jersey swagger)

  依赖

<dependency>
    <groupId>io.swagger</groupId>
    <artifactId>swagger-jersey2-jaxrs</artifactId>
    <version>1.6.5</version>
</dependency>

  实现接口自定义排序:通过自定义BaseApiListingResource的子类,当第一次生成Swagger对象后,对swagger对象的tags进行排序,从而实现接口自定义排序

public class MySwaggerApiListingResource extends BaseApiListingResource {

    protected Response getListingJsonResponse(
            Application app,
            ServletContext servletContext,
            ServletConfig servletConfig,
            HttpHeaders headers,
            UriInfo uriInfo) {
        Swagger swagger = process(app, servletContext, servletConfig, headers, uriInfo);

        /* 对 tags 进行排序 */
        List<Tag> tags = swagger.getTags();

        // 获取添加@Api注解的类
        SwaggerContextService ctxService = new SwaggerContextService()
                .withServletConfig(servletConfig)
                .withBasePath(getBasePath(uriInfo));
        Scanner scanner = ctxService.getScanner();
        Set<Class<?>> classes = scanner.classes(); // 所有扫描的类
        final Map<String, Integer> map = new TreeMap<>(); // 
        if (classes != null && !classes.isEmpty()) {
            for (Class<?> aClass : classes) {
                Api annotation = AnnotationUtils.findAnnotation(aClass, Api.class);
                if (annotation == null) continue;
                map.put(annotation.value(), annotation.position());
            }
        }
        
        tags.sort((e1, e2) -> {
            Integer order1 = map.get(e1.getName());
            Integer order2 = map.get(e2.getName());
            if (order1 != null && order2 != null) {
                return order1.compareTo(order2);
            }
            return 0;
        });
        

        if (swagger != null) {
            return Response.ok().entity(swagger).type(MediaType.APPLICATION_JSON).build();
        }
        return Response.status(404).build();
    }

    private static String getBasePath(UriInfo uriInfo) {
        if (uriInfo != null) {
            return uriInfo.getBaseUri().getPath();
        } else {
            return "/";
        }
    }
    
}

  SwaggerApiListingResource

@Path("/swagger.{type:json|yaml}")
public class SwaggerApiListingResource extends MySwaggerApiListingResource {

    @Context
    ServletContext context;

    @GET
    @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
    @ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
    public Response getListing(
            @Context Application app,
            @Context ServletConfig sc,
            @Context HttpHeaders headers,
            @Context UriInfo uriInfo,
            @PathParam("type") String type) {
        if (StringUtils.isNotBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
            return getListingYamlResponse(app, context, sc, headers, uriInfo);
        } else {
            return getListingJsonResponse(app, context, sc, headers, uriInfo);
        }
    }
}

  JerseyConfig

/**
 * 配置 jersey rest api 资源路径, SpringBoot 建议使用 ResourceConfig 添加资源类的时候,
 * 不要使用 packages() 方法去自动扫描,建议还是使用 register() 添加。
 * <p>
 * 注解ApplicationPath,默认为/*
 */
@Component
@ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {

    public JerseyConfig() {
        // 扫描包,使其识别 JAX-RS 注解
        //packages("com.oy.api");
        // 注册站点服务 注册顺序与界面排列顺序无关, 根据@Api注解的position排序, 并且@Api注解必须配置value值
        register(UserService.class);
        register(CityService.class);
        // 注册过滤器
        //register(XxxFilter.class);
    }

    @PostConstruct
    private void init() {
        configureSwagger();
    }

    /**
     * 访问Swagger主页: http://localhost:8080/apidocs/index.html
     */
    private void configureSwagger() {
        // Available at localhost:port/swagger.json
        //this.register(ApiListingResource.class);
        this.register(SwaggerApiListingResource.class); // 使用自定义的swagger.json访问服务类
        this.register(SwaggerSerializers.class);
        register(AcceptHeaderApiListingResource.class);

        BeanConfig config = new BeanConfig();
        config.setConfigId("1001");
        config.setTitle("xxx Open API");
        config.setVersion("v1.0.0");
        config.setContact("xxx");
        config.setSchemes(new String[]{"http", "https"});
        config.setBasePath("/api");
        config.setResourcePackage("com.oy.api");
        config.setPrettyPrint(true);
        config.setScan(true);
    }
}

 

4、springboot-jersey全局异常处理

  在jersey中注册了一个全局异常处理器 GlobalExceptionHandler

import com.oy.result.ApiResult;

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

@Provider
public class GlobalExceptionHandler implements ExceptionMapper<Throwable> {

    public Response toResponse(Throwable ex) {

        if (ex instanceof WebApplicationException) {
            WebApplicationException exception = (WebApplicationException) ex;
            Response response = exception.getResponse();
            String msg = response.getStatusInfo().toString();
            ApiResult<Object> apiResult = ApiResult.fail(response.getStatus(), msg);

            return Response.status(response.getStatus()).entity(apiResult).type("application/json;charset=utf-8").build();
        }
        String msg = ex.getMessage();
        if (msg == null || !msg.isEmpty())
            msg = ex.getClass().getName();
        ApiResult<Object> apiResult = ApiResult.fail(500, msg);
        return Response.status(500).entity(apiResult).type("application/json;charset=utf-8").build();
    }
}
View Code

  但是发现,一般的异常是会走GlobalExceptionHandler的,包括404错误等都会走GlobalExceptionHandler。但是某些情况不会走GlobalExceptionHandler:比如请求传递错误格式的json数据时。

  请求正常传递json数据时

 

  请求传递错误格式的json数据时

 

  原因是jersey的ExceptionMapperFactory默认创建了三个错误处理器,如下图,导致当发生JsonParseException异常时,优先走了jersey默认的处理器,而没有走GlobalExceptionHandler。

 

  解决:给JsonParseException,JsonMappingException,ValidationException定义处理器

  JerseyConfig中注册

// 注册异常处理器
register(JsonMappingExceptionHandler.class, 1);
register(JsonParseExceptionHandler.class, 1);
register(ValidationExceptionHandler.class, 1);
register(GlobalExceptionHandler.class);

  JsonParseExceptionHandler

import com.fasterxml.jackson.core.JsonParseException;
import com.oy.result.ApiResult;

import javax.annotation.Priority;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
@Priority(1)
public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> {

    @Override
    public Response toResponse(JsonParseException ex) {
        ApiResult<Object> apiResult = ApiResult.fail(400, ex.getMessage());
        return Response.status(400).entity(apiResult).type("application/json;charset=utf-8").build();
    }
}
View Code

 

posted on 2022-02-22 18:18  wenbin_ouyang  阅读(818)  评论(0编辑  收藏  举报