尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Java开发入门:从零开始构建第一个RESTAPI

Java开发入门:从零开始构建第一个RESTAPI
📅 发布时间:2026/7/4 9:25:51

你打开IDE,心里默念:我要用Java写一个REST API。这个念头可能是来自产品经理的紧迫需求,或是你想从CRUD工程师跃迁为全栈开发者的第一步。别被“REST”这个词吓到——它本质上就是一对规则,告诉你的API如何用HTTP动词去拥抱资源。资源就是数据,比如用户、订单、文章;HTTP动词就是GET、POST、PUT、DELETE。Java生态里有Spring Boot这个黑魔法工具,它帮你省掉配置Tomcat的麻烦,让你把注意力聚焦在业务逻辑上。今天我们就从零开始,不跳过任何细节,构建一个能真正运行在浏览器或Postman里的REST API。

项目骨架:用Spring Initializr一键生成

到start.spring.io去,这是你的神殿。选择Maven Project、Java 17或21、Spring Boot 3.x。填入Group:com.example,Artifact:demo。依赖搜索框里至少要勾选:Spring Web(提供REST支持)、Spring Boot DevTools(热部署)、Lombok(减少样板代码)。点Generate,下载zip后解压,用IntelliJ或VS Code打开。你会看到一个DemoApplication.java,里面有个main方法。别碰它,它是启动入口。你真正的战场在com.example.demo包下。

你的第一个任务是让程序跑起来。右键运行DemoApplication,控制台输出Spring的Banner,并看到Tomcat started on port 8080。恭喜,已经有一个空壳的Web应用在运行。现在访问http://localhost:8080,会返回一个Whitelabel Error Page——正常,因为你还没建立任何控制器。

第一个端点:用@RestController宣示主权

新建一个包controller,在里面创建HelloController.java。粘贴如下代码:

package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/api/hello") public String sayHello() { return "Hello, World!"; } }

关键点:@RestController告诉Spring:这个类的所有方法返回值都直接写入HTTP响应体,而不是跳转到视图模板。加上@GetMapping("/api/hello"),就定义了一个GET端点。重启应用,访问http://localhost:8080/api/hello,浏览器里会显示“Hello, World!”。你刚刚完成了零到一的突破。

现在开始不要满足于字符串。REST API的核心是操作资源,资源通常以JSON格式呈现。我们需要让API返回Java对象,Spring会自动将其序列化为JSON。这是Spring Boot默认在classpath里包含Jackson依赖的功劳,你不必手动配置任何json库。

定义资源:从简单POJO开始

资源模型应该放在model包下。新建User.java:

package com.example.demo.model; import lombok.Data; @Data public class User { private Long id; private String name; private String email; }

@Data是Lombok的注解,自动生成getter、setter、toString、equals等。没有它你需要手写几十行代码。现在修改HelloController,返回一个用户列表:

@GetMapping("/api/users") public List<User> getUsers() { User user = new User(); user.setId(1L); user.setName("Alice"); user.setEmail("alice@example.com"); return Collections.singletonList(user); }

访问/api/users,你会看到JSON数组。看,这就是REST的启蒙:HTTP GET /api/users 返回用户集合,资源通过URL路径标识,数据通过JSON交换。但每次硬编码创建对象显然不靠谱。你需要一个数据层,但本环节先不用数据库,用内存里的静态列表模拟。

CRUD的骨架:GET、POST、PUT、DELETE

新建service包,创建UserService.java,这个服务类负责管理一个ConcurrentHashMap作为持久化存储。同时让控制器注入这个服务。

package com.example.demo.service; import com.example.demo.model.User; import org.springframework.stereotype.Service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; @Service public class UserService { private final Map<Long, User> store = new ConcurrentHashMap<>(); private final AtomicLong idCounter = new AtomicLong(1); public User createUser(User user) { long id = idCounter.getAndIncrement(); user.setId(id); store.put(id, user); return user; } public User getUser(Long id) { return store.get(id); } public List<User> getAllUsers() { return new ArrayList<>(store.values()); } public User updateUser(Long id, User user) { if (!store.containsKey(id)) return null; user.setId(id); store.put(id, user); return user; } public boolean deleteUser(Long id) { return store.remove(id) != null; } }

然后控制器扩展为:

@RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping public List<User> getAll() { return userService.getAllUsers(); } @GetMapping("/{id}") public User getById(@PathVariable Long id) { return userService.getUser(id); } @PostMapping public User create(@RequestBody User user) { return userService.createUser(user); } @PutMapping("/{id}") public User update(@PathVariable Long id, @RequestBody User user) { return userService.updateUser(id, user); } @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { userService.deleteUser(id); } }

注意几个要点:@RequestMapping("/api/users")在类级别定义了根路径,省去每个方法重复写路径。@PathVariable绑定URL里的占位符,@RequestBody把请求体JSON反序列化为User对象。现在用Postman测试:POST到/api/users,body为{"name":"Bob","email":"bob@test.com"},返回201状态码(Spring默认201,因为@PostMapping返回201 Created)。GET /api/users/1返回那个用户。PUT /api/users/1修改email。DELETE /api/users/1删除。

