【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler (下)

简介: 【小家Spring】Spring MVC容器的web九大组件之---HandlerAdapter源码详解---一篇文章带你读懂返回值处理器HandlerMethodReturnValueHandler (下)

AsyncHandlerMethodReturnValueHandler


它是一个子接口,增加了一个方法。这个接口是Spring4.2提供的,挺有意思的一个接口,Spring内部并没有提供任何实现。


// @since 4.2
// 支持异步类型的返回值处理程序。此类返回值类型需要优先处理,以便异步值可以“展开”。
// 异步实现此接口并不是必须的,但是若你需要在处理程序之前执行,就需要实现这个接口了~~~
// 因为默认情况下:我们自定义的Handler它都是在内置的Handler后面去执行的~~~~
public interface AsyncHandlerMethodReturnValueHandler extends HandlerMethodReturnValueHandler {
  // 给定的返回值是否表示异步计算
  boolean isAsyncReturnValue(@Nullable Object returnValue, MethodParameter returnType);
}


需要注意的是,这个接口和异步好像并没有任何关系,只是体现出了它的优先级。


因为默认情况下我们定义custom自己的处理器,排名都是靠后的。但是如果你定义了一个实现类,实现的是AsyncHandlerMethodReturnValueHandler这个子接口,你的排名就会靠前执行了~~~


由于上面已有类似的例子了,此处就不花篇幅举例了。


关于Spring MVC异步处理的几个返回值处理器


因为Spring MVC支持多种异步返回的方式,因此放在此处一起讲。推荐先参考博文:

【小家Spring】高性能关键技术之—体验Spring MVC的异步模式(Callable、WebAsyncTask、DeferredResult) 基础使用篇


StreamingResponseBodyReturnValueHandler

Spring4.2才出来。(因为StreamingResponseBody是Spring4.2才出来的~~~它很方便做文件下载)


public class StreamingResponseBodyReturnValueHandler implements HandlerMethodReturnValueHandler {
  // 显然这里支持返回值直接是StreamingResponseBody类型,也支持你用`ResponseEntity`在包一层
  // ResponseEntity的泛型类型必须是StreamingResponseBody类型~~~
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    if (StreamingResponseBody.class.isAssignableFrom(returnType.getParameterType())) {
      return true;
    } else if (ResponseEntity.class.isAssignableFrom(returnType.getParameterType())) {
      Class<?> bodyType = ResolvableType.forMethodParameter(returnType).getGeneric().resolve();
      return (bodyType != null && StreamingResponseBody.class.isAssignableFrom(bodyType));
    }
    return false;
  }
  @Override
  @SuppressWarnings("resource")
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 从这句代码也可以看出,只有返回值为null了,它才关闭,否则可以持续不断的向response里面写东西
    if (returnValue == null) {
      mavContainer.setRequestHandled(true);
      return;
    }
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    Assert.state(response != null, "No HttpServletResponse");
    ServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
    // 从ResponseEntity里body提取出来~~~~~
    if (returnValue instanceof ResponseEntity) {
      ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;
      response.setStatus(responseEntity.getStatusCodeValue());
      outputMessage.getHeaders().putAll(responseEntity.getHeaders());
      returnValue = responseEntity.getBody();
      if (returnValue == null) {
        mavContainer.setRequestHandled(true);
        outputMessage.flush();
        return;
      }
    }
    ServletRequest request = webRequest.getNativeRequest(ServletRequest.class);
    Assert.state(request != null, "No ServletRequest");
    ShallowEtagHeaderFilter.disableContentCaching(request); // 禁用内容缓存
    Assert.isInstanceOf(StreamingResponseBody.class, returnValue, "StreamingResponseBody expected");
    StreamingResponseBody streamingBody = (StreamingResponseBody) returnValue;
    // 最终也是开启了一个Callable 任务,最后交给WebAsyncUtils去执行的~~~~
    Callable<Void> callable = new StreamingResponseBodyTask(outputMessage.getBody(), streamingBody);
    // WebAsyncUtils.getAsyncManager得到的是一个`WebAsyncManager`对象
    // startCallableProcessing会把callable任务都包装成一个`WebAsyncTask`,最终交给`AsyncTaskExecutor`执行
    // 至于异步的详细执行原理,请参考上面的相关博文,此处只点一下~~~~
    WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
  }
  // 这个任务很简单,实现了Callable的call方法,它就是相当于启一个线程,把本次body里面的内容写进response输出流里面~~~
  // 但是此时输出流并不会关闭~~~~
  private static class StreamingResponseBodyTask implements Callable<Void> {
    private final OutputStream outputStream;
    private final StreamingResponseBody streamingBody;
    public StreamingResponseBodyTask(OutputStream outputStream, StreamingResponseBody streamingBody) {
      this.outputStream = outputStream;
      this.streamingBody = streamingBody;
    }
    @Override
    public Void call() throws Exception {
      this.streamingBody.writeTo(this.outputStream);
      return null;
    }
  }
}


