2024年物联网嵌入式最新从MVC到前后端分离(Rest框架搭建)_mvc rest框架,2024年最新【2024物联网嵌入式开发最新学习路线

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. 每次请求必须经过“控制器->模型->视图”这个流程,用户才能看到最终的展现的界面,这个过程似乎有些复杂。
  2. 实际上视图是依赖于模型的,换句话说,如果没有模型,视图也无法呈现出最终的效果。
  3. 渲染视图的过程是在服务端来完成的,最终呈现给浏览器的是带有模型的视图页面,性能无法得到很好的优化。

为了使数据展现过程更加直接,并且提供更好的用户体验,我们有必要对 MVC 模式进行改进。不妨这样来尝试,首先从浏览器发送 AJAX 请求,然后服务端接受该请求并返回 JSON 数据返回给浏览器,最后在浏览器中进行界面渲染。

改进后的 MVC 模式如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

也就是说,我们输入的是 AJAX 请求,输出的是 JSON 数据,市面上有这样的技术来实现这个功能吗?答案是 REST。REST 全称是 Representational State Transfer(表述性状态转移),它是 Roy Fielding 博士在 2000 年写的一篇关于软件架构风格的论文,此文一出,威震四方!国内外许多知名互联网公司纷纷开始采用这种轻量级的 Web 服务,大家习惯将其称为 RESTful Web Services,或简称 REST 服务。

如果将浏览器这一端视为前端,而服务器那一端视为后端的话,可以将以上改进后的 MVC 模式简化为以下前后端分离模式:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可见,有了 REST 服务,前端关注界面展现,后端关注业务逻辑,分工明确,职责清晰。那么,如何使用 REST 服务将应用程序进行前后端分离呢?我们接下来继续探讨,首先我们需要认识 REST。

3、认识 REST

REST 本质上是使用 URL 来访问资源的一种方式。众所周知,URL 就是我们平常使用的请求地址了,其中包括两部分:请求方式请求路径,比较常见的请求方式是 GET 与 POST,但在 REST 中又提出了几种其它类型的请求方式,汇总起来有六种:GET、POST、PUT、DELETE、HEAD、OPTIONS。尤其是前四种,正好与CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、POST(增)、PUT(改)、DELETE(删),这正是 REST 与 CRUD 的异曲同工之妙!需要强调的是,REST 是“面向资源”的,这里提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进行数据建模。

REST 是一个“无状态”的架构模式,因为在任何时候都可以由客户端发出请求到服务端,最终返回自己想要的数据,当前请求不会受到上次请求的影响。也就是说,服务端将内部资源发布 REST 服务,客户端通过 URL 来访问这些资源,这不就是 SOA 所提倡的“面向服务”的思想吗?所以,REST 也被人们看做是一种“轻量级”的 SOA 实现技术,因此在企业级应用与互联网应用中都得到了广泛应用。

下面我们举几个例子对 REST 请求进行简单描述:

REST 请求描述
GET:/advertisers获取所有的广告主
GET:/advertiser/1获取 ID 为 1 的广告主
PUT:/advertiser/1更新 ID 为 1 的广告主
DELETE:/advertiser/1删除 ID 为 1 的广告主
POST:/advertiser创建广告主

可见,请求路径相同,但请求方式不同,所代表的业务操作也不同,例如,/advertiser/1 这个请求,带有 GET、PUT、DELETE 三种不同的请求方式,对应三种不同的业务操作。虽然 REST 看起来还是很简单的,实际上我们往往需要提供一个 REST 框架,让其实现前后端分离架构,让开发人员将精力集中在业务上,而并非那些具体的技术细节。下面我们将使用 Java 技术来实现这个REST框架,整体框架会基于 Spring 进行开发。

4、实现 REST 框架
4.1、统一响应结构

使用 REST 框架实现前后端分离架构,我们需要首先确定返回的 JSON 响应结构是统一的,也就是说,每个 REST 请求将返回相同结构的 JSON 响应结构。不妨定义一个相对通用的 JSON 响应结构,其中包含两部分:元数据返回值,其中,元数据表示操作是否成功与返回值消息等,返回值对应服务端方法所返回的数据。该 JSON 响应结构如下:

{
    "meta": {
        "success": true,
        "message": "ok"
    },
    "data": ...
}

为了在框架中映射以上 JSON 响应结构,我们需要编写一个 Response 类与其对应:

public class Response {

    private static final String OK = "ok";
    private static final String ERROR = "error";

    private Meta meta;
    private Object data;

    public Response success() {
        this.meta = new Meta(true, OK);
        return this;
    }

    public Response success(Object data) {
        this.meta = new Meta(true, OK);
        this.data = data;
        return this;
    }

    public Response failure() {
        this.meta = new Meta(false, ERROR);
        return this;
    }

    public Response failure(String message) {
        this.meta = new Meta(false, message);
        return this;
    }

    public Meta getMeta() {
        return meta;
    }

    public Object getData() {
        return data;
    }

    public class Meta {

        private boolean success;
        private String message;

