后端api之路——初窥门径

本文介绍了作者从Android开发转向后端API开发的历程,选择了Java作为开发语言,使用Spring Boot和SSM(Spring+SpringMVC+MyBatis)进行项目搭建。详细讲解了@Controller、JSON数据处理和数据校验等内容,还提到了使用IDEA、Postman等工具的技巧。
摘要由CSDN通过智能技术生成

前言

作为一名优秀的(自认为)Android开发工程师,在工作之余研究一些新的有趣的技术是我的乐趣,每当有一些想法就会想去尝试实现,demo敲的多了就产生了自己制作一款个人的app的想法。但是如今互联网的发展,没有后端支持的单机app很难有亮点,每每一些有趣的想法终止于没有后端api的支持,多次之后终于打定主意,学习后端接口的开发,独立完成app前后端的实现。

后端的语言可选的有很多种,Android开发的我当仁不让地选择Java作为后端开发语言,可以节省不少时间。作为出现了一门十多年的语言,java在后端的发展也经历几个代表性的时代:早期的JSP+Servlet+JavaBean,之后的SSH(Spring+Struts2+Hibernate),以及现如今的SSM(Spring+SpringMVC+MyBatis)。
当你搜索“后端开发”,上述这些词很容易出现在搜索结果中,我也是看了很多资料才理清它们之间的关系。网络上搜索结果有很多JSP+Servlet+JavaBean以及SSH相关的教程,但我最终还是选择的最新的技术方案:SSM,新技术能取代老技术总有它特别的优势,要不更强大,要不使用更简便,因此最新的成熟技术总会是好的。并且工具也没有选择Eclipse或者MyEclipse,而是IDEA,虽然对电脑的配置要求更高,但是在代码提示以及项目管理方面有着很大的优势,而且Android studio也是基于IDEA,界面和快捷方式都是一样的,非常方便android开发者的适应过度。

环境搭建

一开始我也是跟随 这篇文章 搭建环境并运行了第一个web项目。新建项目时用spring boot 的方式,用于取代传统Spring项目繁琐的xml配置,其配置全部可以通过注解的形式进行配置,后续会具体说明。
附带一说:IDEA是需要付费的,而且一年的价格还不便宜,表示买不起啊!!当时找到了在线免费生成IntelliJ IDEA 15.0注册码 由于我还在30天试用期,不确定是否真的有用,但是设置后确实没有弹出提示付费的通知,但愿是有效的。

搭建环境的过程中也遇到一些问题,如果你也遇到了,可以看一下:
Mac 安装mysql问题

如果你对sql语句不熟悉,也可以看下:常用SQL语句汇总
Workbench之类的mysql可视化工具也是必不可少的,可以非常方便地操作数据库。
这里写图片描述

调试接口的工具Postman也是非常好用的
这里写图片描述

开始项目

开始之前最好像了解下MVC设计模式,SpringMVC流程,如:
SpringMVC流程架构图
Spring MVC 流程图

@Controller

用于标记类,表示这个类是SpringMVC的一个Controller,通常于@RequestMapping配合使用,指定匹配的路径。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloContorller {

    @RequestMapping("/hello")//指定匹配路径
    @ResponseBody//将返回数据直接放入responseBody中
    public String helloWorld() {
        return "hello world!";
    }
}

由于我们要做的事app的后端接口,因此加上了@ResponseBody注解。如果不清楚responseBody可以看下http协议的相关知识,如:HTTP协议详解(真的很经典)
使用Postman测试一下接口:
这里写图片描述

@RequestMapping除了放在方法上,也可以放在类上,表示这个Controller类中所有方法的共同路径前缀

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@ResponseBody
@RequestMapping("/hello")
public class HelloContorller {

    @RequestMapping("/world")
    public String helloWorld() {
        return "hello world!";
    }
}

此时访问的路径为:http://localhost:8080/hello/world

同样的@ResponseBody放在类上表示其下所有方法都默认注解了@ResponseBody

   @RequestMapping(value = "/world", method = RequestMethod.GET, produces = "application/json", params = "myParam=myValue", headers = "Refer=http://www.example.com")

@RequestMapping有很多属性用来匹配请求:
value指定路径,默认可以省略
method指定请求方式,只有指定的请求方式可以访问到该方法
consumes指定处理请求的提交内容类型(即Content-Type)
produces指定返回的内容类型
paran指定request中必须包含指定的参数值
header指定request中必须包含指定的header值

@RequestParam注解用于将制定类型的请求参数赋值方法中的形参

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController//相当于同时注解了@Controller + @ResponseBody
public class HelloContorller {

    @RequestMapping("/hello")
    public String helloWorld(@RequestParam("name") String name, @RequestParam("password") String password) {
        return name + "/" + password;
    }
}

请求结果如下:
这里写图片描述

