从 Android 开发视角学习后端项目
2026/5/8 14:05:31 网站建设 项目流程

从 Android 开发视角学习后端项目

本文是一次从 Android 开发转向理解 Java 后端项目的学习记录。文中已隐藏真实公司、项目、模块、接口域名、内网地址、账号密码和版权信息,仅保留通用技术结构与学习方法。

背景

我原本主要做 Android 开发,日常更熟悉 Activity、Fragment、ViewModel、Retrofit、OkHttp、Room、Gson 等移动端技术。最近接触到一个与 Android 项目配套的 Java 后端工程,希望能看懂接口是如何从 Android 请求一路走到数据库,再把结果返回给 App。

刚开始看后端项目时,最困惑的是目录很多、模块很多、注解很多,不知道应该从哪里入手。后来我发现,不应该一开始就试图理解全部后端知识,而是应该先抓住一条真实接口链路。

项目技术栈概览

这个后端项目是一个典型的 Java 企业后端工程,主要技术栈包括:

  • Java 8
  • Maven 多模块工程
  • Spring Boot
  • Spring MVC
  • Spring Security
  • JWT Token
  • MyBatis / MyBatis-Plus
  • MySQL
  • Redis
  • Druid 数据库连接池
  • Swagger / 接口文档工具
  • PageHelper 分页
  • Undertow / Web 容器
  • Lombok

从 Android 视角类比:

后端技术作用Android 类比
Maven管理依赖和模块Gradle
Spring Boot启动和组织后端应用Android Application + 框架基础设施
Controller提供 HTTP 接口Retrofit 接口的服务端实现
Service业务逻辑层Repository / UseCase
Mapper数据库访问接口DAO
MyBatis XMLSQL 查询文件Room SQL / SQLite 查询
DTO / VO参数和返回数据模型Request / Response Bean
AjaxResult统一接口返回格式ApiResponse
Redis缓存、登录态、Token 用户信息MMKV / DataStore 的服务端类比
Security / JWT登录鉴权OkHttp Header Token 机制

第一阶段:先看懂一个接口

学习后端最有效的入口不是配置文件,也不是所有模块,而是一条 Android 正在调用的接口。

我选择了一条成绩查询接口作为入口,它的后端链路大概是:

Android Retrofit -> Controller -> Request 对象 -> Service -> ServiceImpl -> Mapper -> MyBatis XML -> MySQL -> DTO -> AjaxResult -> Android Response Bean

这条链路可以理解为:

App 发起 HTTP 请求 后端 Controller 接收请求 Service 处理业务 Mapper 执行 SQL 数据库返回数据 后端封装 JSON App 解析响应

URL 是怎么拼出来的

后端接口 URL 通常由三部分组成:

服务地址 + 应用上下文路径 + Controller 路径 + 方法路径

例如:

http://host:port/app-context/module/action

在 Spring Boot 项目里,端口和上下文路径一般来自配置文件:

server:port:8080servlet:context-path:/app-context

Controller 上通常有:

@RestController@RequestMapping("/module")publicclassDemoController{@GetMapping("/action")publicAjaxResultaction(){returnAjaxResult.success();}}

最终接口路径就是:

GET /app-context/module/action

Android Retrofit 中,如果baseUrl已经包含:

http://host:port/

那么接口路径一般写:

@GET("app-context/module/action")

GET、POST 和参数传递

后端常见的参数接收方式有几类。

GET + 普通对象参数

后端:

@GetMapping("/list")publicAjaxResultlist(QueryReqreq){returnAjaxResult.success(service.list(req));}

Android:

@GET("app-context/module/list")Call<ResultBean>list(@Query("userId")longuserId,@Query("type")Stringtype);

请求类似:

GET /list?userId=1&type=2

Spring 会自动把 query 参数绑定到QueryReq对象里。

GET + @RequestParam

后端:

@GetMapping("/detail")publicAjaxResultdetail(@RequestParam("id")Longid){returnAjaxResult.success(service.detail(id));}

Android:

@GET("app-context/module/detail")Call<ResultBean>detail(@Query("id")longid);

这种适合参数比较少的查询接口。

POST + @RequestBody

后端:

@PostMapping("/save")publicAjaxResultsave(@RequestBodySaveReqreq){returnAjaxResult.success(service.save(req));}

