如何在Google Cloud Endpoints(Java)中按API方法动态忽略特定属性
嘿,这个需求在Google Cloud Endpoints(Java)里有几种很实用的实现方式,我给你拆解一下,都是项目里常用的思路:
方案1:使用DTO(数据传输对象)拆分响应结构
这是最直观也最容易维护的方式——核心就是为不同的API场景定义不同的响应对象,彻底避免直接暴露原始实体类的受保护属性。
步骤如下:
- 先保留你的原始
Book实体类(包含所有属性,不用做特殊改动):
public class Book { private String author; private Integer views; private String code; // 你的受保护属性 // 构造器、getter、setter这里就省略啦 }
- 创建两个DTO类:
BookResponse:给大部分API用,只包含需要对外暴露的属性:public class BookResponse { private String author; private Integer views; // 从Book实体快速转换的构造器 public BookResponse(Book book) { this.author = book.getAuthor(); this.views = book.getViews(); } // getter方法省略 }BookWithCodeResponse:给那两个指定API用,带上code属性:public class BookWithCodeResponse { private String author; private Integer views; private String code; public BookWithCodeResponse(Book book) { this.author = book.getAuthor(); this.views = book.getViews(); this.code = book.getCode(); } // getter方法省略 }
- 在Endpoint方法里返回对应的DTO就行:
@Api(name = "bookstore", version = "v1") public class BookEndpoint { // 大部分API返回不带code的响应 @ApiMethod(name = "books.list", path = "books") public List<BookResponse> listBooks() { List<Book> books = // 从数据库或其他数据源获取Book列表 return books.stream().map(BookResponse::new).collect(Collectors.toList()); } // 第一个需要返回code的指定API @ApiMethod(name = "books.getWithCode", path = "books/{id}/with-code") public BookWithCodeResponse getBookWithCode(@Named("id") String id) { Book book = // 根据id查询到对应的Book return new BookWithCodeResponse(book); } // 第二个需要返回code的指定API @ApiMethod(name = "books.createWithCode", path = "books/create-with-code") public BookWithCodeResponse createBookWithCode(Book book) { // 执行保存Book的业务逻辑 return new BookWithCodeResponse(book); } }
这个方案的好处是边界特别清晰,不同API的响应结构一眼就能看懂,后期维护起来也省心,新增属性时只要对应修改DTO就行。
方案2:利用Jackson的JSON视图(JsonView)
如果不想写一堆DTO,用Jackson的@JsonView注解来控制属性序列化是更简洁的选择,不用额外创建类,直接在原始实体上标记即可。
步骤如下:
- 先定义两个视图标记接口(只是标记用,不需要写任何方法):
// 普通视图:给大部分API用,不包含code public interface NormalBookView {} // 带code的视图:给指定API用,继承普通视图意味着会包含所有普通属性+code public interface BookWithCodeView extends NormalBookView {}
- 在
Book实体类上给每个属性标注对应的视图:
import com.fasterxml.jackson.annotation.JsonView; public class Book { @JsonView(NormalBookView.class) private String author; @JsonView(NormalBookView.class) private Integer views; @JsonView(BookWithCodeView.class) private String code; // 只有在BookWithCodeView场景下才会被序列化 // 构造器、getter、setter省略 }
- 在Endpoint方法上指定要使用的视图:
import com.fasterxml.jackson.annotation.JsonView; import com.google.api.server.spi.config.Api; import com.google.api.server.spi.config.ApiMethod; @Api(name = "bookstore", version = "v1") public class BookEndpoint { // 大部分API用普通视图,不会返回code @ApiMethod(name = "books.list", path = "books") @JsonView(NormalBookView.class) public List<Book> listBooks() { return // 从数据源获取Book列表 } // 第一个指定API:用带code的视图 @ApiMethod(name = "books.getWithCode", path = "books/{id}/with-code") @JsonView(BookWithCodeView.class) public Book getBookWithCode(@Named("id") String id) { return // 根据id查询Book } // 第二个指定API:同样用带code的视图 @ApiMethod(name = "books.createWithCode", path = "books/create-with-code") @JsonView(BookWithCodeView.class) public Book createBookWithCode(Book book) { // 保存Book逻辑 return book; } }
注意:Google Cloud Endpoints默认支持Jackson注解,如果你的项目有自定义的ObjectMapper配置,要确保启用了视图支持。这个方案的优势是代码冗余少,适合属性变化不频繁的场景。
方案3:自定义响应拦截器(进阶玩法)
如果需要更全局的动态控制(比如根据某些业务规则判断是否返回code),可以自定义Jackson的WriterInterceptor,在序列化前判断当前的API方法,决定是否包含code属性。不过这个方式相对复杂,一般前两个方案就足够覆盖需求了,这里给个简单示例参考:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.ext.WriterInterceptor; import jakarta.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; public class BookCodeFilterInterceptor implements WriterInterceptor { @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { // 这里可以通过context获取当前API的路径、方法信息,判断是否是指定的两个API boolean needIncludeCode = checkIfNeedCode(context); ObjectMapper mapper = (ObjectMapper) context.getProviders() .getContextResolver(ObjectMapper.class, null) .getContext(null); FilterProvider filterProvider; if (needIncludeCode) { // 序列化所有属性(包含code) filterProvider = new SimpleFilterProvider().setDefaultFilter(SimpleBeanPropertyFilter.serializeAll()); } else { // 排除code属性 filterProvider = new SimpleFilterProvider().setDefaultFilter(SimpleBeanPropertyFilter.serializeAllExcept("code")); } mapper.setFilterProvider(filterProvider); context.proceed(); } // 自定义判断逻辑:比如根据请求路径或方法名判断 private boolean checkIfNeedCode(WriterInterceptorContext context) { String path = context.getUriInfo().getPath(); return path.contains("/with-code") || path.contains("/create-with-code"); } }
然后在你的Endpoint类上添加@Interceptors(BookCodeFilterInterceptor.class)注解就能生效。
内容的提问来源于stack exchange,提问作者shadyhossin
相关产品推荐
相关产品推荐