@RequestParam也有几种属性:
value指定参数名,可以省略,默认于注解的参数名相同
required指定参数是否事必须的,默认必须,如果参数必须但是请求未加参数,则返回400.
defaultValue 如果没有传递参数,使用的默认值

与@RequestParam类似的还有几种注解,功能也类似,用于获取request不同的参数。

@PathVariable 可以获取请求url中的动态参数

@RequestMapping("/pathVariableTest/{id}")
    public Integer pathVariable(@PathVariable(required = false) Integer id) {
        return id;
    }

这里写图片描述

@RequsetHeader 将请求 的头信息区数据映射到功能处理方法的参数上。
@CookieValue将请求的Cookies数据映射到功能处理方法的参数上。

JSON数据

SpringMVC提供了处理json格式请求/响应的HttpMessageConverter:MappingJackson2HttpMessageConverter。
当使用Psotman发送请求时,需要注意编码格式的请求头的COntentType指定:
1.application/x-www-form-urlencoded,这种情况的数据@RequestParam和@RequestBody都可以处理;
2.multipart/form-data,@RequestBody不能处理;
3.application/json,application/xml等格式必须使用@RequestBody来处理;

    @RequestMapping("/json")
    public User json(@RequestBody User user) {
        LOGGER.info(user.toString());
        user.setName("change");
        return user;
    }
public class User {
    private Integer id;
    private String name;
    private String sex;
    private Integer age;

   //省略getter,setter和构造

这里写图片描述

注意发送请求时请求方式为Post,Body属性选择raw,并且text选择JSON

数据校验

Spring拥有自己独立的数据校验框架Validation,推荐使用实现了Validation的JSR303校验:Hibernate Validator
通过在gradle文件中引入compile group: ‘org.hibernate’, name: ‘hibernate-validator’, version: ‘5.3.4.Final’


dependencies {
    compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.0')
    compile('org.springframework.boot:spring-boot-starter-web')
    runtime('mysql:mysql-connector-java')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    compile group: 'org.hibernate', name: 'hibernate-validator', version: '5.3.4.Final'
}

就可以使用JSR303数据校验了。

通过添加对应注解增加限制,例如上一例的User

public class User {
    private Integer id;

    @Size(max = 18 ,message = "姓名不能超过18位字符")
    @NotNull
    private String name;

    @Size(max = 2)
    private String sex;

    @Max(value = 200,message = "年龄不能大于200")
    private Integer age;

    //省略getter,setter和构造
}

然后在需要进行校验的方法中添加@Valid注解

@RequestMapping("/json")
    public User json(@Valid @RequestBody User user) {
        LOGGER.info(user.toString());
        user.setName("change");
        return user;
    }

可以看到,返回的数据提示数据不正确。
这里写图片描述

这里我用到了全局处理异常的Handler


import com.example.demo.bean.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.validation.UnexpectedTypeException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 全局异常处理拦截者,当抛出异常时执行对应的方法
 */
@ResponseBody
@ControllerAdvice
public class ApiExceptionHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApiExceptionHandler.class);

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(UnexpectedTypeException.class)//当抛出此异常时执行该方法
    public Result unexpectedType(UnexpectedTypeException exception) {
        LOGGER.error("校验方法太多,不确定合适的校验方法。", exception);
        return Result.error(exception.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Result messageNotReadable(HttpMessageNotReadableException exception) {
        LOGGER.error("请求参数不匹配,request的json格式不正确", exception);
        return Result.error(exception.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result ex(MethodArgumentNotValidException exception) {
        LOGGER.error("请求参数不合法。", exception);
        BindingResult bindingResult = exception.getBindingResult();
        return new Result<Map>(Result.CODE_ERROR, "请求参数不合法。", getErrors(bindingResult));
    }

    private Map<String, String> getErrors(BindingResult result) {
        Map<String, String> map = new HashMap<>();
        List<FieldError> list = result.getFieldErrors();
        for (FieldError error : list) {
            map.put(error.getField(), error.getDefaultMessage());
        }
        return map;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public Result exc(HttpMediaTypeNotSupportedException exception) {
        LOGGER.error("Unsupported Media Type。", exception);
        return new Result(415, exception.getMessage());
    }
}

当数据校验异常时,实际上抛出MethodArgumentNotValidException异常,@ControllerAdvice注解类中有@ExceptionHandler(MethodArgumentNotValidException.class)注解的方法会执行,将错误信息返回给调用者。

Result类是一个标准的结果实体类:

public class Result<T> {

    public static final int CODE_OK = 200;
    public static final int CODE_ERROR = 400;

    private int code;
    private String message;
    private T data;

    public Result() {
    }

    public Result(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static Result error(String message) {
        return new Result(CODE_ERROR, message);
    }

    public static <T> Result<T> ok(T data) {
        return new Result<T>(CODE_OK, "ok", data);
    }
}

未完待续。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值