徐昊 · TDD 项目实战 70 讲
徐昊
Thoughtworks 中国区 CTO
18159 人已学习
新⼈⾸单¥98
登录后,你可以任选4讲全文学习
课程目录
已完结/共 88 讲
实战项目二|RESTful开发框架:依赖注入容器 (24讲)
实战项目三|RESTful Web Services (44讲)
徐昊 · TDD 项目实战 70 讲
15
15
1.0x
00:00/00:00
登录|注册

64|RESTful Web Services(28):如何重构Resource、SubResource等接口与实现?

你好,我是徐昊。今天我们继续使用 TDD 的方式实现 RESTful Web Services。

回顾架构愿景与任务列表

目前我们已经实现了 ResourceRouter,和 UriTemplate 整体的架构愿景如下:
目前的任务列表:
Resource/RootResource/ResourceMethod
从 Path 标注中获取 UriTemplate
如不存在 Path 标注,则抛出异常
在处理请求派分时,可以根据客户端提供的 Http 方法,选择对应的资源方法
当请求与资源方法的 Uri 模版一致,且 Http 方法一致时,派分到该方法
没有资源方法于请求的 Uri 和 Http 方法一致时,返回 404
在处理请求派分时,可以支持多级子资源
当没有资源方法可以匹配请求时,选择最优匹配 SubResourceLocater,通过它继续进行派分
如果 SubResourceLocator 也无法找到满足的请求时,返回 404
代码为:
package geektime.tdd.rest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.container.ResourceContext;
import jakarta.ws.rs.core.GenericEntity;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
interface ResourceRouter {
OutboundResponse dispatch(HttpServletRequest request, ResourceContext resourceContext);
interface Resource {
Optional<ResourceMethod> match(UriTemplate.MatchResult result, String method, String[] mediaTypes, ResourceContext resourceContext, UriInfoBuilder builder);
}
interface RootResource extends Resource, UriHandler {
}
interface ResourceMethod extends UriHandler {
String getHttpMethod();
GenericEntity<?> call(ResourceContext resourceContext, UriInfoBuilder builder);
}
interface SubResourceLocator extends UriHandler {
Resource getSubResource(ResourceContext resourceContext, UriInfoBuilder uriInfoBuilder);
}
}
class DefaultResourceRouter implements ResourceRouter {
private Runtime runtime;
private List<RootResource> rootResources;
public DefaultResourceRouter(Runtime runtime, List<RootResource> rootResources) {
this.runtime = runtime;
this.rootResources = rootResources;
}
@Override
public OutboundResponse dispatch(HttpServletRequest request, ResourceContext resourceContext) {
String path = request.getServletPath();
UriInfoBuilder uri = runtime.createUriInfoBuilder(request);
Optional<ResourceMethod> method = UriHandlers.mapMatched(path, rootResources, (result, resource) -> findResourceMethod(request, resourceContext, uri, result, resource));
if (method.isEmpty()) return (OutboundResponse) Response.status(Response.Status.NOT_FOUND).build();
return (OutboundResponse) method.map(m -> m.call(resourceContext, uri)).map(entity -> Response.ok(entity).build())
.orElseGet(() -> Response.noContent().build());
}
private Optional<ResourceMethod> findResourceMethod(HttpServletRequest request, ResourceContext resourceContext, UriInfoBuilder uri, Optional<UriTemplate.MatchResult> matched, RootResource handler) {
return handler.match(matched.get(), request.getMethod(),
Collections.list(request.getHeaders(HttpHeaders.ACCEPT)).toArray(String[]::new), resourceContext, uri);
}
}
class RootResourceClass implements ResourceRouter.RootResource {
private PathTemplate uriTemplate;
private Class<?> resourceClass;
private ResourceMethods resourceMethods;
private SubResourceLocators subResourceLocators;
public RootResourceClass(Class<?> resourceClass) {
this.resourceClass = resourceClass;
this.uriTemplate = new PathTemplate(resourceClass.getAnnotation(Path.class).value());
this.resourceMethods = new ResourceMethods(resourceClass.getMethods());
this.subResourceLocators = new SubResourceLocators(resourceClass.getMethods());
}
@Override
public Optional<ResourceRouter.ResourceMethod> match(UriTemplate.MatchResult result, String method, String[] mediaTypes, ResourceContext resourceContext, UriInfoBuilder builder) {
builder.addMatchedResource(resourceContext.getResource(resourceClass));
String remaining = Optional.ofNullable(result.getRemaining()).orElse("");
return resourceMethods.findResourceMethods(remaining, method).or(() ->
subResourceLocators.findSubResourceMethods(remaining, method, mediaTypes, resourceContext, builder));
}
@Override
public UriTemplate getUriTemplate() {
return uriTemplate;
}
}
class SubResource implements ResourceRouter.Resource {
private Object subResource;
private ResourceMethods resourceMethods;
private SubResourceLocators subResourceLocators;
public SubResource(Object subResource) {
this.subResource = subResource;
this.resourceMethods = new ResourceMethods(subResource.getClass().getMethods());
this.subResourceLocators = new SubResourceLocators(subResource.getClass().getMethods());
}
@Override
public Optional<ResourceRouter.ResourceMethod> match(UriTemplate.MatchResult result, String method, String[] mediaTypes, ResourceContext resourceContext, UriInfoBuilder builder) {
String remaining = Optional.ofNullable(result.getRemaining()).orElse("");
return resourceMethods.findResourceMethods(remaining, method).or(() -> subResourceLocators.findSubResourceMethods(remaining, method, mediaTypes, resourceContext, builder));
}
}
class DefaultResourceMethod implements ResourceRouter.ResourceMethod {
private String httpMethod;
private UriTemplate uriTemplate;
private Method method;
public DefaultResourceMethod(Method method) {
this.method = method;
this.uriTemplate = new PathTemplate(Optional.ofNullable(method.getAnnotation(Path.class)).map(Path::value).orElse(""));
this.httpMethod = Arrays.stream(method.getAnnotations()).filter(a -> a.annotationType().isAnnotationPresent(HttpMethod.class))
.findFirst().get().annotationType().getAnnotation(HttpMethod.class).value();
}
@Override
public String getHttpMethod() {
return httpMethod;
}
@Override
public UriTemplate getUriTemplate() {
return uriTemplate;
}
@Override
public GenericEntity<?> call(ResourceContext resourceContext, UriInfoBuilder builder) {
return null;
}
@Override
public String toString() {
return method.getDeclaringClass().getSimpleName() + "." + method.getName();
}
}
class ResourceMethods {
private Map<String, List<ResourceRouter.ResourceMethod>> resourceMethods;
public ResourceMethods(Method[] methods) {
this.resourceMethods = getResourceMethods(methods);
}
private static Map<String, List<ResourceRouter.ResourceMethod>> getResourceMethods(Method[] methods) {
return Arrays.stream(methods).filter(m -> Arrays.stream(m.getAnnotations())
.anyMatch(a -> a.annotationType().isAnnotationPresent(HttpMethod.class)))
.map(DefaultResourceMethod::new)
.collect(Collectors.groupingBy(ResourceRouter.ResourceMethod::getHttpMethod));
}
public Optional<ResourceRouter.ResourceMethod> findResourceMethods(String path, String method) {
return Optional.ofNullable(resourceMethods.get(method)).flatMap(methods -> UriHandlers.match(path, methods, r -> r.getRemaining() == null));
}
}
class SubResourceLocators {
private final List<ResourceRouter.SubResourceLocator> subResourceLocators;
public SubResourceLocators(Method[] methods) {
subResourceLocators = Arrays.stream(methods).filter(m -> m.isAnnotationPresent(Path.class) &&
Arrays.stream(m.getAnnotations()).noneMatch(a -> a.annotationType().isAnnotationPresent(HttpMethod.class)))
.map((Function<Method, ResourceRouter.SubResourceLocator>) DefaultSubResourceLocator::new).toList();
}
public Optional<ResourceRouter.SubResourceLocator> findSubResource(String path) {
return UriHandlers.match(path, subResourceLocators);
}
public Optional<ResourceRouter.ResourceMethod> findSubResourceMethods(String path, String method, String[] mediaTypes, ResourceContext resourceContext, UriInfoBuilder builder) {
return UriHandlers.mapMatched(path, subResourceLocators, (result, locator) -> locator.getSubResource(resourceContext, builder).match(result.get(), method, mediaTypes, resourceContext, builder));
}
static class DefaultSubResourceLocator implements ResourceRouter.SubResourceLocator {
private PathTemplate uriTemplate;
private Method method;
public DefaultSubResourceLocator(Method method) {
this.method = method;
this.uriTemplate = new PathTemplate(method.getAnnotation(Path.class).value());
}
@Override
public UriTemplate getUriTemplate() {
return uriTemplate;
}
@Override
public String toString() {
return method.getDeclaringClass().getSimpleName() + "." + method.getName();
}
@Override
public ResourceRouter.Resource getSubResource(ResourceContext resourceContext, UriInfoBuilder uriInfoBuilder) {
Object resource = uriInfoBuilder.getLastMatchedResource();
try {
Object subResource = method.invoke(resource);
uriInfoBuilder.addMatchedResource(subResource);
return new SubResource(subResource);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

视频演示

进入今天的环节:
00:00 / 00:00
    1.0x
    • 2.0x
    • 1.5x
    • 1.25x
    • 1.0x
    • 0.75x
    • 0.5x
    网页全屏
    全屏
    00:00

    思考题

    后续任务要如何划分?
    欢迎把你的想法分享在留言区,也欢迎把你的项目代码分享出来。相信经过你的思考与实操,学习效果会更好!
    确认放弃笔记?
    放弃后所记笔记将不保留。
    新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
    批量公开的笔记不会为你同步至部落
    公开
    同步至部落
    取消
    完成
    0/2000
    荧光笔
    直线
    曲线
    笔记
    复制
    AI
    • 深入了解
    • 翻译
      • 英语
      • 中文简体
      • 中文繁体
      • 法语
      • 德语
      • 日语
      • 韩语
      • 俄语
      • 西班牙语
      • 阿拉伯语
    • 解释
    • 总结

    本文介绍了如何使用TDD的方式实现RESTful Web Services,并重点讨论了如何重构Resource、SubResource等接口与实现。文章首先回顾了架构愿景与任务列表,然后详细介绍了代码实现部分,包括ResourceRouter接口、RootResource、ResourceMethod、SubResourceLocator等相关类和接口的实现。通过代码示例和解释,读者可以了解如何使用Java实现RESTful Web Services的相关功能,包括请求派分、资源方法匹配、子资源支持等。文章还提出了思考题,鼓励读者分享自己的想法和项目代码。整体来说,本文通过具体的代码实现和思考题,帮助读者更好地理解和实践RESTful Web Services的相关知识。

    仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
    《徐昊 · TDD 项目实战 70 讲》
    新⼈⾸单¥98
    立即购买
    登录 后留言

    全部留言(1)

    • 最新
    • 精选
    • Jason
      amazing
      2022-09-14归属地:上海
    收起评论
    大纲
    固定大纲
    回顾架构愿景与任务列表
    视频演示
    思考题
    显示
    设置
    留言
    1
    收藏
    沉浸
    阅读
    分享
    手机端
    快捷键
    回顶部