Spring Boot入门教程:60分钟快速上手

注释比代码长
2025-12-17 16:49
阅读 463

上周五晚上9点半,我瘫在工位上盯着IDEA里满屏的红色报错,脑子里只有一个念头:“早知道就用 Go 写这个爬虫了。”
这事儿得从头说起——我们组最近被临时塞了个需求:搞一个内部内容聚合服务,定时从几个公开站点抓取商品信息(别问为啥不直接调API,问就是产品说“这些站没开放接口”)。Deadline是三天后,而我刚好在双11大促压测完的第二天接到这个活儿。人还飘着,脑子还在处理Redis集群的慢查询日志,结果产品经理一句“很简单吧?你不是会Java吗?”直接把我钉死在椅子上。

我是京东上海后端团队的五年老狗,经历过618和双11的流量洪峰,日常开发用Mac(Windows只留着测试IE兼容性,虽然现在基本没人用了),对前端动画还挺感兴趣(毕竟看着loading转圈也得有点审美)。但说实话,Spring Boot 这玩意儿,我一直用得挺佛系——公司主框架早就封装好了starter、中间件、配置中心,我平时写业务代码就像搭乐高,很少从零搭建。这次突然要裸写一个独立服务,还得集成爬虫逻辑,瞬间有点懵。


为什么不用 Go?别急,先比一比

很多人第一反应是:“这种IO密集型任务,Go不香吗?” 我一开始也这么想。毕竟Go的goroutine天生适合高并发爬虫,轻量、编译快、部署简单。但在实际业务场景里,技术选型从来不是“哪个更酷”,而是“哪个更稳、更快交付、更符合团队栈”。

我把两个方案拉出来遛了遛:

维度 Spring Boot (Java 17) Go (1.22)
开发速度 ⭐⭐⭐⭐(有大量现成生态) ⭐⭐⭐(需手动集成很多轮子)
团队熟悉度 ⭐⭐⭐⭐⭐(全员Java栈) ⭐(只有俩人会)
爬虫库成熟度 Jsoup/HttpClient + 定时任务 Colly + Cron
部署运维 Docker + K8s(公司标准) 同左,但监控埋点要重写
与现有系统集成 直接调用公司统一认证、日志、配置中心 需要额外适配
内存占用 较高(~500MB起) 极低(~20MB)

结论很现实:虽然Go更轻量,但为了三天交付,用Spring Boot能复用公司已有的中间件SDK、统一告警、链路追踪,省下至少一天调试时间。而且——我司运维看到Go服务会皱眉(他们只认Java应用的JMX指标),测试同学也不会写Go的Mock Server。算了,忍一忍,上Spring Boot。


60分钟实操:从 start.spring.io 到跑起来

Step 1:初始化项目(5分钟)

打开 https://start.spring.io,填好Group、Artifact,语言选Java,打包选Jar,Java版本17(公司新项目标配),依赖勾上:

  • Spring Web(必须)
  • Spring Data JPA(后面存数据用)
  • H2 Database(本地开发用内存DB,上线切MySQL)
  • Spring Boot DevTools(热加载,救命神器)
  • Quartz Scheduler(定时爬虫)

⚠️ 吐槽:别信网上那些“Spring Initializr太重”的说法——对我们这种赶deadline的人来说,它就是生产力核弹。

生成下载,解压,IDEA打开。搞定。

Step 2:写个最简Controller验证通路(10分钟)

@RestController
public class HealthController {
    @GetMapping("/ping")
    public String ping() {
        return "pong! - built at " + new Date();
    }
}

终端敲:

./mvnw spring-boot:run

浏览器访问 http://localhost:8080/ping,看到返回就算通了。千万别跳过这一步! 去年双11前我就因为跳过健康检查,上线后发现端口被占,差点背锅。

Step 3:集成爬虫逻辑(20分钟)

这里用 Jsoup(HTML解析) + Apache HttpClient(高性能HTTP客户端)。在 pom.xml 加依赖:

<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.17.2</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
</dependency>

写个Service:

@Service
public class ProductCrawlerService {

    private final CloseableHttpClient httpClient = HttpClients.createDefault();

    public List<Product> crawlFromSite(String url) throws IOException {
        // 设置User-Agent防反爬
        HttpGet request = new HttpGet(url);
        request.setHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36");

        try (CloseableHttpResponse response = httpClient.execute(request)) {
            String html = EntityUtils.toString(response.getEntity());
            Document doc = Jsoup.parse(html);

            // 假设页面结构是 <div class="product">...<span class="price">¥100</span></div>
            return doc.select(".product").stream()
                .map(el -> new Product(
                    el.select(".name").text(),
                    el.select(".price").text().replace("¥", "")
                ))
                .collect(Collectors.toList());
        }
    }
}