但你发现没有:如果请求的ID不存在,API返回了null,或者状态码是200但body为空。这不是规范的REST响应。规范的做法是:GET单个资源不存在时返回404,POST创建返回201,PUT更新返回200或204,DELETE成功返回204。我们需要引入HTTP状态码的显式控制。

让错误更优雅:ResponseEntity与全局异常处理

修改控制器的getById方法:

@GetMapping("/{id}") public ResponseEntity<User> getById(@PathVariable Long id) { User user = userService.getUser(id); if (user == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); }

delete方法也改一下:

@DeleteMapping("/{id}") public ResponseEntity<Void> delete(@PathVariable Long id) { boolean deleted = userService.deleteUser(id); if (!deleted) { return ResponseEntity.notFound().build(); } return ResponseEntity.noContent().build(); }

但每个方法都写if判断会很笨重。更好的方式是使用全局异常处理。自定义一个ResourceNotFoundException,然后加上@ControllerAdvice。

@ResponseStatus(HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } }

在getById中直接:return userService.getUser(id).orElseThrow(() -> new ResourceNotFoundException("User not found"));但因为我们之前用null返回,现改为Optional。重新设计service的getUser返回Optional 。然后在controller中:

@GetMapping("/{id}") public User getById(@PathVariable Long id) { return userService.getUser(id) .orElseThrow(() -> new ResourceNotFoundException("User with id " + id + " not found")); }

然后创建GlobalExceptionHandler:

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public Map<String, String> handleNotFound(ResourceNotFoundException ex) { return Map.of("error", ex.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public Map<String, String> handleValidation(MethodArgumentNotValidException ex) { // 提取字段验证错误 String message = ex.getBindingResult().getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(", ")); return Map.of("error", message); } }

全局异常处理让你把错误逻辑从控制器中剥离,REST API变得整洁且一致。每个异常类型对应一个HTTP状态码和统一格式的错误响应体。这是专业API的标配。

数据验证:用Jakarta Validation保驾护航

用户提交的数据必须校验。加入spring-boot-starter-validation(旧版本需要手动加,Spring Boot 3.x已经包含)。在User类字段上加注解:

@Data public class User { private Long id; @NotBlank(message = "Name cannot be blank") private String name; @Email(message = "Email must be valid") @NotBlank(message = "Email cannot be blank") private String email; }

然后在控制器中的@RequestBody前加@Valid:

@PostMapping public ResponseEntity<User> create(@Valid @RequestBody User user) { User created = userService.createUser(user); return ResponseEntity.status(HttpStatus.CREATED).body(created); }

当你发送空name或非法email时,Spring会自动抛出MethodArgumentNotValidException,被我们的全局异常处理器捕获,返回400和错误信息。不要信任任何客户端输入,后端校验是安全的最后一道防线。

让API可发现:HATEOAS?(不,先做好基础分页和过滤)

HATEOAS是REST成熟度模型第三级,但多数生产API只用到第二级(资源加动词)。更实际的是你API需要支持分页、排序、按字段过滤。Spring Data提供了Pageable接口,但你目前没有数据库。我们可以模拟分页:在service中根据参数返回子列表。但更佳实践是直接引入Spring Data JPA并连接一个H2内存数据库,让数据持久化天然支持分页。这是很多教程跳过的步骤,但我们不跳。

在pom.xml里添加:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>

删除之前的UserService和内存存储,创建User实体和JpaRepository:

@Entity @Table(name = "users") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank private String name; @Email @NotBlank private String email; }

UserRepository:

public interface UserRepository extends JpaRepository<User, Long> { List<User> findByNameContainingIgnoreCase(String name); }

控制器修改为直接注入Repository,但为了分层,还是创建service:

@Service @Transactional public class UserService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } public User createUser(User user) { return repository.save(user); } public Optional<User> getUser(Long id) { return repository.findById(id); } public Page<User> getAllUsers(Pageable pageable) { return repository.findAll(pageable); } public Optional<User> updateUser(Long id, User newData) { return repository.findById(id).map(existing -> { existing.setName(newData.getName()); existing.setEmail(newData.getEmail()); return repository.save(existing); }); } public boolean deleteUser(Long id) { if (repository.existsById(id)) { repository.deleteById(id); return true; } return false; } }

控制器getAll接收Pageable参数:

@GetMapping public Page<User> getAll( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "id") String sortBy, @RequestParam(defaultValue = "asc") String sortDir) { Sort sort = sortDir.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending(); Pageable pageable = PageRequest.of(page, size, sort); return userService.getAllUsers(pageable); }

当你请求GET /api/users?page=0&size=5&sortBy=name&sortDir=desc,Spring Data JPA自动生成SQL查询并返回分页后的JSON,包含totalPages、totalElements、content等元数据。分页是任何CURD API的必修课,直接返回整个表是反模式。