可以看出它的原理是自己构建出一个内部的异步线程,交给reponse的异步上下文去处理。


由上面代码课件,它不仅仅支持json内容,也是支持一直返回页面渲染的内容的。(只是大多数情况下我们把它和@ResponseBody联合使用)


DeferredResultMethodReturnValueHandler

这个也许是我们最为常用的一种异步处理方式。它不仅仅处理返回值类型为DeferredResult,也会处理返回值类型为ListenableFuture和CompletionStage(Java8新增的接口)类型的


public class DeferredResultMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
  // 它支持处理丰富的数据类型
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    Class<?> type = returnType.getParameterType();
    return (DeferredResult.class.isAssignableFrom(type) ||
        ListenableFuture.class.isAssignableFrom(type) ||
        CompletionStage.class.isAssignableFrom(type));
  }
  @Override
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 一样的  只有返回null了才代表此请求处理完成了
    if (returnValue == null) {
      mavContainer.setRequestHandled(true);
      return;
    }
    DeferredResult<?> result;
    // 此处是适配器模式的使用,最终都适配成了一个DeferredResult(使用的内部类实现的~~~)
    if (returnValue instanceof DeferredResult) {
      result = (DeferredResult<?>) returnValue;
    } else if (returnValue instanceof ListenableFuture) {
      result = adaptListenableFuture((ListenableFuture<?>) returnValue);
    } else if (returnValue instanceof CompletionStage) {
      result = adaptCompletionStage((CompletionStage<?>) returnValue);
    } else {
      // Should not happen...
      throw new IllegalStateException("Unexpected return value type: " + returnValue);
    }
    // 此处调用的异步方法是:startDeferredResultProcessing
    WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
  }
  // 下为两匿名内部实现类做的兼容适配、兼容处理~~~~~非常的简单~~~~
  private DeferredResult<Object> adaptListenableFuture(ListenableFuture<?> future) {
    DeferredResult<Object> result = new DeferredResult<>();
    future.addCallback(new ListenableFutureCallback<Object>() {
      @Override
      public void onSuccess(@Nullable Object value) {
        result.setResult(value);
      }
      @Override
      public void onFailure(Throwable ex) {
        result.setErrorResult(ex);
      }
    });
    return result;
  }
  private DeferredResult<Object> adaptCompletionStage(CompletionStage<?> future) {
    DeferredResult<Object> result = new DeferredResult<>();
    future.handle((BiFunction<Object, Throwable, Object>) (value, ex) -> {
      if (ex != null) {
        result.setErrorResult(ex);
      }
      else {
        result.setResult(value);
      }
      return null;
    });
    return result;
  }
}


CallableMethodReturnValueHandler


因为已经解释了StreamingResponseBodyReturnValueHandler,它最终也是转换为一个Callable去处理了的。因此此处返回值直接是callable,简直就不要太简单了~~~


ResponseBodyEmitterReturnValueHandler


XXXEmitter它相当于加强版的DeferredResult,它可以返回多个值给客户端。其实它的底层原理还是DeferredResult,此处不再做过多的介绍~~~~


AsyncTaskMethodReturnValueHandler


顾名思义,它是专门处理返回值类型为WebAsyncTask的异步请求形式。

