【干货】Spring MVC与JAX-RS比较与分析

}

访问路径“/accounts/{username}”(其中的username是路径参数,可以是某个账户的用户名)的请求将由getAccount()方法处理。

根资源由JAX-RS运行时(在本示例中是Spring)实例化,子资源则由应用本身实例化。比如说,对于“/accounts /{username}/portfolios/{portfolioName}”这样的请求,AccountResource(由路径的第一部分“ /accounts”标识)会创建一个子资源实例,请求会被代理给该实例:

@Path(“/accounts/”)

@Component

@Scope(“prototype”)

public class AccountResource {

@Path(“{username}/portfolios/”)

public PortfolioResource getPortfolioResource(@PathParam(“username”) String username) {

return new PortfolioResource(accountRepository, username, uriInfo);

}

}

PortfolioResource本身的声明并没有使用注解,因此其所有的依赖都是由父资源传递过来的:

public class PortfolioResource {

private AccountRepository accountRepository;

private String username;

private UriInfo uriInfo;

public PortfolioResource(AccountRepository accountRepository, String username, UriInfo uriInfo) {

this.accountRepository = accountRepository;

this.username = username;

this.uriInfo = uriInfo;

}

}

JAX-RS中的根与子资源创建了一个处理链,它会调用多个资源:

请记住,资源类是Web Services层组件,应当关注于Web Services相关的处理,比如输入转换、准备响应、设定响应代码等等。此外,将Web Services逻辑与业务逻辑分隔开来的实践需要将业务逻辑包装到单独的方法中以作为事务边界。

创建Spring MVC @Controller类

对于Spring MVC来说,我们需要创建DispatcherServlet,同时将contextConfigLocation参数指定为Spring MVC配置:

Spring MVC Dispatcher Servlet

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

