Go/Gin 与 Spring Boot 代码阅读速查

1. 先建立“请求链路”思维

读后端代码不要从目录开始扫,而是先找到一个请求的完整路径:

HTTP 请求
  → 路由 / Controller
  → 业务服务 Service
  → 数据访问 Repository / Mapper
  → 数据库、缓存、消息队列、外部 RPC / HTTP 服务
  → 响应

先挑一个简单的查询接口,顺着调用链读完一遍。理解一条完整链路,比读十个目录更有效。


2. Go/Gin 与 Spring Boot 概念映射

关注点 Go / Gin Spring Boot
程序入口 func main() main() + SpringApplication.run()
路由注册 r.GET(...)r.POST(...) @RequestMapping@GetMapping@PostMapping
HTTP Handler Gin Handler @RestController 中的方法
依赖创建 NewService(repo) Spring IoC 自动注入 Bean
业务层 service package @Service
数据访问 DAO / Repository / ORM Mapper / Repository,常见 MyBatis、JPA
配置 YAML + 环境变量 application.yml + Profile + 环境变量
中间件 r.Use(...) Filter、Interceptor、AOP
错误处理 c.Error / 统一响应中间件 @ControllerAdvice / @ExceptionHandler
事务 显式 tx / 回调 @Transactional
定时任务 cron 库 @Scheduled
异步任务 goroutine / worker pool @Async / 消息队列

核心区别:Go 的依赖和调用大多显式;Spring 有许多“框架启动时注册、运行时分派”的隐式调用。


3. Go/Gin 代码阅读重点

路由从哪里开始

通常搜索:

router.GET(...)
router.POST(...)
group := router.Group(...)

然后按下面顺序阅读:

route → handler → service → repository/dao → model

依赖是如何注入的

Go 常见写法:

func NewUserService(repo UserRepository) *UserService {
    return &UserService{repo: repo}
}

调用关系通常可以通过搜索 NewUserService 找到。重点关注:

  • main.gowire.go:依赖装配位置。
  • init():隐式初始化逻辑。
  • interface 注入:确认实际传入的实现类型。
  • 全局单例:DB、Redis、配置、日志、HTTP Client。

interface 的实现怎么找

type UserRepository interface {
    FindByID(ctx context.Context, id int64) (*User, error)
}

搜索:

type Xxx struct
func (x *Xxx) FindByID(...)
var _ UserRepository = (*Xxx)(nil)

Go interface 是隐式实现;没有 implements 关键字。因此最可靠的确认方式是:

  1. 找到拥有相同方法集的类型;
  2. 找到构造函数或依赖装配处;
  3. 看该接口最终被传入的是哪个具体类型。

4. Spring Boot 代码阅读重点

HTTP 入口

搜索以下注解:

@RestController
@Controller
@RequestMapping
@GetMapping
@PostMapping

示例:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{id}")
    public UserDTO get(@PathVariable Long id) {
        return userService.get(id);
    }
}

阅读路径:

URL + HTTP Method
  → Controller 方法
  → Service 方法
  → Mapper / Repository
  → SQL 或外部调用

Bean 是如何注册和注入的

Spring 管理对象称为 Bean。常见注册方式:

@Service
@Component
@Repository
@Controller
@Configuration
@Bean

常见注入方式:

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
}

这相当于 Go 中:

service := NewUserService(repository)

只是 Spring 在启动时扫描注解并自动完成构造。


5. 接口找不到实现时的排查规则

场景一:普通业务接口

public interface UserService {
    UserDTO get(Long id);
}

先搜索:

implements UserService

如果有多个实现,继续看:

@Primary
@Qualifier("xxx")
@Profile("dev")
@ConditionalOnProperty(...)
  • @Primary:默认优先实现。
  • @Qualifier:明确指定 Bean 名。
  • @Profile:按环境加载实现。
  • @Conditional...:按配置或依赖条件加载实现。

场景二:MyBatis Mapper

@Mapper
public interface UserMapper {
    UserEntity selectById(Long id);
}

通常没有手写实现类。MyBatis 会在运行时生成代理。

要找的是 XML:

<mapper namespace="com.example.UserMapper">
    <select id="selectById">
        SELECT * FROM user WHERE id = #{id}
    </select>
</mapper>

排查顺序:

Mapper 接口
  → @Mapper / @MapperScan
  → XML namespace
  → 同名 SQL id

场景三:Feign、RPC、消息客户端

@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/users/{id}")
    UserDTO get(@PathVariable Long id);
}

这类接口往往没有本地实现;框架会生成 HTTP/RPC 代理,真实业务逻辑在另一个服务中。

识别信号:

@FeignClient
RPC 框架客户端注解
Client / Remote / Provider / Consumer 命名

场景四:策略模式

public interface PaymentStrategy {
    boolean supports(String type);
    void pay(PaymentCommand command);
}

多个实现:

@Component
class AlipayStrategy implements PaymentStrategy { ... }

@Component
class WechatStrategy implements PaymentStrategy { ... }

调用者可能注入全部实现:

private final List<PaymentStrategy> strategies;

再按条件挑选:

strategies.stream()
    .filter(item -> item.supports(type))
    .findFirst();

此时具体实现不是唯一固定的,而是由 supports()、枚举、配置或 Factory 决定。搜索关键词:

Factory
Strategy
supports
getStrategy
Map<String, Interface>
List<Interface>

6. Spring 中的“隐式调用”清单

很多方法没有普通调用方,但仍可能被执行。

注解/机制 触发时机
@RequestMapping HTTP 请求到达
@Scheduled 定时任务触发
@EventListener 事件被发布
@Async 通过 Spring 代理异步执行
@Transactional 代理在调用前后管理事务
@Around / @Before AOP 切面匹配方法时执行
@PostConstruct Bean 初始化完成后执行
@PreDestroy 应用关闭时执行
MQ Listener 注解 消息到达时执行

因此,读 Spring 代码时不要只搜索方法引用;还要搜索注解、配置类、Bean 注册和事件发布点


7. 事务与代理:Spring 的高频坑

@Transactional
public void createOrder() {
    // 数据库操作
}

@Transactional 依赖 Spring AOP 代理生效。典型问题:

public void outer() {
    this.createOrder(); // 可能不会触发事务代理
}

同一个类内部通过 this 调用,往往绕过代理;从另一个 Spring Bean 调用才更符合预期。

阅读事务代码时重点确认:

  1. 方法是否标记 @Transactional
  2. 是否是 public 方法;
  3. 调用是否跨 Bean;
  4. 异常是否被吞掉;
  5. 抛出的异常类型是否会触发回滚。

8. 配置阅读原则

Go 和 Spring 都不要把配置全量读完。只在功能链路需要时找对应配置。

重点关注:

数据库连接
Redis
消息队列
外部服务地址
开关配置
超时与重试
线程池
Profile / 环境变量

Spring 常见优先级通常是:

命令行参数
  > 环境变量
  > profile 配置文件
  > application.yml
  > 代码默认值

不要把真实密码、Token、连接串写进笔记、日志、截图或提交记录。


9. 推荐的阅读顺序

1. 找一个 API / 路由
2. 读 Controller / Handler
3. 读 Service:业务规则、事务、权限、编排
4. 读 Repository / Mapper:查询或写入
5. 找 SQL、缓存、MQ、RPC 调用
6. 回头看配置和异常处理
7. 最后补充测试,确认边界条件

不要一开始阅读:

所有依赖
所有工具类
所有配置
所有实体
整个目录树

这些会让人失去主线。


10. 常用搜索关键词

# Go / Gin
GET(
POST(
Group(
New
interface
init(
Use(
go func
context.Context

# Spring Boot
@RestController
@RequestMapping
@Service
@Component
@Repository
@Mapper
@MapperScan
@Transactional
@Async
@Scheduled
@EventListener
@FeignClient
@Configuration
@Bean
@Qualifier
@Primary
@Profile
@Conditional
@Aspect

11. 一句话方法论

先通过入口建立一条完整调用链,再用注解、依赖注入、动态代理和配置补全那些“代码跳转看不见”的运行时关系。