// @since 3.2  因为WebAsyncTask这个时候才出来~~~
public class AsyncTaskMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
  @Nullable
  private final BeanFactory beanFactory;
  public AsyncTaskMethodReturnValueHandler(@Nullable BeanFactory beanFactory) {
    this.beanFactory = beanFactory;
  }
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    return WebAsyncTask.class.isAssignableFrom(returnType.getParameterType());
  }
  @Override
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    if (returnValue == null) {
      mavContainer.setRequestHandled(true);
      return;
    }
    WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) returnValue;
    if (this.beanFactory != null) {
      webAsyncTask.setBeanFactory(this.beanFactory);
    }
    // 我们发现它使用的也是startCallableProcessing...
  WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(webAsyncTask, mavContainer);
  }
}


代码逻辑非常简单


HandlerMethodReturnValueHandlerComposite:处理器合成


这是个厉害角色。其实它就是提供的所有的HandlerMethodReturnValueHandler集合,它定义了一个链表用于存储所有实现的HandlerMethodReturnValueHandler。


它用在RequestMappingHandlerAdapter和ExceptionHandlerExceptionResolver里,此处以RequestMappingHandlerAdapter为例:


public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
    implements BeanFactoryAware, InitializingBean {
  // 这里保存在用户自定义的一些处理器,大部分情况下无需自定义~~~
  @Nullable
  private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
  // 保存着所有的处理器~~~~上面custom自定义的最终也会放进来,放在尾部
  // 从它的命名似乎可议看出,它就是汇总~~~
  @Nullable
  private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
  // 可以看到即使你调用了set方法,最终也是会给你生成一个HandlerMethodReturnValueHandlerComposite
  public void setReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> returnValueHandlers) {
    if (returnValueHandlers == null) {
      this.returnValueHandlers = null;
    } else {
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
      this.returnValueHandlers.addHandlers(returnValueHandlers);
    }
  }
  @Nullable
  public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
    return (this.returnValueHandlers != null ? this.returnValueHandlers.getHandlers() : null);
  }
  // 它的初始化发生在这:
  @Override
  public void afterPropertiesSet() {
    ...
    // 相当于你自己没有set,那就交给Spring自己去处理吧~~~~
    if (this.returnValueHandlers == null) {
      // 这个getDefaultReturnValueHandlers()会装载15个左右的返回值处理器,可以说覆盖我们日常开发的所有
      // 若你自己自定义了custom的,放进了customReturnValueHandlers里,最终也会被加进来放进去~~~~ 放在末尾~~~~
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
  }
}


Composite:混合成的,由此可见它就是和汇总的作用。

那么接下来,就看看它本尊自身,提供了哪些能力?其实它的代码量不大:


// 首先发现,它也实现了接口HandlerMethodReturnValueHandler 
// 它会缓存以前解析的返回类型以加快查找速度
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
  private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
  // 返回的是一个只读视图
  public List<HandlerMethodReturnValueHandler> getHandlers() {
    return Collections.unmodifiableList(this.returnValueHandlers);
  }
  public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler handler) {
    this.returnValueHandlers.add(handler);
    return this;
  }
  public HandlerMethodReturnValueHandlerComposite addHandlers( @Nullable List<? extends HandlerMethodReturnValueHandler> handlers) {
    if (handlers != null) {
      this.returnValueHandlers.addAll(handlers);
    }
    return this;
  }
  // 由这两个可议看出,但凡有一个Handler支持处理这个返回值,就是支持的~~~
  @Override
  public boolean supportsReturnType(MethodParameter returnType) {
    return getReturnValueHandler(returnType) != null;
  }
  @Nullable
  private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      if (handler.supportsReturnType(returnType)) {
        return handler;
      }
    }
    return null;
  }
  // 这里就是处理返回值的核心内容~~~~~
  @Override
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // selectHandler选择收个匹配的Handler来处理这个返回值~~~~ 若一个都木有找到  抛出异常吧~~~~
    // 所有很重要的一个方法是它:selectHandler()  它来匹配,以及确定优先级
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
      throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
  }
  // 根据返回值,以及返回类型  来找到一个最为合适的HandlerMethodReturnValueHandler
  @Nullable
  private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    // 这个和我们上面的就对应上了  第一步去判断这个返回值是不是一个异步的value(AsyncHandlerMethodReturnValueHandler实现类只能我们自己来写~)
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      // 如果判断发现这个值是异步的value,那它显然就只能交给你自己定义的异步处理器处理了,别的处理器肯定就靠边站~~~~~
      if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
        continue;
      }
      if (handler.supportsReturnType(returnType)) {
        return handler;
      }
    }
    return null;
  }
  private boolean isAsyncReturnValue(@Nullable Object value, MethodParameter returnType) {
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
      if (handler instanceof AsyncHandlerMethodReturnValueHandler && ((AsyncHandlerMethodReturnValueHandler) handler).isAsyncReturnValue(value, returnType)) {
        return true;
      }
    }
    return false;
  }
}