        public Meta(boolean success) {
            this.success = success;
        }

        public Meta(boolean success, String message) {
            this.success = success;
            this.message = message;
        }

        public boolean isSuccess() {
            return success;
        }

        public String getMessage() {
            return message;
        }
    }
}

以上 Response 类包括两类通用返回值消息:ok 与 error,还包括两个常用的操作方法:success( ) 与 failure( ),通过一个内部类来展现元数据结构,我们在下文中多次会使用该Response类。实现该 REST 框架需要考虑许多问题,首当其冲的就是对象序列化问题。

4.2、实现对象序列化

想要解释什么是 对象序列化?不妨通过一些例子进行说明。比如,在服务端从数据库中获取了数据,此时该数据是一个普通的 Java 对象,然后需要将这个 Java 对象转换为 JSON 字符串,并将其返回到浏览器中进行渲染,这个转换过程称为序列化;再比如,通过浏览器发送了一个普通的 HTTP 请求,该请求携带了一个 JSON 格式的参数,在服务端需要将该 JSON 参数转换为普通的 Java 对象,这个转换过程称为反序列化。不管是序列化还是反序列化,我们一般都称为序列化。

实际上,Spring MVC 已经为我们提供了这类序列化特性,只需在 Controller 的方法参数中使用 @RequestBody 注解定义需要反序列化的参数即可,如以下代码片段:

@Controller
public class AdvertiserController {

    @RequestMapping(value = "/advertiser", method = RequestMethod.POST)
    public Response createAdvertiser(@RequestBody AdvertiserParam advertiserParam) {
        ...
    }
}

若需要对 Controller 的方法返回值进行序列化,则需要在该返回值上使用 @ResponseBody 注解来定义,如以下代码片段:

@Controller
public class AdvertiserController {

    @RequestMapping(value = "/advertiser/{id}", method = RequestMethod.GET)
    public @ResponseBody Response getAdvertiser(@PathVariable("id") String advertiserId) {
        ...
    }
}

当然,@ResponseBody 注解也可以定义在类上,这样所有的方法都继承了该特性。由于经常会使用到 @ResponseBody 注解,所以 Spring 提供了一个名为@RestController 的注解来取代以上的 @Controller 注解,这样我们就可以省略返回值前面的 @ResponseBody 注解了,但参数前面的 @RequestBody 注解是无法省略的。实际上,看看 Spring 中对应 @RestController 注解的源码便可知晓:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

    String value() default "";
}

可见,@RestController 注解已经被 @Controller 与 @ResponseBody 注解定义过了,Spring 框架会识别这类注解。需要注意的是,该特性在 Spring 4.0 中才引入。因此,我们可将以上代码进行如下改写:

@RestController
public class AdvertiserController {

    @RequestMapping(value = "/advertiser", method = RequestMethod.POST)
    public Response createAdvertiser(@RequestBody AdvertiserParam advertiserParam) {
        ...
    }

    @RequestMapping(value = "/advertiser/{id}", method = RequestMethod.GET)
    public Response getAdvertiser(@PathVariable("id") String advertiserId) {
        ...
    }
}

除了使用注解来定义序列化行为以外,我们还需要使用 Jackson 来提供 JSON 的序列化操作,在 Spring 配置文件中只需添加以下配置即可:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
    </mvc:message-converters>
</mvc:annotation-driven>

若需要对 Jackson 的序列化行为进行定制,比如,排除值为空属性、进行缩进输出、将驼峰转为下划线、进行日期格式化等,这又如何实现呢?

首先,我们需要扩展 Jackson 提供的 ObjectMapper 类,代码如下:

public class CustomObjectMapper extends ObjectMapper {

    private boolean camelCaseToLowerCaseWithUnderscores = false;
    private String dateFormatPattern;

    public void setCamelCaseToLowerCaseWithUnderscores(boolean camelCaseToLowerCaseWithUnderscores) {
        this.camelCaseToLowerCaseWithUnderscores = camelCaseToLowerCaseWithUnderscores;
    }

    public void setDateFormatPattern(String dateFormatPattern) {
        this.dateFormatPattern = dateFormatPattern;
    }

    public void init() {
        // 排除值为空属性
        setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 进行缩进输出
        configure(SerializationFeature.INDENT_OUTPUT, true);
        // 将驼峰转为下划线
        if (camelCaseToLowerCaseWithUnderscores) {
            setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
        }
        // 进行日期格式化
        if (StringUtil.isNotEmpty(dateFormatPattern)) {
            DateFormat dateFormat = new SimpleDateFormat(dateFormatPattern);
            setDateFormat(dateFormat);
        }
    }
}

然后,将 CustomObjectMapper 注入到 MappingJackson2HttpMessageConverter 中,Spring 配置如下:

<bean id="objectMapper" class="com.xxx.api.json.CustomObjectMapper" init-method="init">
    <property name="camelCaseToLowerCaseWithUnderscores" value="true"/>
    <property name="dateFormatPattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

