Web MVC(Model-View-Controller)是一种设计模式,用于将 Web 应用程序的用户界面逻辑与数据处理逻辑分离。它是一种架构模式,主要用于构建 Web 应用程序。FIT 提供了高度灵活和功能强大的 Web MVC 后端服务,非常适用于 Web 应用程序开发。如果你还没有开发过 Web 服务,请参考快速入门指南-构建一个 Web 服务,可以帮助你快速入门。
MVC的主要组成部分包括:
- Model(模型):模型代表应用程序的数据和业务逻辑。它是应用程序的“内容”,包括数据的结构和行为。
- View(视图):视图是用户界面的表示。它是用户看到的内容,通常是 HTML 页面,但也可以是其他格式,如 PDF 或 JSON。视图从模型中获取数据,并将其显示给用户。
- Controller(控制器):控制器是连接模型和视图的中间层。它接收用户的输入(通常是 HTTP 请求),处理业务逻辑,并将结果传递给视图。控制器从模型中获取数据,并将其传递给视图进行显示。
通过这种分离,MVC 模式使得开发者可以专注于单一的任务,而不必混合处理用户界面和数据处理逻辑。此外,这种模式还提高了代码的可维护性和可测试性。
MVC使用准备:在pom文件中添加依赖fit-http-classic ,使得应用程序具备 Web 服务的基本能力。
<dependency>
<groupId>org.fitframework.service</groupId>
<artifactId>fit-http-classic</artifactId>
<version>${fit.version}</version>
</dependency>在Web应用后端开发中,我们主要通过实现控制器(Controller)来负责处理用户请求、调度业务逻辑以及返回适当的响应,接下来将给出一个简单的 Controller 示例,用于后续介绍。
package modelengine.fit.demo1.controller;
import modelengine.fit.demo1.domain.User;
import modelengine.fit.http.annotation.GetMapping;
import modelengine.fit.http.annotation.RequestParam;
import modelengine.fitframework.annotation.Component;
/**
* 表示用户资源的控制器。
*/
@Component
public class UserController {
@GetMapping(path = "/user")
public User getUser(@RequestParam("name") String name, @RequestParam("age") String age) {
return new User(name, age);
}
}控制器作为一个组件,通过在类上标识 @Component 注解,FIT框架会在启动时自动扫描并注册为Bean交由容器管理。
示例中 HTTP 的调用是 Get 方法,所以在对应的调用方法上需要打上 @GetMapping 注解进行标识,并通过path属性确定 HTTP 的访问路径。我们也可以使用@RequestMapping注解,再加上method = HttpRequestMethod.GET属性,可以达到相同的效果。其余的指示请求方法的注解如下所示:
| 注解 | 解释 | Spring 的相应注解 |
|---|---|---|
| @RequestMapping | 表示 REST 接口的请求映射。该注解可以在类和方法上使用,注解在方法上的 @RequestMapping 路径会继承注解在类上的路径。 | @RequestMapping |
| @GetMapping | 表示 REST 接口的 HttpRequestMethod.GET 请求映射。 | @GetMapping |
| @DeleteMapping | 表示 REST 接口的 HttpRequestMethod.DELETE 请求映射。 | @DeleteMapping |
| @PatchMapping | 表示 REST 接口的 HttpRequestMethod.PATCH 请求映射。 | @PatchMapping |
| @PostMapping | 表示 REST 接口的 HttpRequestMethod.POST 请求映射。 | @PostMapping |
| @PutMapping | 表示 REST 接口的 HttpRequestMethod.PUT 请求映射。 | @PutMapping |
FIT 框架接受 Http 请求,获取请求的相关信息,并自动查找相应的方法处理器实现方法的调用,并返回正确的结果。这个过程中 FIT 将自动进行如动态路由、负载均衡、请求解析、请求过滤、方法查找、方法调用、异常处理、序列化与序列化等操作,而开发者只需关注如何编写后端代码,无需关注内部实现。
对于HTTP请求,FIT 框架提供了一系列注解用于提取HTTP请求中的信息或对处理器方法进行设定,方便开发者将HTTP请求的参数用于处理器方法中,如下所示:
| 注解 | 解释 |
|---|---|
| @PathVariable | 表示 REST 接口的请求映射中的路径参数。 |
| @ExceptionHandler | 表示 REST 请求的异常处理器 |
| @RequestBean | 表示 REST 接口的请求映射中的 Http 值聚合到方法参数上。 |
| @RequestBody | 表示 REST 接口的请求映射中的消息体参数。 |
| @RequestCookie | 表示 REST 接口的请求映射中的 Cookie 参数。 |
| @RequestForm | 表示 REST 接口的请求映射中的表单参数。 |
| @RequestHeader | 表示 REST 接口的请求映射中的消息头参数。 |
| @RequestParam | 表示 REST 接口的请求映射中的查询参数或表单参数。 |
| @RequestQuery | 表示 REST 接口的请求映射中的查询参数。 |
| @ResponseBody | 表示 REST 接口的响应映射中的消息体返回值。 |
| @ResponseStatus | 表示 REST 接口的响应映射中的状态码。 |
部分接口的使用示例如下:
//获取请求中的路径参数,返回响应状态码
@GetMapping(path = "/user/{name}")
@ResponseStatus(HttpResponseStatus.OK)
public String returnName(@PathVariable("name") String name){
return name;
}//获取Post请求体及表单参数
@PostMapping(path = "/example")
public String handleExample(@RequestBody String body, @RequestParam(value = "param") String param) {
return StringUtils.format("Received body: {0}, param: {1}", body, param);
}当参数所需信息来自HTTP请求的不同部分时,可以使用@RequestBean注解进行传递,使用示例如下:
//定义类,对属性添加注解指示数据来自请求的哪一部分
public class Person {
@RequestHeader("name")
@Property(description = "表示姓名")
private String name;
@RequestCookie("phoneNumber")
@Property(description = "表示电话列表")
private String phoneNumber;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPhoneNumber() {
return this.phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
//定义处理器方法,获取HTTP请求中各项参数注入对象中
@PostMapping("/person")
public String personExample(@RequestBean @Property(description = "表示人员信息") Person person) {
return "Received: " + person;
}FIT 提供@ExceptionHandler注解来指定处理的异常类型,示例如下:
@ExceptionHandler(Exception.class)
public Map<String, Object> handleException(Exception cause) {
return MapBuilder.<String, Object>get().put("error", cause.getMessage()).build();
}开发者可以根据需要添加更多的异常处理方法,以处理不同类型的异常,例如为404错误、401错误等添加特定的处理方法。
FIT 提供了HttpServerFilter接口,开发者可通过实现该接口,自定义过滤行为,实现个性化的处理。例如在一个 Web 服务器中,可能需要对请求进行一些处理,如身份验证、日志记录、请求限流等,这时就可以使用HttpServerFilter。
@Component
public class SampleFilter implements HttpServerFilter {
/** 表示过滤器的名字。*/
@Override
public String name() {
return "sampleFilter";
}
/** 表示过滤器的优先级。*/
@Override
public int priority() {
return HIGHEST;
}
/** 表示过滤器的过滤路径样式列表。*/
@Override
public List<String> matchPatterns() {
return Collections.singletonList("/url/**");
}
/** 表示过滤器的不匹配的过滤路径样式列表。*/
@Override
public List<String> mismatchPatterns() {
return Collections.singletonList("/url/h*");
}
/** 表示过滤器的自定义过滤逻辑。*/
@Override
public void doFilter(HttpClassicServerRequest request, HttpClassicServerResponse response,
HttpServerFilterChain chain) throws DoHttpServerFilterException {
// 自定义过滤逻辑
chain.doFilter(request, response);
// 自定义过滤逻辑
}
/** 表示过滤器的范围。*/
@Override
public Scope scope() {
return Scope.GLOBAL;
}
}- priority 表示该过滤器的优先级,数字越小,越小执行,可使用 FIT 提供的
@Order注解,也可自定义优先级; - matchPatterns 和 mismatchPatterns 进行组合实现对 Http 请求的 url 的筛选,其具体配置规则如下:
- 当路径样式为 '/a' 时,只有完全匹配的请求 '/a' 才能过滤;
- 当路径样式为 '/a?' 时,可以模糊匹配 1 个字符,如 '/aa' 或 '/ab' 这样的请求;
- 当路径样式为 '/a*' 时,可以在一段路径内模糊匹配任意个字符,如 '/a' 或 '/abc' 这样的请求;
- 当路径样式为 '/a**' 时,可以在任意段路径内模糊匹配任意个字符,如 '/a' 或 '/a/b/c' 这样的请求;
- doFilter 由开发者自定义实现过滤逻辑;
- scope 表示过滤器的范围,根据
FIT的插件化开发思想,提供PLUGIN和GLOBAL两种范围,支持插件的特有过滤器及公用过滤器。
开发者对于 Http 请求可能有多个不同的处理需求, FIT 支持开发者自定义实现多个过滤器,并自动管理为一条过滤链,根据每个过滤器的优先级和匹配原则等进行顺序过滤,最终处理请求。 开发者无需关注过滤链的具体实现,只需关注过滤器的具体设置、合理安排优先级顺序即可。
public interface HttpServerFilterChain {
/**
* 继续执行下一个过滤器。
*
* @param request 表示当前 Http 请求的 {@link HttpClassicServerRequest}。
* @param response 表示当前 Http 响应的 {@link HttpClassicServerResponse}。
* @throws HttpServerException 当执行当前请求发生异常时。
*/
void doFilter(HttpClassicServerRequest request, HttpClassicServerResponse response) throws HttpServerException;
}根据请求的类型,请求映射可以分为 GetMapping,PostMapping,PutMapping,DeleteMapping 等等。在这些注解中,我们通过设置 path 来设置请求映射的路径。
注意:设置 path 时,path 字符串中请勿包含特殊字符,其中特殊字符包括
"#%@()+,/:;<=>?@\\|等。使用需要转移的特殊字符会导致请求无法路由到期望 Controller。 例子:@PostMapping(path = "/this/is/an/example")反例:
@PostMapping(path = "/this/is/an/error example")
目前,请求映射的 path 并不支持 ../,./,// 等,这意味着请求映射中设置的 path 与实际访问时的 uri 应该保持一致。
@PostMapping(path = "/first/second")
void twoLevelMethod() {}
@PostMapping(path = "/first/second/third")
void threeLevelMethod() {}在上面的例子中,我们无法通过 /first/second/third/../ 来调用 twoLevelMethod。
使用 @RequestParam 注解可以在入参中注入请求中添加的 query 参数,
最常用用法是设置一个 key 来注入对应的 value。比如 @RequestParam("helloKey") String hello 会将 query 参数中键为 helloKey 所对应的值注入到入参 hello 中。
url 中使用 ? 表示接下来都是 query 参数,且多个 query 参数间使用 & 进行区分。这里 ; 被看作是普通的字符。比如 k1=v1;k2=v2 表示一个键为 k1,且值为 v1;k2=v2 的 query 参数。
目前限制了 query 参数的数量,当 query 参数的数量超过 1000 时,会抛出异常,并返回 500 的 HTTP 状态码。
FIT 提供了多个实体类用于处理消息体数据,其中几个实体类可用于处理 MVC 中文件上传、下载的功能,定义如下:
| 类名 | 定义 |
|---|---|
| PartitionedEntity | 表示分块的消息体数据 |
| NamedEntity | 表示带名字的消息体数据 |
| FileEntity | 表示文件类型的消息体数据 |
| TextEntity | 表示文本格式的消息体数据 |
用户可以通过定义一个Post方法来接收文件的上传,将消息体数据接收为一个PartitionedEntity实体,通过该实体获取带名字的消息体数据后,使用NamedEntity::asFile()将之转换为FileEntity,再进行文件的处理。
@Component
public class UploadFileController {
@PostMapping(path = "/save", description = "上传文件")
public void saveUploadFile(PartitionedEntity receivedFiles) {
notNull(receivedFiles, "The file to be uploaded cannot be null.");
List<NamedEntity> entityList =
receivedFiles.entities().stream().filter(NamedEntity::isFile).collect(Collectors.toList());
for(NamedEntity namedEntity : entityList){
FileEntity file = namedEntity.asFile();
//文件处理逻辑
}
}
}Swagger 是一个开源工具集,用于设计、构建、记录和使用 RESTful API。它提供了一种简单的方法来生成 API 文档,并通过交互式界面让开发者可以轻松地测试 API,Swagger的主要特点如下:
- API 文档生成:
- 使用 Swagger 注解,自动生成清晰的 API 文档,支持 OpenAPI 规范。
- 交互式界面:
- 提供 Swagger UI,允许开发者在浏览器中查看 API 文档并直接进行 API 调用。
- 规范化:
- 使用 OpenAPI 规范(之前称为 Swagger 规范),标准化 API 的描述,使得不同工具和语言之间能够互操作。
FIT 框架内置整合了Swagger以方便开发者进行接口的查询及使用,本章将介绍如何启动Swagger以及如何将接口文档接入Swagger中。
首先对开发的所有插件进行构建编译:
mvn clean install
打开 bash 窗口,并进入到自定义的用户插件目录,输入命令 fit start,其中 start 表示应用的启动;
Running command: java -D"plugin.fit.dynamic.plugin.directory=/d/WorkSpace/custom" -jar fit-discrete-launcher-3.7.0-SNAPSHOT.jar
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [main] [modelengine.fitframework.runtime.discrete.DiscreteFitRuntime] Prepare to start FIT application...
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [main] [modelengine.fitframework.runtime.discrete.DiscreteFitRuntime] FIT application started.
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [registry-client-thread-0] [modelengine.fit.service.RegistryClient] Register fitables successfully. All fitable services are ready.
[yyyy-MM-dd hh:mm:ss.SSS] [INFO ] [netty-http-server-thread-0] [modelengine.fit.http.server.netty.NettyHttpClassicServer] Start netty http server successfully. [port=8080]
当出现以上段落时,表明应用启动成功。我们也可以通过 debug 参数对应用进行调试,具体可以通过命令 fit help 来获取各个命令参数的详细信息和作用。
FIT 框架会自动扫描插件目录,访问http://localhost:8080/openapi.html 可进入至Swagger-UI页面,此处localhost:8080请按照服务器实际地址及端口进行访问。
以第七章数据访问MyBatis用例为例,具体数据访问操作请参考第七章文档,以下为用例中的HTTP方法的控制器:
/**
* 表示用户资源的控制器。
*/
@Component
@RequestMapping("/data")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 通过用户 id 获取信息
*/
@GetMapping(path = "/getUserById")
public UserData getTask(@RequestParam("id") Integer userId) {
return this.userService.getUserById(userId);
}
/**
* 添加用户
*/
@GetMapping(path = "/user")
public void getTask(@RequestParam("name") String name, @RequestParam("email") String email) {
UserData user = new UserData();
user.setName(name);
user.setEmail(email);
this.userService.insertUser(user);
}
}将插件打包后载入用户插件目录,访问http://localhost:8080/openapi.html
在Swagger-UI页面中可以查看各接口的请求方法、路径、注释,开发者可以在该页面中进行接口的测试执行,以获取接口的使用方法信息以及响应格式。
如果需要去除某一个 API 的文档显示,可以在对应方法上添加
@DocumentIgnored注解; 通过访问http://localhost:8080/v3/openapi可以获取所有接口信息的 Json 格式。