我们可以看到,它内的逻辑其实非常的简单。重点在于我们需要关心下调用栈:


请求的入口处在这:doDispatcher里会找到一个HandlerAdapter会调用@handle方法来真正执行Spring MVC的Handler。扔给ServletInvocableHandlerMethod#invokeAndHandle去执行处理器,从而拿到方法返回值:returnValue。

最终交给HandlerMethodReturnValueHandlerComposite#handleReturnValue它去处理~~~上面看了源码处理过程,这就简单了,其实最终做事的是我们的具体的找到唯一的一个HandlerMethodReturnValueHandler~


Spring MVC默认配置返回值处理器们

不管开启@EnableWebMvc还是未开启,都是15个


备注:如果是Spring5一下的版本,若未开启@EnableWebMvc,处理的类是过时的AnnotationMethodHandlerAdapter,而它里面还并没有HandlerMethodReturnValueHandler这个接口,所以此处就不介绍了,知道就行。注意版本必须是Spring5以内的,因为Spring5以后那两个过时的类就直接都干掉了~~~


image.png

请注意,上面原理已经讲过,这里面处理器的先后顺序还是比较重要的~~~从下面源码处也能看出,Spring MVC大致上做了分类



  private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
    // Single-purpose return value types
    // 目的单纯的返回值处理器(这个一般都和视图解析器有关,当然还有异步~)
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
        this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
    handlers.add(new StreamingResponseBodyReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
        this.contentNegotiationManager, this.requestResponseBodyAdvice));
    handlers.add(new HttpHeadersReturnValueHandler());
    handlers.add(new CallableMethodReturnValueHandler());
    handlers.add(new DeferredResultMethodReturnValueHandler());
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
    // Annotation-based return value types
    // 基于注解的返回值处理器:@ModelAttribute和@ResponseBody
    handlers.add(new ModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
        this.contentNegotiationManager, this.requestResponseBodyAdvice));
    // Multi-purpose return value types
    // 多值返回处理器  这两个其实相对稍微复杂点,功能强大点
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());
    // Custom return value types
    // 用户自定义的处理器们~~~~顺序是非常靠后的哟~
    if (getCustomReturnValueHandlers() != null) {
      handlers.addAll(getCustomReturnValueHandlers());
    }
    // Catch-all:处理所有
    // Spring MVC相当于它定位成自己是能够处理所有的请求的~~~~
    // 特别是ModelAndViewResolverMethodReturnValueHandler,我们上面也有举例了
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
      handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
      handlers.add(new ModelAttributeMethodProcessor(true));
    }
    return handlers;
  }


备注:若遇上多个处理器都能处理器的情况下,是按照添加顺序执行的。比如Jackson和FastJson都能处理,那就根据添加顺序了,最终生效的肯定只有一个


总结


Spring MVC支持各种返回值类型,是因为默认给我们注册了足够锁的返回值处理器。它面向接口编程以及对责任链模式很好的使用,实现了非常高的扩展性和解耦性。


一个成熟的框架,体现在它对很多细节上的处理,这才行程了一个产品,而Spring Framework就是这么样一个很优秀的产品,值得参考、学习