💡 注意:真实爬虫要考虑重试、代理、验证码、动态渲染(这时候可能得上Selenium或Playwright),但内部工具先跑通再说。产品经理说“就几个静态页”,信他个鬼!

Step 4:存数据库 + 定时任务(15分钟)

先建Entity:

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String price;

    // constructor, getters, setters...
}

Repository就一行:

public interface ProductRepository extends JpaRepository<Product, Long> {}

然后写定时任务:

@Component
public class CrawlScheduler {

    @Autowired
    private ProductCrawlerService crawlerService;
    
    @Autowired
    private ProductRepository productRepo;

    // 每30分钟跑一次
    @Scheduled(fixedRate = 30 * 60 * 1000)
    public void crawlAndSave() {
        log.info("Starting crawl job...");
        try {
            List<Product> products = crawlerService.crawlFromSite("https://example-shop.com/list");
            productRepo.saveAll(products);
            log.info("Saved {} products", products.size());
        } catch (Exception e) {
            log.error("Crawl failed!", e);
            // 这里应该发企业微信告警,但demo先打印
        }
    }
}

别忘了在主类加 @EnableScheduling

Step 5:配置文件(5分钟)

application.yml

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

logging:
  level:
    com.yourpackage: debug

🤫 秘密:ddl-auto: update 在生产环境是大忌!我们上线前一定会改成 validate 并手动管理SQL脚本。但本地开发图快,谁管那么多?

Step 6:跑起来 & 验证(5分钟)

./mvnw spring-boot:run

等30秒,看控制台有没有 Saved X products。再查H2数据库(Spring Boot自动启H2 Console,访问 /h2-console,JDBC URL填 jdbc:h2:mem:testdb)。


踩坑实录:那些让我想砸Mac的瞬间

  1. Jsoup解析乱码:目标站是GBK编码,Jsoup默认UTF-8。解决方案:Jsoup.parse(html, "GBK")
  2. HttpClient连接池耗尽:没close response,线程卡死。务必用try-with-resources!
  3. 定时任务阻塞主线程:Quartz默认单线程。加配置:
    spring:
      task:
        scheduling:
          pool:
            size: 5
    
  4. H2和MySQL语法差异:本地用H2的 AUTO_INCREMENT,上线MySQL报错。后来统一用 GenerationType.IDENTITY 才解决。

最惨的是——忘了加 @Transactional,导致批量save时每个insert开一个事务,DB CPU飙到100%。还好是在测试环境,不然又是一次P0事故。


为什么我还是推荐Spring Boot给新人?

虽然Go在爬虫场景确实更轻量,但Spring Boot的工程化能力太强了:

  • 自动配置(AutoConfiguration)省去90%样板代码
  • Actuator提供 /health, /metrics 等生产级端点
  • Starter机制让集成Redis、RabbitMQ像喝水一样简单
  • Spring Cloud全家桶无缝对接微服务

更重要的是——在大厂,稳定性和可维护性永远排在“技术时髦”前面。我们组去年尝试用Go重写一个边缘服务,结果发现:

  • 日志格式和公司ELK不兼容
  • 链路追踪ID透传要自己实现
  • 压测时GC停顿没了,但goroutine泄漏更难查

最后还是回滚到Java。血泪教训啊!


最后几句真心话

写这篇教程,其实是因为上周帮实习生搭环境时,发现很多人对Spring Boot还停留在“XML配置地狱”的印象。其实现在的Spring Boot,真的可以60分钟跑通一个生产级雏形。

当然,如果你只是写个一次性爬虫脚本,Go or Python definitely better。但一旦涉及用户体系、权限控制、事务、监控告警、灰度发布——Spring Boot这套组合拳,依然是Java世界的最优解。

哦对了,今天那个爬虫服务已经上线了。虽然产品经理昨天又提了新需求:“能不能把图片也下载下来?”……算了,不说了,我去改代码了。

作者:京东上海后端,Mac党,双11幸存者。
本文所有代码已脱敏,可在GitHub Gist查看(假的,别点)。
下期预告:《如何在Spring Boot里优雅地接入公司统一登录?》——求产品经理别再让我重复造轮子了!

评论 0

最热最新
暂无评论
注释比代码长Lv.1
0
影响力
0
文章
0
粉丝