通过以上过程,我们已经完成了一个基于 Spring MVC 的 REST 框架,只不过该框架还非常单薄,还缺乏很多关键性特性,尤其是异常处理。

4.3、处理异常行为

在 Spring MVC 中,我们可以使用 AOP 技术,编写一个全局的异常处理切面类,用它来统一处理所有的异常行为,在 Spring 3.2 中才开始提供。使用法很简单,只需定义一个类,并通过@ControllerAdvice 注解将其标注即可,同时需要使用 @ResponseBody 注解表示返回值可序列化为 JSON 字符串。代码如下:

@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Response handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        logger.error("参数解析失败", e);
        return new Response().failure("could_not_read_json");
    }

    /**
     * 405 - Method Not Allowed
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        logger.error("不支持当前请求方法", e);
        return new Response().failure("request_method_not_supported");
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Response handleHttpMediaTypeNotSupportedException(Exception e) {
        logger.error("不支持当前媒体类型", e);
        return new Response().failure("content_type_not_supported");
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public Response handleException(Exception e) {
        logger.error("服务运行异常", e);
        return new Response().failure(e.getMessage());
    }
}

可见,在 ExceptionAdvice 类中包含一系列的异常处理方法,每个方法都通过 @ResponseStatus 注解定义了响应状态码,此外还通过@ExceptionHandler 注解指定了具体需要拦截的异常类。以上过程只是包含了一部分的异常情况,若需处理其它异常,可添加方法具体的方法。需要注意的是,在运行时从上往下依次调用每个异常处理方法,匹配当前异常类型是否与 @ExceptionHandler 注解所定义的异常相匹配,若匹配,则执行该方法,同时忽略后续所有的异常处理方法,最终会返回经 JSON 序列化后的 Response 对象。

4.4、支持参数验证

我们回到上文所提到的示例,这里处理一个普通的 POST 请求,代码如下:

@RestController
public class AdvertiserController {

    @RequestMapping(value = "/advertiser", method = RequestMethod.POST)
    public Response createAdvertiser(@RequestBody AdvertiserParam advertiserParam) {
        ...
    }
}

其中,AdvertiserParam 参数包含若干属性,通过以下类结构可见,它是一个传统的 POJO:

public class AdvertiserParam {

    private String advertiserName;

    private String description;

    // 省略 getter/setter 方法
}

如果业务上需要确保 AdvertiserParam 对象的 advertiserName 属性必填,如何实现呢?

若将这类参数验证的代码写死在 Controller 中,势必会与正常的业务逻辑搅在一起,导致责任不够单一,违背于“单一责任原则”。建议将其参数验证行为从 Controller 中剥离出来,放到另外的类中,这里仅提供一个@Valid 注解来定义 AdvertiserParam 参数,并在 AdvertiserParam 类中通过 @NotEmpty 注解来定义 advertiserName 属性,就像下面这样:

@RestController
public class AdvertiserController {

    @RequestMapping(value = "/advertiser", method = RequestMethod.POST)
    public Response createAdvertiser(@RequestBody @Valid AdvertiserParam advertiserParam) {
        ...
    }
}

public class AdvertiserParam {

    @NotEmpty
    private String advertiserName;

    private String description;

    // 省略 getter/setter 方法
}

这里的 @Valid 注解实际上是 Validation Bean 规范提供的注解,该规范已由 Hibernate Validator 框架实现,因此需要添加以下 Maven 依赖到 pom.xml 文件中:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>${hibernate-validator.version}</version>
</dependency>

需要注意的是,Hibernate Validator 与 Hibernate 没有任何依赖关系,唯一有联系的只是都属于 JBoss 公司的开源项目而已。

要实现 @NotEmpty 注解的功能,我们需要做以下几件事情。

首先,定义一个 @NotEmpty 注解类,代码如下:

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotEmptyValidator.class)
public @interface NotEmpty {

    String message() default "not_empty";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

以上注解类必须包含 message、groups、payload 三个属性,因为这是规范所要求的,此外,需要通过 @Constraint 注解指定一个验证器类,这里对应的是 NotEmptyValidator,其代码如下:

public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> {

    @Override
    public void initialize(NotEmpty constraintAnnotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return StringUtil.isNotEmpty(value);
    }
}

以上验证器类实现了 ConstraintValidator 接口,并在该接口的 isValid( ) 方法中完成了具体的参数验证逻辑。需要注意的是,实现接口时需要指定泛型,第一个参数表示验证注解类型(NotEmpty),第二个参数表示需要验证的参数类型(String)。

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
img
img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ty(value);
}
}


       以上验证器类实现了 ConstraintValidator 接口,并在该接口的 isValid( ) 方法中完成了具体的参数验证逻辑。需要注意的是,实现接口时需要指定泛型,第一个参数表示验证注解类型(NotEmpty),第二个参数表示需要验证的参数类型(String)。


**收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。**
[外链图片转存中...(img-CSffvRtJ-1715662642510)]
[外链图片转存中...(img-d3mn0ap0-1715662642511)]

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人**

**都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值