测试你的API:不只是手工Postman

集成测试用Spring Boot的@WebMvcTest。新建test类:

@WebMvcTest(UserController.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private UserService userService; @Test void shouldReturnUserWhenExists() throws Exception { User user = new User(); user.setId(1L); user.setName("Test"); user.setEmail("test@example.com"); given(userService.getUser(1L)).willReturn(Optional.of(user)); mockMvc.perform(get("/api/users/1")) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Test")); } @Test void shouldReturn404WhenNotFound() throws Exception { given(userService.getUser(99L)).willReturn(Optional.empty()); mockMvc.perform(get("/api/users/99")) .andExpect(status().isNotFound()); } }

自动化测试能让你在重构时立即发现破坏性变更。别偷懒,每一层都写点测试。除了Web层,还应写service层单元测试(使用@ExtendWith(MockitoExtension.class))和Repository层测试(@DataJpaTest)。

安全第一位:添加简单的API Key验证

虽然完整认证要用Spring Security + JWT,但入门阶段可以尝一个简单的filter来验证请求头中的API Key。实现一个OncePerRequestFilter:

@Component public class ApiKeyFilter extends OncePerRequestFilter { private static final String API_KEY = "my-secret-key-123"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String apiKey = request.getHeader("X-API-KEY"); if (apiKey == null || !apiKey.equals(API_KEY)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("{\"error\":\"Invalid API key\"}"); return; } filterChain.doFilter(request, response); } }

然后配置FilterRegistrationBean在控制器之前执行:

@Bean public FilterRegistrationBean<ApiKeyFilter> apiKeyFilterRegistration(ApiKeyFilter filter) { FilterRegistrationBean<ApiKeyFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(filter); registration.addUrlPatterns("/api/"); registration.setOrder(1); return registration; }

现在你的API受到硬编码密钥保护。当然生产环境绝不会这么干,但理解过滤器机制是学习Spring Security的前奏。

文档自动生成:Swagger/OpenAPI

配合springdoc-openapi-starter-webmvc-ui(Spring Boot 3版本),在pom.xml添加:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.3.0</version> </dependency>

启动后访问http://localhost:8080/swagger-ui.html,你会看到交互式API文档。你可以为控制器和方法添加@Operation和@ApiResponse注解描述业务含义。好的API文档不止是曲线救赎,更是团队协作的基础设施。

进阶:输入验证、自定义序列化、多版本共存

你还可以做很多:使用@JsonView控制序列化字段暴露;通过@ControllerAdvice统一包装响应格式(例如总是返回{status, message, data}结构);利用Spring的Content Negotiation支持XML或自定义格式;使用版本控制(路径版/v1/users,头部版Accept: application/vnd.myapp.v2+json)。

记住,REST API的终极奥义是使客户端与服务端解耦。每个端点应当幂等(GET、PUT、DELETE符合,POST不一定),URL应该命名复数名词,使用复数而不是单数(/users而不是/user),不要出现动词(/getUsers这种错误)。HTTP状态码是沟通语言,务必正确使用:201表示创建成功,204表示无内容,400表示客户端错误,401表示未认证,403表示未授权,404表示资源不存在,500表示服务器内部错误。

把项目打包成可交付制品

在pom.xml里配置Spring Boot Maven插件,然后用命令行mvn clean package,生成一个fat jar文件。在服务器上用java -jar target/demo-0.0.1-SNAPSHOT.jar运行,你的REST API就能在任意环境启动。你还可以在application.properties里设置server.port=80,以及数据库连接、日志级别等。

回过头看看,你从Hello World走到了一个分页、校验、异常处理、认证、文档齐全的REST API。这个过程不是背语法,而是建立一种思维模型:资源经过URL暴露,操作通过HTTP动词映射,数据通过JSON流动,错误通过状态码和格式传递。你的第一个API可能只有几百行代码,但它承载着REST全貌。将来面对更复杂的微服务、事件驱动架构时,今天打下的地基永远不会浪费。

现在,去构建更多接口吧。把这段代码提交到GitHub,叫它“first-rest-api”,然后告诉世界:你跨过了Java Web开发的门槛。

相关新闻

  • cuda06- 流 并发
  • 15分钟极速部署:TrueNAS Scale上搭建高性能Minecraft Forge服务器全指南
  • gsplat完整指南:如何快速掌握CUDA加速的高斯溅射技术

最新新闻

  • 大模型部署六种方式:从Ollama到vLLM的选型实战指南
  • Transformer与GNN图建模能力边界三标尺分析
  • 深入解析VeraCrypt核心模块:架构、加密机制与安全实践
  • CentOS 7.9安装全攻略:从镜像选择到安全配置的完整指南
  • YOLO26双重注意力机制优化与实现
  • 动物森友会存档编辑器NHSE:从零开始打造完美岛屿的终极指南

日新闻

  • STM32F745VG与MC6470 IMU的高性能姿态控制系统设计
  • 机器不消费,人何以生存
  • AI项目操作手册编写规范与最佳实践

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号