Android:

@POST("app-context/module/save")Call<ResultBean>save(@BodySaveReqreq);

请求体是 JSON:

{"userId":1,"score":95}

这种适合新增、保存、提交复杂对象。

Multipart 文件上传

后端:

@PostMapping("/upload")publicAjaxResultupload(@RequestParam("file")MultipartFilefile){returnAjaxResult.success();}

Android:

@Multipart@POST("app-context/module/upload")Call<ResultBean>upload(@PartMultipartBody.Partfile);

适合上传图片、视频、文件。

@PathVariable 路径参数

后端:

@GetMapping("/user/{userId}")publicAjaxResultuser(@PathVariable("userId")LonguserId){returnAjaxResult.success(service.user(userId));}

Android:

@GET("app-context/module/user/{userId}")Call<ResultBean>user(@Path("userId")longuserId);

请求路径:

GET /user/1

这里的1是路径的一部分,不是 query 参数。

Controller、Service、Mapper 的分工

后端项目里最重要的是分层。

Controller 管 HTTP Service 管业务 Mapper 管数据库 XML 管 SQL

Controller 不应该直接写 SQL。它应该负责接收参数、调用 Service、返回统一结果。

Service 负责业务逻辑,比如:

  • 参数校验
  • 业务规则判断
  • 默认值处理
  • 调用多个 Mapper
  • 计算排名或分数
  • 组装返回数据
  • 抛出业务异常

Mapper 负责数据库访问。它的 Java 方法会和 MyBatis XML 里的 SQL 对应。

例如:

publicinterfaceDemoMapper{List<ScoreDTO>list(QueryDTOquery);}

对应 XML:

<selectid="list"resultType="com.example.ScoreDTO">SELECT id AS scoreId, user_id AS userId, score FROM score_table WHERE user_id = #{userId}</select>

方法名list和 XML 中的id="list"对应。

Request、DTO、VO 的区别

一开始我很困惑:为什么 Controller 接收一个对象,转手又 copy 成另一个对象?

后来理解为:

Req:接口层请求对象,面向 Android / 前端 DTO:业务层传输对象,面向 Service / Mapper VO:返回给前端或 Android 的展示对象

例如:

publicAjaxResultlist(QueryReqreq){QueryDTOdto=BeanUtils.copy(req,QueryDTO.class);List<ScoreVO>list=service.list(dto);returnAjaxResult.success(list);}

这样做的好处是解耦:

外部接口参数变化,不一定影响内部业务对象 内部业务字段变化,也不一定暴露给外部

这和 Android 中把网络 Response 转成 UI Model 的思路很像。

MyBatis 参数是怎么进入 SQL 的

XML 中经常看到:

#{userId}

它的值来自 Mapper 方法传入的参数对象。

例如:

List<ScoreDTO>list(QueryDTOquery);

XML:

<selectid="list"parameterType="com.example.QueryDTO">SELECT * FROM score_table<where><iftest="userId != null">AND user_id = #{userId}</if></where></select>

这里的:

#{userId}

本质是:

query.getUserId()

<if>是动态 SQL,表示有值才拼接条件。

MyBatis 返回结果怎么变成 Java 对象

MyBatis 常见两种返回映射方式:

resultType

<selectid="list"resultType="com.example.ScoreDTO">SELECT id AS scoreId, user_id AS userId, score FROM score_table</select>

resultType表示 SQL 每一行结果自动封装成一个 Java 对象。

一行 -> ScoreDTO 多行 -> List<ScoreDTO>

resultMap

<resultMapid="ScoreMap"type="com.example.ScoreDTO"><idcolumn="id"property="scoreId"/><resultcolumn="user_id"property="userId"/><resultcolumn="score"property="score"/></resultMap>
<selectid="list"resultMap="ScoreMap">SELECT id, user_id, score FROM score_table</select>

resultMap是手动告诉 MyBatis:

数据库字段 -> Java 字段

比如:

user_id -> userId

如果项目没有开启下划线转驼峰配置,那么 SQL 中最好写别名:

user_idASuserId

否则user_id不一定能自动进入 Java 的userId字段。

统一返回格式

后端接口一般不会直接返回业务对象,而是包一层统一结构。

例如:

{"code":200,"msg":"操作成功","time":"2026-05-07 10:00:00","data":{}}

Android 端通常应该有类似:

publicclassApiResult<T>{publicintcode;publicStringmsg;publicStringtime;publicTdata;}

判断时不要只看 HTTP 是否成功,还要看业务code

if(response.body()!=null&&response.body().code==200){// 业务成功}else{// 显示 msg}

异常处理

后端里常见两种错误返回方式:

returnAjaxResult.error("操作失败");

或者:

thrownewServiceException("业务异常");

如果项目配置了全局异常处理器,那么 Controller 一般不需要每个接口都手动 try-catch。业务层抛出的异常会被统一捕获,然后转换成统一 JSON 返回给 Android。

理解方式:

Service 抛异常 -> 全局异常处理器捕获 -> 统一封装 code/msg -> Android 显示 msg

登录接口的两种情况

这次学习中,我发现后端项目中可能同时存在两类登录。

业务登录

有些 Android 端登录接口只是校验账号密码,然后返回userId

流程:

Android 传 username/password -> 后端查询用户 -> 后端校验密码 -> 返回 userId -> Android 用 userId 继续查用户信息或提交业务数据

这种接口不一定返回 token,也不一定使用 Authorization 请求头。

JWT 登录

另一类是标准 token 登录。

流程:

登录成功 -> 后端生成 token -> Android 保存 token -> 后续请求添加 Authorization 请求头 -> 后端过滤器校验 token -> 后端识别当前登录用户

Android 请求头类似:

Authorization: Bearer xxx

后端通常通过过滤器从请求头读取 token,然后把登录用户放入安全上下文。

Android baseUrl 和后端端口

Android 项目中 Retrofit 一般配置:

newRetrofit.Builder().baseUrl("http://host:port/").addConverterFactory(GsonConverterFactory.create()).build();

后端本地配置的端口不一定和 Android 访问端口一致。

可能存在:

  • Nginx 转发
  • 网关
  • Docker 端口映射
  • 测试环境和正式环境端口不同

所以看到:

Android 访问端口 A 后端配置端口 B

不一定矛盾,可能是部署层做了转发。

如果我要新增一个接口

以后如果要新增一个后端接口,我会按这个顺序做:

1. 明确需求:URL、GET/POST、入参、返回值 2. 定义 Req / DTO / VO 3. 写 Controller 方法 4. 在 Service 接口加方法 5. 在 ServiceImpl 写业务逻辑 6. 在 Mapper 接口加方法 7. 在 MyBatis XML 写 SQL 8. Android Retrofit 增加接口 9. Android 增加 Result Bean 10. 联调并检查 code/msg/data

一个简单查询接口可能是:

@GetMapping("/latest")publicAjaxResultlatest(@RequestParam("userId")LonguserId){returnAjaxResult.success(scoreService.latest(userId));}

SQL 中为了避免映射问题,尽量写清楚别名:

SELECTidASscoreId,user_idASuserId,score,create_timeAScreateTimeFROMscore_tableWHEREuser_id=#{userId}ORDERBYcreate_timeDESCLIMIT1

Android:

@GET("app-context/score/latest")Call<ScoreResult>latest(@Query("userId")longuserId);

学习收获

这次学习让我最有收获的是:后端不是一堆陌生注解,而是一条可以追踪的链路。

从 Android 视角看后端,可以按下面的问题一步步拆:

1. Android 调的是哪个 URL? 2. 后端哪个 Controller 接收? 3. 参数是 Query、Path、Body 还是 Multipart? 4. Controller 调哪个 Service? 5. Service 有没有业务处理? 6. Mapper 方法名对应哪个 XML select/update? 7. SQL 查哪张表? 8. SQL 结果怎么映射成 DTO? 9. AjaxResult 的 data 是什么? 10. Android 的 Result Bean 是否和后端返回一致?

只要能回答这些问题,就能看懂大部分业务接口。

后续计划

接下来我准备继续做两件事:

  1. 选一个 Android 页面,从点击按钮开始,完整追踪到后端 SQL。
  2. 尝试自己写一个简单查询接口,并在 Android 中完成联调。

这样比单独看教程更有效,因为它直接连接了我熟悉的 Android 项目和正在学习的后端项目。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询