/WEB-INF/spring/*.xml

要想在Spring MVC(@MVC)中使用基于注解的编程模型还需要少量的配置。下面的component-scan元素会告诉Spring去哪里寻找@Controller注解类。

<context:component-scan base-package=“org.springframework.samples.stocks” />

接下来,我们声明了AccountController,如下代码所示:

@Controller

@RequestMapping(“/accounts”)

public class AccountController {

@Autowired

private AccountRepository accountRepository;

}

@RequestMapping注解会将该控制器映射到所有以“/accounts”开头的请求上。AccountController中的方法如getAccount()只需声明针对“/accounts”的相对地址即可。

@RequestMapping(value = “/{username}”, method = GET)

public Account getAccount(@PathVariable String username) {

}

Spring MVC则没有根资源与子资源的概念,这样每个控制器都是由Spring而非应用来管理的:

@Controller

@RequestMapping(“/accounts/{username}/portfolios”)

public class PortfolioController {

@Autowired

private AccountRepository accountRepository;

}

对“/accounts/{username}/portfolios”的请求会被直接代理给 PortfolioController,AccountController则完全不会参与其中。需要注意的是,该请求也可以直接由 AccountController处理,这样就不需要PortfolioController了。

Web层组件范围

在JAX-RS中,AccountResource是通过前请求(per-request)语义声明的,这也是JAX-RS默认的推荐设置。这么做 可以将特定于请求的数据注入并存储到资源类本身当中,这适用于由JAX-RS所管理的根级别资源。子资源由应用实例化,并不会直接从这种方法中获益。

在Spring MVC中,控制器永远都是单例的,他们将特定于请求的数据作为方法参数。JAX-RS也可以这么做,以单例的方式创建资源。

将请求映射到方法上

接下来,我们看看Spring MVC和JAX-RS如何将请求映射到方法上。@Path和@RequestMapping都可以从URL中抽取出路径变量:

@Path(“/accounts/{username}”)

@RequestMapping(“/accounts/{username}”)

这两个框架也都可以使用正则表达式抽取路径变量:

@Path(“/accounts/{username:.*}”)

@RequestMapping(“/accounts/{username:.*}”

Spring MVC的@RequestMapping可以根据查询参数的有无来匹配请求:

@RequestMapping(parameters=“foo”)

@RequestMapping(parameters=“!foo”)

或是根据查询参数值进行匹配:

@RequestMapping(parameters=“foo=123”)

@RequestMapping还可以根据头信息的有无来匹配请求:

@RequestMapping(headers=“Foo-Header”)

@RequestMapping(headers=“!Foo-Header”)

或是根据头信息的值进行匹配:

@RequestMapping(headers=“content-type=text/*”)

处理请求数据

HTTP请求中包含着应用需要提取和处理的数据,如HTTP头、cookie、查询字符串参数、表单参数以及请求体(XML、JSON等)中所包含 的大量数据。在RESTful应用中,URL本身也可以带有重要的信息,如通过路径参数指定需要访问哪个资源、通过文件扩展名(.html, .pdf)指定需要何种内容类型等。HttpServletRequest提供了处理这一切的所有底层访问机制,但直接使用 HttpServletRequest实在是太乏味了。

请求参数、Cookies和HTTP头

Spring MVC和JAX-RS拥有能够抽取这种HTTP请求值的注解:

@GET @Path

public void foo(@QueryParam(“q”) String q, @FormParam(“f”) String f, @CookieParam(“c”) String c,

@HeaderParam(“h”) String h, @MatrixParam(“m”) m) {

// JAX-RS

}

@RequestMapping(method=GET)

public void foo(@RequestParam(“q”) String q, @CookieValue(“c”) String c, @RequestHeader(“h”) String h) {

// Spring MVC

}

上面的注解非常像,区别在于JAX-RS支持矩阵参数(matrix parameters)的抽取,拥有单独的注解来处理查询字符串和表单参数。矩阵参数并不常见,他们类似于查询字符串参数,但却使用了特殊的路径片段(比 如GET /images;name=foo;type=gif)。稍后将介绍表单参数。

假如使用了前请求范围声明资源,那么JAX-RS可以在属性和setters方法上使用上述注解。

Spring MVC有个特性能让我们少敲几个字符,如果注解名与Java参数名相同,那么就可以省略掉上面的注解名了。比如说,名为“q”的请求参数要求方法参数也得为“q”:

public void foo(@RequestParam String q, @CookieValue c, @RequestHeader h) {

}

这对于那些在参数中使用了注解而导致方法签名变长的情况来说实在是太方便了。请记住,这个特性要求代码使用调试符号进行编译。

类型转换与HTTP请求值的格式化

HTTP请求值(头、cookies和参数)是不变的字符串并且需要解析。

JAX-RS通过寻找valueOf()方法或是在客户化的目标类型中接收字符串的构造方法来解析请求数据。JAX-RS支持如下类型的注解方法参数,包括路径变量、请求参数、HTTP头值和cookies:

原生类型。

拥有接收单个字符串参数的构造方法的类型。

拥有一个接收单个字符串参数的名为valueOf的静态方法的类型。

List、Set或是SortedSet,其中的T满足上面2个或3个要求。

Spring 3支持上面所有要求。除此之外,Spring 3提供了一种全新的类型转换与格式化机制 ,并且可以使用注解实现。

表单数据

如前所述,JAX-RS处理查询字符串参数和表单参数的方式是不同的。虽然Spring MVC只有一个@RequestParam,但它还提供了一种Spring MVC用户很熟悉的数据绑定机制来处理表单输入。

比如说,如果一个表单提交了3个数据,那么一种可能的处理方式就是声明一个带有3个参数的方法:

@RequestMapping(method=POST)

public void foo(@RequestParam String name, @RequestParam creditCardNumber, @RequestParam expirationDate) {

Credit card = new CreditCard();

card.setName(name);

card.setCreditCardNumber(creditCardNumber);

card.setExpirationDate(expirationDate);

}

然而,随着表单数据量的增加,这种处理方式就会变得不切实际。借助于数据绑定,Spring MVC可以创建、组装并传递包含有嵌套数据(账单地址、邮件地址等)、任意结构的表单对象。

@RequestMapping(method=POST)

public void foo(CreditCard creditCard) {

// POST /creditcard/1

// name=Bond

// creditCardNumber=1234123412341234

// expiration=12-12-2012

}

要想与Web浏览器协同工作,表单处理是个重要环节。另一方面,Web Services客户端一般会在请求体中提交XML或JSON格式的数据。

处理请求体中的数据

无论是Spring MVC还是JAX-RS都能够自动处理请求体中的数据:

@POST

public Response createAccount(Account account) {

// JAX_RS

}

@RequestMapping(method=POST)

public void createAccount(@RequestBody Account account) {

// Spring MVC

}

JAX-RS中的请求体数据

在JAX-RS中,类型MessageBodyReader的实体供应者负责转换请求体数据。JAX-RS的实现需要拥有一个JAXB MessageBodyReader,这可以使用具有注解@Provider的客户化MessageBodyReader实现。

Spring MVC中的请求体数据

在Spring MVC中,如果想通过请求体数据初始化方法参数,那可以将@RequestBody注解加到该方法参数前,这与之前介绍的表单参数初始化正好相反。

在Spring MVC中,HttpMessageConverter类负责转换请求体数据,Spring MVC提供了一个开箱即用的Spring OXM HttpMessageConverter。它支持JAXB、Castor、JiBX、XMLBeans和XStream,此外还有一个用于处理JSON 的Jackson HttpMessageConverter。

HttpMessageConverter会注册到AnnotationMethodHandlerAdapter上,后者会将到来的请求映射到Spring MVC @Controllers上。下面是其配置:

<oxm:jaxb2-marshaller id=“jaxb2Marshaller”/>

下图阐述了该配置:

Spring 3新增的mvc客户化命名空间 将上述配置自动化了,只需增加如下配置片段即可:

<mvc:annotation-driven />

如果JAXB位于类路径上,它会注册一个用于读写XML的转换器;如果Jackson 位于类路径上,它会注册一个用于读写JSON的转换器。

准备响应

典型的响应需要准备响应代码、设定HTTP响应头、将数据放到响应体当中,还需要处理异常。

使用JAX-RS设定响应体数据

在JAX-RS中,要想将数据加到响应体中,只需要从资源方法中返回对象即可:

@GET

@Path(“{username}”)

public Account getAccount(@PathParam(“username”) String username) {

return accountRepository.findAccountByUsername(username);

}

JAX-RS会寻找类型MessageBodyWriter的实体供应者,它能将对象转换为所需的内容类型。JAX-RS实现需要具备一个JAXB MessageBodyWriter,这可以使用具有注解@Provider的客户化MessageBodyWriter实现。

使用Spring MVC设定响应体数据

在Spring MVC中,响应是通过一个视图解析过程来实现的,这样就可以从一系列视图技术中选择了。但在与Web Services客户端交互时,更加合理的方式则是舍弃视图解析过程,转而使用方法所返回的对象:

@RequestMapping(value=“/{username}”, method=GET)

public @ResponseBody Account getAccount(@PathVariable String username) {

return accountRepository.findAccountByUsername(username);

}

如果对控制器方法或其返回类型应用注解@ResponseBody,那么就会使用HttpMessageConverter处理返回值,然后用该返回值设定响应体。用于请求体参数的HttpMessageConverter集合也用于响应体,因此无需再做任何配置。

状态代码与响应头

JAX-RS使用一个链式API来构建响应:

@PUT @Path(“{username}”)

public Response updateAccount(Account account) {

// …

return Response.noContent().build(); // 204 (No Content)

}

这可以与UriBuilder联合使用来为Location响应头创建实体链接:

@POST

public Response createAccount(Account account) {

// …

URI accountLocation = uriInfo.getAbsolutePathBuilder().path(account.getUsername()).build();

return Response.created(accountLocation).build();

}

上面代码中所用的uriInfo要么被注入到根资源(使用了@Context)中,要么是从父资源传递给子资源。它可以附加到当前请求的路径之后。

Spring MVC提供了一个注解来设定响应代码:

@RequestMapping(method=PUT)

@ResponseStatus(HttpStatus.NO_CONTENT)

public void updateAccount(@RequestBody Account account) {

// …

}

可以直接使用HttpServletResponse对象设定Location头:

@RequestMapping(method=POST)

@ResponseStatus(CREATED)

public void createAccount(@RequestBody Account account, HttpServletRequest request,

HttpServletResponse response) {

// …

String requestUrl = request.getRequestURL().toString();

URI uri = new UriTemplate(“{requestUrl}/{username}”).expand(requestUrl, account.getUsername());

response.setHeader(“Location”, uri.toASCIIString());

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

针对以上面试题,小编已经把面试题+答案整理好了

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

最新大厂必问微服务面试题汇总:SpringCloud、Boot、Dubbo

面试专题

image

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

image

image

image
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
以上面试题,小编已经把面试题+答案整理好了**

[外链图片转存中…(img-6UF5rG5h-1713412236735)]

[外链图片转存中…(img-YXkGuqEa-1713412236735)]

[外链图片转存中…(img-5gw0Y8z5-1713412236735)]

面试专题

[外链图片转存中…(img-TKAl3JLO-1713412236736)]

除了以上面试题+答案,小编同时还整理了微服务相关的实战文档也可以分享给大家学习

[外链图片转存中…(img-YU71L8D4-1713412236736)]

[外链图片转存中…(img-T7mpFi3e-1713412236736)]

[外链图片转存中…(img-JgjkL085-1713412236736)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值