相关文章
|
5月前
|
Web App开发 前端开发 JavaScript
前端新利器:CSS容器查询——让组件真正“自适应
前端新利器:CSS容器查询——让组件真正“自适应
386 83
|
9月前
|
前端开发 API 开发者
harmonyOS基础- 快速弄懂HarmonyOS ArkTs基础组件、布局容器(前端视角篇)
本文由黑臂麒麟(6年前端经验)撰写,介绍ArkTS开发中的常用基础组件与布局组件。基础组件包括Text、Image、Button等,支持样式设置如字体颜色、大小和加粗等,并可通过Resource资源引用统一管理样式。布局组件涵盖Column、Row、List、Grid和Tabs等,支持灵活的主轴与交叉轴对齐方式、分割线设置及滚动事件监听。同时,Tabs组件可实现自定义样式与页签切换功能。内容结合代码示例,适合初学者快速上手ArkTS开发。参考华为开发者联盟官网基础课程。
810 75
harmonyOS基础- 快速弄懂HarmonyOS ArkTs基础组件、布局容器(前端视角篇)
|
5月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
377 0
|
设计模式 前端开发 数据库
深入理解MVC设计模式:构建高效Web应用程序的基石
【7月更文挑战第4天】在软件工程领域,设计模式是解决常见问题的一系列经过验证的方法。其中,Model-View-Controller(MVC)设计模式自诞生以来,便成为了构建用户界面,特别是Web应用程序的黄金标准。MVC通过将应用程序逻辑分离为三个核心组件,提高了代码的可维护性、可扩展性和重用性。本文将深入探讨MVC设计模式的原理,并通过一个简单的代码示例展示其应用。
449 0
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
195 7
|
前端开发 测试技术 开发者
MVC模式在现代Web开发中有哪些优势和局限性?
MVC模式在现代Web开发中有哪些优势和局限性?
|
缓存 开发者 Docker
Dockerfile是Docker容器化过程中的核心组件,它允许开发者以一种可重复、可移植的方式自动化地构建Docker镜像
【8月更文挑战第19天】Dockerfile是构建Docker镜像的脚本文件,含一系列指令定义镜像构建步骤。每条大写指令后跟至少一个参数,按序执行,每执行一条指令即生成新的镜像层。常用指令包括:FROM指定基础镜像;RUN执行构建命令;EXPOSE开放端口;CMD指定容器启动行为等。优化策略涉及减少镜像层数、选择轻量基础镜像、利用缓存及清理冗余文件。示例:基于Python应用的Dockerfile包括设置工作目录、复制文件、安装依赖等步骤。掌握Dockerfile有助于高效自动化构建镜像,加速应用部署。
180 1
|
开发者 前端开发 Java
架构模式的诗与远方:如何在MVC的田野上,用Struts 2编织Web开发的新篇章
【8月更文挑战第31天】架构模式是软件开发的核心概念,MVC(Model-View-Controller)通过清晰的分层和职责分离,成为广泛采用的模式。随着业务需求的复杂化,Struts 2框架应运而生,继承MVC优点并引入更多功能。本文探讨从MVC到Struts 2的演进,强调架构模式的重要性。MVC将应用程序分为模型、视图和控制器三部分,提高模块化和可维护性。
148 0
|
Java 开发者 前端开发
Struts 2、Spring MVC、Play Framework 上演巅峰之战,Web 开发的未来何去何从?
【8月更文挑战第31天】在Web应用开发中,Struts 2框架因强大功能和灵活配置备受青睐,但开发者常遇配置错误、类型转换失败、标签属性设置不当及异常处理等问题。本文通过实例解析常见难题与解决方案,如配置文件中遗漏`result`元素致页面跳转失败、日期格式不匹配需自定义转换器、`&lt;s:checkbox&gt;`标签缺少`label`属性致显示不全及Action中未捕获异常影响用户体验等,助您有效应对挑战。
245 0
|
Java 前端开发 Apache
Apache Wicket与Spring MVC等Java Web框架大PK,究竟谁才是你的最佳拍档?点击揭秘!
【8月更文挑战第31天】在Java Web开发领域,众多框架各具特色。Apache Wicket以组件化开发和易用性脱颖而出,提高了代码的可维护性和可读性。相比之下,Spring MVC拥有强大的生态系统,但学习曲线较陡;JSF与Java EE紧密集成,但在性能和灵活性上略逊一筹;Struts2虽成熟,但在RESTful API支持上不足。选择框架时还需考虑社区支持和文档完善程度。希望本文能帮助开发者找到最适合自己的框架。
221 0