这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

在AI时代该如何学习Java

在AI时代该如何学习Java,一个Goper如何快速转Java

在AI时代该如何学习Java,一个Goper如何快速转Java

1 - Go转Java万字总结, 要跨越哪些思维鸿沟

Goper如何快速转Java进行项目开发

主要介绍Go和java间的设计思想、工程目录结构、典型框架使用上的对比

Go转Java的设计思想对比

Go的心智模式是过程式 + 轻量级面向对象 + 并发,从接需求开始,要识别结构体和关键方法,先实现小而美的功能点,再通过组合演进式地实现复杂功能。 Java不用说了,主要是以重量级OOP为主的玩法,要从需求中先找实体,加行为,定关系,用设计模式实现更复杂的业务。

不过现代Java也在拥抱轻量级设计, 而Go里也有设计模式,不过更倾于使用函数、闭包和通道等语言特性来解决。

先从直观语法上,总结有以下几种差异情况(只总结差异,不做优劣评价):

Go: 极简主义​ - 少即是多, Go语法中有很多隐含的写法规则,在Java里则需要编写显式的修饰符(关键字)声明,比如某结构(类)中,首字母大写,是会暴露一个字段,在java则需要显示的使用public。

隐式规则与显式声明的碰撞

Go的"命名即契约"哲学:

// 大小写决定可见性
type User struct {
    ID   int    // 公开
    name string // 私有
}

func (u *User) GetName() string { // 公开方法
    return u.name
}

func (u *User) setName(name string) { // 私有方法
    u.name = name
}

Java语法层面的"显式声明"文化:

// 每个成员都需要明确修饰符
public class User {
    private Long id;        // 必须指定private
    private String name;    // 必须指定private
    
    // 公开getter,需要显式public
    public String getName() {
        return this.name;
    }
    
    // 私有setter,需要显式private
    private void setName(String name) {
        this.name = name;
    }
}

差异说明:

  • Go认为:代码即文档,命名应该自说明,过多的修饰符是噪声

    • Go在“约定优于配置”的设计上主要体现在语法中,而在实际项目开发生态上,又是需要显式控制的写法,初学者可能会有隐形记忆负担。
    • 但也由此衍生了很多标记tag的写法,如json tag,xml tag,gorm tag等等
    • 这类工程需求,如数据库映射,验证,序列化等等,标记tag是由生态库来支持解决
    • 仅在语法层面有编译时检查,Go的静态分析工具也在不断弥补运行时检查的不足
    • Go的接口实现 "如果你能走起来像鸭子,叫起来像鸭子,那你就是鸭子",只要具备对应的方法行为,那么就认为实现了该接口
  • Java认为:强调明确性和类型安全,IDE和工具可以基于修饰符提供更好的支持

    • 显式修饰符,如public,private,protected,static,final等等
    • 与Go里tag可以对标Java里annotation注解的用法场景,不过后者注解是类型安全
    • 各种@注解满天飞,存在有框架级注解、持久化注解、验证注解、序列化注解等等。看似可以编译期间做显式检查,但有不小的学习成本,开发者需要熟悉不同注解的使用场景和配置选项
    • Java的接口实现要"签字画押,明确契约",必须显示声明,如:implements,extends等等
    • 虽然有非常优秀的“约定优于配置”的设计,过度了,就累赘了,现代的Java就在不断通过添加新特性来减少样板代码和注解复杂度。

"组合优于继承"原则

Go语言采用组合而非继承的设计哲学,通过结构体嵌入和接口组合实现代码复用。而Java的继承机制虽然提供了代码复用的便利性,但也引入了父类修改可能破坏子类的风险。两种设计哲学各有优劣:Go的组合更适合构建松耦合、可演进的系统,而Java的继承在构建具有清晰类型层次的大型应用时更有效率。

拥抱Java的显式性:

// 不要试图在Java中完全复制Go的隐式组合
// 接受Java需要更多样板代码的事实,体现了显式优于隐式的设计原则

// Go风格(隐式组合)
type Service struct {
    *Logger     // 嵌入指针,自动获得Logger的方法
    *Metrics    // 自动获得Metrics的方法
    *Database   // 自动获得Database的方法
}

// Java风格(显式但有更多控制)
class Service {
    private final Logger logger;
    private final Metrics metrics;
    private final Database database;
    // 显式构造器
    public Service(Logger logger, Metrics metrics, Database database) {
        this.logger = logger;
        this.metrics = metrics;
        this.database = database;
    }
    
    // 显式委托方法
    public void log(String message) {
        logger.log(message); // 明确的方法调用
    }
}

思想总结

Go的组合哲学是"通过组合实现代码复用,而不是继承"。这种设计带来了:

  • 灵活性:可以组合任意类型的任意方法
  • 解耦:类型之间没有强制的层次关系
  • 安全性:没有脆弱的基类问题
  • 简洁性:语法简洁,自动委托

Java虽然支持继承,但现代Java开发也越来越倾向于"组合优于继承"。从Go到Java,需要:

  • 接受更多的显式代码
  • 使用设计模式(装饰器、代理、策略等)实现类似功能
  • 利用Java 8+的接口默认方法
  • 使用Lombok等工具减少样板代码

记住:Go是行为决定了类型,Java是类型决定了行为。理解这个核心差异,就能在两种语言间自如切换。

Go转Java的目录结构对比

标准项目布局

Go社区有一个被广泛接受的标准项目布局,虽然不是强制性的,但很多项目遵循。

my-go-project/
├── cmd/                    # 应用程序入口目录
│   ├── app1/              # 每个可执行文件有自己的目录
│   │   ├── main.go        # main函数
│   │   └── config.yaml    # 该应用的配置
│   └── app2/
│       └── main.go
├── internal/               # 私有应用程序代码库
│   ├── pkg1/              # 项目内部包,外部项目不能导入
│   │   ├── service.go
│   │   └── service_test.go
│   └── pkg2/
│       ├── repository.go
│       └── repository_test.go
├── pkg/                    # 公共库代码
│   ├── lib1/              # 可以被外部项目导入
│   │   ├── lib.go
│   │   └── lib_test.go
│   └── lib2/
│       ├── util.go
│       └── util_test.go
├── api/                    # API定义
│   ├── protobuf/          # Protocol Buffer定义
│   │   └── user.proto
│   └── openapi/
│       └── swagger.yaml
├── web/                    # Web静态资源
│   ├── static/
│   │   ├── css/
│   │   └── js/
│   └── templates/
│       └── index.html
├── configs/                # 配置文件模板或默认配置
│   ├── config.yaml.example
│   └── config.dev.yaml
├── scripts/                # 构建、安装、分析等脚本
│   ├── build.sh
│   ├── install.sh
│   └── release.sh
├── test/                   # 额外的外部测试和测试数据
│   ├── integration/
│   └── testdata/
├── deployments/            # 部署配置
│   ├── docker/
│   │   ├── Dockerfile
│   │   └── docker-compose.yaml
│   └── kubernetes/
│       ├── deployment.yaml
│       └── service.yaml
├── docs/                   # 文档
│   ├── design.md
│   ├── api.md
│   └── README.md
├── vendor/                 # 依赖的副本(可选)
├── go.mod                  # 模块定义
├── go.sum                  # 模块校验和
├── .gitignore
├── LICENSE
└── README.md

Java标准布局(maven规范)

my-java-project/
├── src/
│   ├── main/                     # 主源代码
│   │   ├── java/                # Java源代码
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── myapp/
│   │   │               ├── Application.java
│   │   │               ├── config/           # 配置类
│   │   │               ├── controller/       # 控制器层
│   │   │               ├── service/          # 服务层
│   │   │               │   ├── impl/         # 服务实现
│   │   │               │   └── UserService.java
│   │   │               ├── repository/       # 数据访问层
│   │   │               │   ├── entity/       # JPA实体类
│   │   │               │   ├── dao/         # DAO接口
│   │   │               │   └── mapper/      # MyBatis Mapper
│   │   │               ├── model/           # 数据传输对象
│   │   │               │   ├── dto/         # 请求/响应对象
│   │   │               │   └── vo/          # 视图对象
│   │   │               ├── exception/       # 异常类
│   │   │               └── util/            # 工具类
│   │   │
│   │   ├── resources/           # 资源文件
│   │   │   ├── application.yml  # 主配置文件(启动端口、servlet配置之类的)
│   │   │   ├── application-dev.yml
│   │   │   ├── application-prod.yml
│   │   │   ├── logback-spring.xml
│   │   │   ├── mapper/         # MyBatis Mapper XML
│   │   │   ├── static/         # 静态资源
│   │   │   │   ├── css/
│   │   │   │   ├── js/
│   │   │   │   └── images/
│   │   │   └── templates/      # 模板文件
│   │   │       └── thymeleaf/
│   │   │
│   │   └── webapp/             # Web应用资源
│   │       ├── WEB-INF/
│   │       └── index.jsp
│   │
│   └── test/                   # 测试代码
│       ├── java/
│       │   └── com/example/myapp/
│       │       ├── ApplicationTests.java
│       │       ├── controller/
│       │       ├── service/
│       │       └── repository/
│       └── resources/          # 测试资源
│           ├── application-test.yml
│           └── test-data.sql
├── target/                    # 构建输出目录
│   ├── classes/
│   ├── test-classes/
│   ├── generated-sources/
│   ├── my-app.jar
│   └── surefire-reports/
├── pom.xml                    # Maven配置文件
├── mvnw                       # Maven包装器
├── mvnw.cmd
├── .mvn/                      # Maven配置
│   └── wrapper/
│       └── maven-wrapper.properties
├── .gitignore
├── LICENSE
└── README.md

微服务架构对比

Go微服务典型布局

microservices-go/
├── api-gateway/              # API网关
│   ├── cmd/
│   ├── internal/
│   └── go.mod
├── user-service/             # 用户服务
│   ├── cmd/
│   ├── internal/
│   │   ├── handler/
│   │   ├── service/
│   │   └── repository/
│   ├── pkg/
│   └── go.mod
├── order-service/            # 订单服务
│   ├── cmd/
│   ├── internal/
│   └── go.mod
├── product-service/          # 产品服务
│   ├── cmd/
│   ├── internal/
│   └── go.mod
├── pkg/                      # 共享包
│   ├── models/              # 共享模型
│   ├── utils/               # 共享工具
│   └── errors/              # 共享错误定义
├── deployments/              # 部署配置
│   ├── docker-compose.yaml
│   └── kubernetes/
├── scripts/
├── .gitignore
├── README.md
└── Makefile                  # 统一构建脚本

Java微服务典型布局(Spring Boot)

microservices-java/
├── discovery-server/         # 服务发现
│   ├── src/
│   └── pom.xml
├── api-gateway/              # API网关
│   ├── src/
│   └── pom.xml
├── user-service/
│   ├── src/main/java/com/example/user/
│   │   ├── UserApplication.java
│   │   ├── controller/
│   │   ├── service/
│   │   ├── repository/
│   │   ├── model/
│   │   └── config/
│   ├── src/main/resources/
│   ├── Dockerfile
│   └── pom.xml
├── order-service/
│   ├── src/
│   └── pom.xml
├── product-service/
│   ├── src/
│   └── pom.xml
├── common/                   # 公共模块
│   ├── src/main/java/com/example/common/
│   │   ├── dto/
│   │   ├── exception/
│   │   └── utils/
│   └── pom.xml
├── config-server/            # 配置中心
│   ├── src/
│   └── pom.xml
├── deployments/
│   └── kubernetes/
├── .gitignore
├── README.md
└── pom.xml                   # 父POM,子模块可以继承配置

包/命名空间对比

Go的包导入路径

// 基于域名的导入路径
import (
    "github.com/username/project/pkg/math"
    "github.com/username/project/internal/database"
    "mycompany.com/shared/utils"
)

// 包名与目录名相关,一目录一包命名
// 目录: /home/user/project/pkg/math
// 包声明: package math

特点:

  • 包名简短,通常小写单数名词
  • 导入路径反映代码仓库位置
  • 不需要与目录结构完全对应

Java的包命名规范

// 反向域名 + 项目结构
package com.example.myapp.domain.user;

// 或分层结构
package com.example.myapp.controller;
package com.example.myapp.service;
package com.example.myapp.repository.entity;
package com.example.myapp.model.dto;

特点:

  • 完全限定名,避免冲突
  • 通常反映公司域名和项目结构
  • 包名严格对应物理目录结构

构建和依赖管理对比

Go的构建和依赖管理

// Go: go.mod 文件
module github.com/mycompany/myapp

go 1.19  // 指定Go版本

require (
    github.com/gin-gonic/gin v1.8.1
    github.com/go-sql-driver/mysql v1.6.0
)

// 特点:
// 1. 语言版本在go.mod中指定
// 2. 依赖版本直接写在require中
// 3. 没有类似Maven的"属性变量"概念
// 4. 版本管理更简单直接

Go构建命令

go build ./cmd/app      # 编译
go test ./...           # 测试所有包
go mod tidy             # 整理依赖
go mod vendor           # 创建vendor目录

Java的构建管理

<!-- Maven: pom.xml -->
<project>
    <!-- pom.xml的版本号,表明pom文件个字段含义的,不同于maven和项目的版本号 -->
    <modelVersion>4.0.0</modelVersion>
    
    <!-- 项目的基本信息坐标 -->
    <groupId>com.example</groupId>
    <artifactId>my-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <!-- 继承父pom.xml-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <!-- 不写relativePath时,会默认使用../pom.xml作为父pom.xml -->
        <!-- 如下,为(空值)时,Maven 会跳过本地文件系统查找,直接从配置的远程仓库中下载父 POM -->
        <relativePath/>
    </parent>

    <!-- properties 是 Maven 的"变量声明"区 -->
    <properties>
        <!-- 这里定义的都是"键值对",可以在整个pom.xml和代码中引用 -->
        <java.version>11</java.version>  <!-- 键: java.version, 值: 11 -->
    </properties>

    <dependencies>
        <!-- Jar包依赖,包含Spring Boot的核心包依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    <build>
        <!-- 插件配置, 配置和管理mvn命令行的构建过程-->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>  <!-- 引用上面properties的属性 -->
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

构建命令(根据pom.xml中的build.plugins插件配置):

# 编译项目
mvn compile
# 运行应用,开发调试
mvn spring-boot:run
# 打包成可执行JAR
mvn package
# 清理构建
mvn clean
# 清理并重新下载依赖
mvn clean install

也有可以利用插件使用外部配置文件来配置maven构建参数,如version.properties或者application.properties。

<!-- 外部属性加载方式 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>properties-maven-plugin</artifactId>
            <version>1.1.0</version>
            <executions>
                <execution>
                    <phase>initialize</phase>
                    <goals>
                        <goal>read-project-properties</goal>
                    </goals>
                    <configuration>
                        <files>
                            <file>versions.properties</file>
                        </files>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

在后文的spring boot框架使用中,也会说明到使用外部独立properties或者yml文件来配置业务属性。

依赖管理总结

Go中内置了依赖管理机制,从GOPATH -> vendor到现在的GO modules机制, 而java当下典型的解决方案是需要用到Maven/Gradle工具的,并且Go.mod中没有类似Maven(properties)、Gradle(ext)的"属性变量"概念,一般是使用环境变量+Makefile变量的结合来解决同类场景,另外Maven属性查找是有优先级顺序的:

  • 命令行参数 (-Dproperty=value) - 最高优先级
  • settings.xml 中的
  • 当前pom.xml 中的
  • 父POM的
  • Java系统属性
  • 操作系统环境变量 (${env.VAR})
  • Maven内置属性 - 最低优先级

典型框架使用对比

上文提到的Maven作为Java项目的构建工具,提供了强大的依赖管理能力,但依赖间的版本兼容性和配置细节仍需开发者操心。Spring Boot通过整合大量通用功能组件(以Bean形式,注解@启用)和提供自动化配置,极大地简化了项目搭建过程,结合IDE的脚手架生成能力,让开发者能更专注于业务开发。

相比之下,Go语言生态虽采用了不同的设计哲学,标准库功能强大,社区鼓励组合小而美的第三方库,但也使得开发者需要在 ORM、日志、认证等中间件通用组件方面进行更多自主选型和集成工作。虽然Go也有一些全功能框架(如 Goframe、Kratos),但尚未形成如 Spring 那样具有广泛影响力和企业级标准共识的生态系统。

web框架对比

Go的Gin框架示例

// main.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()  // 显式创建路由
    
    // 中间件:显式添加
    r.Use(Logger())  // 全局中间件
    
    // 路由定义:简单直接
    r.GET("/users", getUsers)
    r.POST("/users", createUser)
    r.GET("/users/:id", getUser)
    
    auth := r.Group("/api", AuthMiddleware())  // 分组中间件
    
    // 启动:一行代码
    r.Run(":8080")
}

// 处理器:普通函数
func getUser(c *gin.Context) {
    id := c.Param("id")  // 手动获取参数,也有Bind()方法,需手动创建Go结构体接收
    // 处理逻辑...
    users := []User{{ID: id, Name: "Alice"}}
    c.JSON(200, gin.H{"data": users})
}

Java的Spring Boot示例:

spring boot的应用,优先找src目录,应用入口默认在***Application.java**中。

@RestController // 默认返回JSON
@RequestMapping("/api/users") // 路由前缀
public class UserController {
    
    @Autowired  // 自动注入
    private UserService userService;
    
    // 通过注解声明路由
    @GetMapping
    public ResponseEntity<List<User>> getUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        return ResponseEntity.status(201)
            .body(userService.createUser(user));
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) { // 自动绑定参数
        return ResponseEntity.ok(userService.getUserById(id));
    }
}

// 主类 (Spring Boot 项目通常有一个名为 *Application 的入口类,
// 入口类里有一个main方法, 这个main方法其实就是一个标准的Java应用的入口方法)
// 最核心的启动类注解,下文细讲
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);  // 自动配置
    }
}

@SpringBootApplication注解,充分体现了java生态中的约定优于配置,其本质上是一系列的注解组合,包括了:

  • @SpringBootConfiguration → 类似Go的main()函数,告诉框架:"这是我的应用入口,从这里开始初始化"
  • @ComponentScan → 类似Go的init()函数 + 依赖注入,自动扫描注册项目中的组件(Controller、Service等),就像Go中通过import和init()自动注册各种处理器
  • @EnableAutoConfiguration → 类似Go的go build自动适配,根据classpath中的依赖,自动配置需要的Bean,比如:检测到MySQL驱动,自动配置DataSource,这就像Go的构建系统自动选择合适的平台实现一样

web框架设计方面,Go体现了开发者需要显式控制每个细节,框架轻量透明,而Java则体现了通过约定和自动配置减少开发者负担。

特性Go (Gin/Echo)Java (Spring Boot)
路由定义方法链式调用注解声明
路由匹配精确匹配,也支持通配符自动匹配,支持通配符
参数获取显式调用 c.Param()注解自动绑定 @PathVariable
分组路由显式创建 Group类级别 @RequestMapping
性能极快,前缀树进行路由匹配启动时构建,运行时匹配

ORM框架对比

Go的GORM是SQL友好,显式控制。

// GORM: 链式调用,类似SQL
type User struct {
    gorm.Model
    Name  string
    Email string `gorm:"uniqueIndex"`
    Age   int
}

// 查询
var user User
db.Where("age > ?", 18).
   Where("name LIKE ?", "%张%").
   Order("created_at desc").
   First(&user)

// 关联查询 - 需要显式Preload
db.Preload("Orders").Preload("Profile").Find(&users)

// 原生SQL支持
db.Raw("SELECT * FROM users WHERE age > ?", 20).Scan(&users)

// 手动事务
tx := db.Begin()
if err := tx.Create(&user).Error; err != nil {
    tx.Rollback()
    return err
}
tx.Commit()

Java的Spring Data JPA是面向对象,声明式的。

// JPA实体
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @Column(unique = true)
    private String email;
    
    private Integer age;
    
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Order> orders;  // 自动关联
    
    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    private Profile profile;
}

// Repository接口 - 声明式查询
public interface UserRepository extends JpaRepository<User, Long> {
    
    // 方法名自动生成查询
    List<User> findByAgeGreaterThan(int age);
    
    List<User> findByNameContaining(String name);
    
    // 自定义查询
    @Query("SELECT u FROM User u WHERE u.age > :age ORDER BY u.createdAt DESC")
    List<User> findAdults(@Param("age") int age);
    
    // 分页查询
    Page<User> findAll(Pageable pageable);
}

// 事务管理 - 声明式
@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User createUser(User user) {
        return userRepository.save(user);  // 自动事务
    }
}

查询方式对比

特性Go (GORM)Java (JPA/Hibernate)
查询构建链式方法调用方法名推导、JPQL、Criteria
关联加载显式 Preload()自动或懒加载 FetchType
N+1问题需手动避免可配置 @BatchSizeJOIN FETCH
缓存无内置一级/二级缓存
乐观锁手动实现@Version 自动管理
审计结构体标签@CreatedDate 等注解

注:N+1问题是数据库查询中一个经典的性能陷阱 1次查询(获取所有文章) N次查询(每篇文章都要单独查一次评论) 总共:1 + N 次数据库查询

依赖注入对比

Go的依赖注入模式是显式声明,通过函数参数传递。

// 1. 手动依赖注入(最常用)
type UserService struct {
    repo UserRepository
    cache Cache
    logger *log.Logger
}

func NewUserService(repo UserRepository, cache Cache, logger *log.Logger) *UserService {
    return &UserService{repo: repo, cache: cache, logger: logger}
}

// 2. 选项模式(Builder模式)
type UserService struct {
    repo UserRepository
    cache Cache
    logger *log.Logger
}

type Option func(*UserService)

func WithCache(cache Cache) Option {
    return func(s *UserService) { s.cache = cache }
}

func NewUserService(repo UserRepository, opts ...Option) *UserService {
    s := &UserService{repo: repo}
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// 使用
service := NewUserService(repo, WithCache(redisCache))

Java的依赖注入模式是隐式声明,通过注解自动注入。

// 1. 构造器注入(Spring推荐)
@Service
public class UserService {
    
    private final UserRepository userRepository;
    private final Cache cache;
    private final Logger logger;
    
    @Autowired
    public UserService(UserRepository userRepository, 
                      Cache cache, 
                      Logger logger) {
        this.userRepository = userRepository;
        this.cache = cache;
        this.logger = logger;
    }
}

// 2. Setter注入
@Service
public class UserService {
    
    private UserRepository userRepository;
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

依赖注入部分的对比,可以看出来,Go的理念是"代码应该清晰表达意图,不依赖魔法",Java生态的理念则是"框架处理基础设施,开发者专注业务逻辑"。

配置管理对比

// 使用Viper, Go生态中最流行的配置管理库
import "github.com/spf13/viper"

func LoadConfig() (*Config, error) {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    
    // 默认值
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.timeout", 30)
    
    // 环境变量支持
    viper.AutomaticEnv()
    viper.SetEnvPrefix("APP")
    viper.BindEnv("server.port", "APP_SERVER_PORT")
    
    // 配置文件
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); ok {
            log.Println("使用默认配置")
        } else {
            return nil, err
        }
    }
    
    var config Config
    if err := viper.Unmarshal(&config); err != nil {
        return nil, err
    }
    
    return &config, nil
}

// 结构体映射
type Config struct {
    Server   ServerConfig   `mapstructure:"server"`
    Database DatabaseConfig `mapstructure:"database"`
    Redis    RedisConfig    `mapstructure:"redis"`
}

type ServerConfig struct {
    Port    int    `mapstructure:"port"`
    Timeout int    `mapstructure:"timeout"`
    Env     string `mapstructure:"env"`
}

Java的配置

// 1. application.yml
server:
  port: 8080
  timeout: 30s

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/db
    username: root
    password: secret
  redis:
    host: localhost
    port: 6379

app:
  name: myapp
  version: 1.0.0

// 2. @ConfigurationProperties
@Configuration
@ConfigurationProperties(prefix = "app")
@Data
public class AppProperties {
    private String name;
    private String version;
    private List<String> features = new ArrayList<>();
}

// 3. @Value注解
@Component
public class MyComponent {
    
    @Value("${server.port}")
    private int port;
    
    @Value("${app.name:defaultApp}")  // 默认值
    private String appName;
    
    @Value("#{'${app.features}'.split(',')}")
    private List<String> features;
}

总计起来,Go (Viper):你需要告诉它每一步做什么,它精确执行,Java (Spring Boot):你告诉它想要什么,它自动完成所有细节。

任务调度

Go的定时任务可以使用原生的time.Ticktime.After函数实现。

// 1. 原生goroutine + ticker
func scheduleTask() {
    ticker := time.NewTicker(1 * time.Hour)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            doTask()
        case <-stopChan:
            return
        }
    }
}

// 2. 使用cron库
import "github.com/robfig/cron/v3"

func main() {
    c := cron.New()
    
    // 添加任务
    c.AddFunc("0 0 * * *", cleanUpTask)       // 每天午夜
    c.AddFunc("*/5 * * * *", heartbeatTask)   // 每5分钟
    c.AddFunc("@hourly", hourlyTask)          // 每小时
    c.AddFunc("@every 1h30m", complexTask)    // 每1.5小时
    
    c.Start()
    defer c.Stop()
    
    // 等待
    select {}
}

// 3. 分布式任务调度 (Machinery)
import "github.com/RichardKnop/machinery/v1"
import "github.com/RichardKnop/machinery/v1/config"

func main() {
    // 配置
    cnf := &config.Config{
        Broker:        "redis://localhost:6379",
        DefaultQueue:  "machinery_tasks",
        ResultBackend: "redis://localhost:6379",
    }
    
    // 创建服务器
    server, err := machinery.NewServer(cnf)
    if err != nil {
        panic(err)
    }
    
    // 注册任务
    server.RegisterTask("add", Add)
    server.RegisterTask("multiply", Multiply)
    
    // 启动worker
    worker := server.NewWorker("worker1", 10)
    go worker.Launch()
    
    // 发送任务
    signature := &tasks.Signature{
        Name: "add",
        Args: []tasks.Arg{
            {Type: "int64", Value: 1},
            {Type: "int64", Value: 2},
        },
    }
    
    asyncResult, err := server.SendTask(signature)
    if err != nil {
        panic(err)
    }
    
    result, err := asyncResult.Get(time.Duration(time.Millisecond * 5))
    if err != nil {
        panic(err)
    }
}

Java的定时任务可以使用Spring的@Scheduled注解实现。

// 1. Spring Scheduled
@Component
public class ScheduledTasks {
    
    // 固定延迟
    @Scheduled(fixedDelay = 5000)
    public void taskWithFixedDelay() {
        // 任务完成5秒后再次执行
    }
    
    // 固定速率
    @Scheduled(fixedRate = 5000)
    public void taskWithFixedRate() {
        // 每5秒执行一次
    }
    
    // Cron表达式
    @Scheduled(cron = "0 0 9-17 * * MON-FRI")
    public void officeHoursTask() {
        // 工作日9点到17点每小时执行
    }
    
    // 初始延迟
    @Scheduled(initialDelay = 1000, fixedRate = 5000)
    public void taskWithInitialDelay() {
        // 启动后延迟1秒,然后每5秒执行
    }
}

// 2. Quartz集成
@Configuration
public class QuartzConfig {
    
    @Bean
    public JobDetail jobDetail() {
        return JobBuilder.newJob(MyJob.class)
                .withIdentity("myJob", "group1")
                .storeDurably()
                .build();
    }
    
    @Bean
    public Trigger trigger() {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail())
                .withIdentity("myTrigger", "group1")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
                .build();
    }
}

@Component
public class MyJob implements Job {
    
    @Override
    public void execute(JobExecutionContext context) {
        // 执行任务
    }
}

// 3. 分布式任务 (XXL-Job/Elastic-Job)
@XxlJob("demoJobHandler")
public ReturnT<String> demoJobHandler(String param) {
    // 分布式任务
    return ReturnT.SUCCESS;
}

核心差异一句话总结的话,Go生态强调的是“你构建调度器,精确控制每个细节”,Java则更强调的是“你声明任务,框架负责调度执行”。

单元测试、API文档、可观测、异常处理等等都是常见的开发需求,Go和Java都有对应的解决方案,此处不表。

总结

通过对Go和Java在设计思想、工程目录结构、典型框架使用三个维度的深入对比,我们可以提炼出两种语言生态在工程化理念上的本质差异:

核心差异的哲学本质

Go是"显式控制"的设计哲学:语言设计强调开发者对每个细节的精确把控,框架轻量透明,代码即文档。通过组合、接口隐式实现、命名即契约等机制,在保持简洁的同时提供足够的灵活性。这种设计让Go特别适合构建高性能、高可靠性的基础设施和微服务。

Java是"约定优于配置"的工程哲学:通过注解、AOP、自动配置等机制,将复杂的基础设施问题抽象为简单的开发体验。Spring Boot为代表的框架让开发者能够专注于业务逻辑,框架负责处理底层复杂性。这种设计让Java在构建大型企业级应用时具有显著优势。

思维转换的关键点

从Go转向Java,开发者需要完成以下几个关键思维转换:

  • 从隐式到显式:Go中"命名即契约"的隐式规则(如首字母大小写决定可见性)在Java中需要显式的修饰符和注解来表达。接受Java的样板代码,理解"显式优于隐式"的设计原则。
  • 从组合到分层:Go的组合哲学在Java中更多体现为分层架构(Controller-Service-Repository)和设计模式的运用。理解Java的OOP本质,学会用接口、抽象类和设计模式构建可维护的系统。
  • 从手动控制到自动配置:Go中需要手动管理依赖注入、配置加载、路由定义等,而Java(尤其是Spring Boot)通过约定和自动配置大幅简化这些工作。学会信任框架,理解"不要重复造轮子"的工程智慧。
  • 从轻量级到全功能:Go标准库功能强大但相对基础,需要开发者自行选型和集成第三方库;Java生态提供了完整的解决方案栈。理解Java生态的复杂性,学会在丰富的工具链中选择合适的组件。

实用建议

未来趋势

两种语言的生态正在相互借鉴和融合:

  • Go生态正在成熟:随着Go 1.18+泛型的引入,以及像Kratos、GoFrame等全功能框架的兴起,Go在大型应用开发方面的能力不断增强,但仍保持"显式控制"的核心哲学。
  • Java生态正在轻量化:Spring Boot 3.x、Quarkus、Micronaut等框架通过GraalVM原生编译、快速启动等特性,让Java应用更轻量、更云原生,同时保留其企业级能力。
  • 多语言架构成为常态:在现代云原生架构中,Go和Java往往共存于同一技术栈:Go负责基础设施、CLI工具、高性能服务;Java负责核心业务逻辑、复杂事务处理。理解两种语言的优劣势,能够在正确的场景选择正确的工具。

语言只是工具,工程化才是本质。无论是Go的"显式控制"还是Java的"约定优于配置",最终目标都是构建高质量、可维护、可演进的软件系统。

2 - AI时代下,Java该如何起手项目开发

结合人工智能在软件工程领域大行其道的当下,该如何快速开启Java项目的设计开发

30多年来,开发者工具层面终于出现了革命者,本文主要结合人工智能在软件工程领域大行其道的当下,针对从零开始和迭代已有项目,介绍如何快速开启Java项目的设计开发。

最近常听到一句话,AI不会替代你,但熟练使用AI的人可以轻松干废你。 初听有点儿耸人听闻,实际却在发生中.... 甚至斯坦福大学最近也开设了CS146S课程, 要求必须使用AI完成开发任务,作业是提交完成开发任务的提示词prompt。专门教授如何将大模型和AI工具(如编程智能体、AI IDE)集成到软件开发流程中,从而大幅提升开发者的生产力,并探讨了现代软件工程师的角色演变。课程大纲如下:

工欲善其事必先利其器

传统开发,必备神器IDEA

下图是2025.3版本,安装包大小1G,包括java生态的大量工具插件,还内置了教学引导,默认给30天的免费旗舰试用期。 启动界面 教学导引

导引学习,主要介绍一些快捷键(包括any搜索、补全建议、重构)和调试技巧等。

  • Ctrl+Shift+A或者两次Shift按键,触发anything search。
  • Alt + Enter, 万能修复/提示
  • Ctrl + Shift + Space​,智能代码补全
  • Ctrl + Alt + L​,代码格式化

传统IDE中,补全是当初能让其迅速普及开的功能之一,而调试也是另一个极好的功能。在现代AI IDE中,调试似乎也不需要了,直接贴错误日志,就自动开始debug纠错了。

使用AI开发的现状

当下主流的使用AI开发模式有3种基本形态:

  • 一种是传统IDE + 编程插件助手,代表有Lingma、Continue等,是属于渐进式拥抱AI辅助开发,从而提高生产力的方式。

  • 另一种是基于开源IDE打造的AI原生IDE, 提供更沉浸式的AI编程体验,由AI自主完成分析、设计、编码乃至测试的完整流程,一般需要订阅收费。

    • 国外:Cursor(基于VS Code)
    • 国内:Trae.CN(字节)、CodeBuddy(腾讯),Qoder(阿里.新加坡团队)
  • 还有一种终端工具,代表是Claude Code、Codex CLI等,可以灵活集成到以上两种模式和其他第三方CI/CD协作流水线中

现在市场参与者,基本都在布局各形态的开发场景,本文发稿时,通义lingma也在公测IDE了,CodeBuddy已实现插件、IDE、CLI三端全形态覆盖。

年初我个人也结合开源vscode+roocode插件,开发过一款自己的IDE,叫aide,基本功能也都具备,后面有时间可以整理发出来一篇手把手打造自己的AI原生IDE。

不过github Copilot在国内市场才是比较可惜的,背靠微软(VS生态),手里有github,竟然在市场上,没有国外初创企业Cursor等的声势浩大。

这几种形态,反映了一个演进方向: 将AI集成到开发工具 ====> 将开发工具集成到AI中。人在其中的动手参与度逐步降低,后者看上去是最理想的终极形态,合理设置后,可以让AI把整个软件交付的全流程(需求分析->设计->编码->测试->部署上线)自动完成。

不过这里不应该存在谁更好的说法,最终要看你的团队运作模式,是单人全栈还是严格‘瀑布模式’下的团队模式,毕竟康威定律还在生效(技术问题的背后,往往是人的问题和组织问题)。这类AI原生IDE现在也基本支持个人版和企业版了,不过从业内的实践来看,在单人全栈模式下,AI原生IDE的优势会更明显。

由于本人工作行业限制,接触最多的是lingma,不过为了客观对比更高阶的形态,这里以Trae.CN为例

安装包大小204M,内置前端工具链,如涉及其他技术栈,需要自行安装插件。 支持两种模式(IDE和solo模式),就是前面提到的IDE+插件助手模式和AI原生IDE的形态。

启动界面 solo模式

AI原生IDE,相对传统IDE,最大的区别是在软件工程中的参与阶段左移了,从需求分析开始就可以介入了,并且以AI为主导推理,自动调度集成MCP工具来推进工作。

Vibe Coding 氛围/AI结对编程

零开项目(避免重复造轮子)

java生态已经非常丰富,几乎每个领域都有对应成熟、经过验证的开源项目,遵循专业的事情找专业的工具干的原则(寻找轮子),避免让AI从零开始重复造轮子。

鉴于LLM的工作原理,预测Token,让AI从零开始重复造轮子,这是血亏的实践结论。。。应该利用已有成熟库,喂文档,进入迭代阶段。 当前最佳SOTA实践是Claude的Skills攻略,此处不介绍,如涉及复杂项目可考虑(个人还没试过)。

推荐先要根据项目需求场景,直接驱动builder使用成熟脚手架或者传统IDE初始化一个项目基础架构。整理提示模版如下:

我需要实现 [集成类系统的权限管理],请帮我:
1. 分析这个需求可能涉及哪些技术领域,有没有成熟的库/项目已经做过类似的事?
2. 确定使用Java技术栈,并使用mvn初始化合适的脚手架

需要提前安装配置好JDK和Maven环境,此处不表。

如果公司有对应的研发规范,推荐使用rules规则,将提示模版作为rules规则,让AI根据规则进行架构设计选型。

迭代存量项目

打开已有项目的根目录,会自动判断项目技术栈,并推荐安装相应的插件。 推荐安装插件

IDE模式下,内置了4中智能体

大多数情况下,builder就可以完成开发任务,要熟悉存量项目架构设计的话可以使用chat模型。chat模式下,可以询问代码算法细节,项目依赖用法等。

建议先在buidler模式下,自动生成团队规范。提示词如下

生成该项目的一个团队规范,存放于.trae\rules\project_rules.md文件中

一定要确认自动生成规范是否有误并合理。

迭代项目时,让AI根据规则进行架构设计选型和代码编写。 未完...后面单独开章分享。

总结

要想真正在AI原生IDE中上手生产级系统开发,还是要掌握一些基础的编程技能,如数据结构、算法、设计模式、软件工程等等。

本文开头提到的CS146S课程,也是要求学生必须具备扎实的编程经验,才能报名的。不然,一是不知道如何让AI更好的替我们工作产出(prompt该如何写),二是不知道AI的产出是否存在隐患,很快就会进入AI“屎山”代码中,是你真的会看不懂一小时前的某段代码逻辑意图... 至少当下,会因为AI的易得性反而更容易欠下技术债务。软件还是要讲究可维护性的,不过话又说回来,不需要后续维护的,讲究快速原型的场景,确实是极好的。

AI协同并不简单,让开发人员最讨厌的文档编写,直接上升到了第一公民,变成面向文档编程了。不过这个领域还在快速发展中,但人与AI协同开发的共识,个人比较认可的原则总结如下:

  • 人来负责“规划”,AI 负责“执行”
  • 绝不提交自己不理解的代码
  • 避免重复造轮子
  • 小步迭代,持续清理

参考资料:

3 - Goper直接撸java代码,真是操碎了心,tips总结①

Java Tips总结

Java发展的这些年,为了复杂大型项目的工程实践能傻瓜落地,真是封了一层又一层啊,C/C++型选手转过来,会感觉各种看不透,总结了一些tips,尽量追根问底,分享给大家。

@SpringBootApplication 注解

java spring项目,第一个看到的基本就是这个注解,只能加到main方法所在的主类上,标识这是一个Spring Boot应用程序的main入口类。

@(注解)的本质是给代码打标签,告诉需要处理注解的工具或框架: 这里的代码需要特殊处理。

“@SpringBootApplication”注解,是个组合注解,是表示要在程序启动运行阶段被特殊处理的(其实现完全依赖于Java的反射机制),是spring框架的核心机制,依靠Spring容器的运行时处理能力,体现了「约定优于配置」的设计思想。

没点儿Java经验,「约定优于配置」的设计思想,会让学习成本增加,但是在大型项目中,会让开发效率大大提高。 spring 容器的运行时处理能力,是实现「约定优于配置」的关键。

Spring 容器

spring 容器是一个IoC容器(简单理解为一个对象工厂),用于管理应用程序中的Bean对象。

Bean的生命周期包括:实例化、依赖注入、初始化、使用、销毁。 依赖注入是指在运行时,通过容器自动将Bean的依赖对象注入到Bean中。 初始化是指在Bean实例化后,调用Bean的初始化方法。 使用是指在Bean初始化后,通过容器获取Bean实例,调用其方法。 销毁是指在应用程序关闭时,调用Bean的销毁方法。

简单来说:

  • 你写好代码,贴上标签(注解)
  • 容器帮你管理这些代码(Bean)
  • 你需要什么,容器就给你什么(依赖注入)

为什么要使用 Spring 容器?

理由有一堆,还都是高大上的那种....

拿用户注册功能对比,举例说明,对比「不用Spring容器」和「用Spring容器」的代码差异,直观展示Spring容器的价值:

场景:用户注册功能(需要3层结构)

  1. Controller :接收注册请求
  2. Service :处理注册业务逻辑(密码加密、保存用户)
  3. Repository :操作数据库(保存用户信息)

方式1:不用Spring容器 → 传统Java开发

// 1. Repository层:操作数据库
public class UserRepository {
    public void save(User user) {
        System.out.println("保存用户到数据库:" + user.getName());
    }
}

// 2. Service层:业务逻辑
public class UserService {
    // 必须手动创建依赖对象 → 「自己new」,初始化省略..
    private UserRepository userRepository = new UserRepository();
    
    public void registerUser(User user) {
        // 密码加密(业务逻辑)
        user.setPassword(encryptPassword(user.getPassword()));
        // 调用Repository保存
        userRepository.save(user);
    }
    
    private String encryptPassword(String password) {
        return "加密后的密码:" + password;
    }
}

// 3. Controller层:接收请求
public class UserController {
    // 必须手动创建依赖对象 → 「自己new」
    private UserService userService = new UserService();
    
    // 可以改进为接收参数
    public void handleRegisterRequest(String name, String password) {
        User user = new User();
        user.setName(name);
        user.setPassword(password);
        userService.registerUser(user);
    }
}

// 4. 启动类:手动创建所有对象
public class App {
    public static void main(String[] args) {
        // 必须手动创建所有层的对象 → 「自己管理依赖」
        UserController controller = new UserController();
        // 调用注册接口
        controller.handleRegisterRequest("张三", "123456");
    }
}

有点儿偏面向过程的开发思路,需要自己管理各层依赖对象的创建初始化 :

  • 必须手动 new 所有对象,代码冗余
  • 依赖关系硬编码(UserService直接依赖UserRepository的具体实现)
  • 修改依赖时有可能需要修改所有相关代码(比如把UserRepository换成Redis实现)
  • 无法集中管理对象生命周期

方式2:用Spring容器 → 依赖注入开发

// 1. Repository层:用@Repository标记,告诉容器「UserRepository类是要管理的Bean对象」
@Repository
public class UserRepository {
    public void save(User user) {
        System.out.println("保存用户到数据库:" + user.getName());
    }
}

// 2. Service层:用@Service标记,@Autowired让容器自动注入依赖
@Service
public class UserService {
    // 容器自动注入UserRepository → 「不用自己new」,配置已初始化
    @Autowired
    private UserRepository userRepository;
    
    public void registerUser(User user) {
        // 专注于业务逻辑(密码加密)
        user.setPassword(encryptPassword(user.getPassword()));
        userRepository.save(user);
    }
    
    private String encryptPassword(String password) {
        return "加密后的密码:" + password;
    }
}

// 3. Controller层:用@RestController标记,@Autowired自动注入Service
@RestController
public class UserController {
    // 容器自动注入UserService → 「自动解决依赖」
    @Autowired
    private UserService userService;
    
    @PostMapping("/register")
    public String registerUser(@RequestBody User user) {
        // 专注于处理请求,不用关心Service怎么来的
        userService.registerUser(user);
        return "注册成功";
    }
}

// 4. 启动类:用@SpringBootApplication标记,容器自动创建所有对象
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        // 一行代码启动 → 容器自动处理所有对象创建和依赖
        SpringApplication.run(App.class, args);
    }
}

具体价值体现 1. 价值1:不用自己new对象

  1. 价值1:不用自己new对象
  • 传统方式 :每个类都要手动 new 依赖对象,并自己管理参数初始化
  • Spring方式 :只需要用 @Service / @Repository 标记,容器自动创建,并配合其他注解可自动配置参数。
  • 例子 : UserService 不用写 new UserRepository() ,容器会自动创建并注入
  1. 价值2:自动解决依赖
  • 传统方式 :依赖关系硬编码,修改时要改所有相关代码
  • Spring方式 : @Autowired 自动找到并注入(嵌入)合适的对象
  • 例子 :如果把 UserRepository 换成Redis实现,只需要给Redis实现类加 @Repository ,不需要修改 UserService 代码
  1. 价值3:专注业务逻辑
  • 传统方式 :花大量时间管理对象创建和依赖
  • Spring方式 :只需要关心业务逻辑(如密码加密、用户验证)
  • 例子 : UserService 只需要写 registerUser 的业务代码,不用关心 UserRepository 怎么来的

spring 启动过程

回到开头提到的spring应用中常遇到的第一个注解,会出现在主类上,“@SpringBootApplication”注解,标识这是一个Spring Boot应用程序的main入口类。

“@SpringBootApplication”注解,包含了以下3个注解:

  1. @SpringBootConfiguration: 标识这是应用的主配置类(入口),是@Configuration的派生注解,提供了Spring Boot特定的配置语义,确保配置类能被正确识别和处理,优先级说明:默认配置 → 自动配置 → 外部配置(application.yml) → 手动配置(@Configuration)。

  2. @EnableAutoConfiguration: 启用Spring Boot的自动配置机制。

  • 自动扫描 classpath 下所有 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件(Spring Boot 2.7+)
  • 根据pom中的配置的依赖包、条件配置类等信息,自动配置相应的Bean(比如数据源、Web MVC配置等)。
  • 另外会看application.yml配置文件中的属性,覆盖默认配置。举例:修改Tomcat默认端口为9090。
# application.yml
server:
  port: 9090  # 覆盖默认的 8080 端口

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/yudao
    username: root
    password: 123456
  • Profile多环境支持
    • 可以根据不同的环境(如开发、测试、生产),加载不同的配置文件(如application-dev.yml、application-test.yml、application-prod.yml)。
    • 可以通过spring.profiles.active属性指定当前激活的环境,默认是default环境。
  1. @ComponentScan: 开启组件扫描,Spring Boot会自动扫描主类所在的包及其子包,重点找到使用了@Controller、@Service、@Repository等注解的类,并将这些Bean对象注册到Spring容器中。
    • 支持通过basePackages参数设置,指定要扫描的包名,控制扫描范围,灵活组织应用的功能。

经过以上3个隐含注解的协同处理,程序启动完成,会进入运行状态,等待接收外部请求。

示例:

/**
 * Spring Boot应用程序的主启动类
 * 
 * @SpringBootApplication 是一个组合注解,包含以下三个核心功能:
 * 1. @SpringBootConfiguration: 标识这是一个Spring Boot应用的主配置类
 * 2. @EnableAutoConfiguration: 启用Spring Boot的自动配置机制
 * 3. @ComponentScan: 自动扫描当前包及其子包下的组件(如@Controller、@Service、@Repository等)
 * 
 * 这个注解告诉Spring Boot框架:这是一个Spring Boot应用程序的入口点
 */
@SpringBootApplication
public class ServerApplication {
    
    /**
     * 应用程序的主入口方法
     * 
     * 当JVM启动时,会首先执行这个main方法
     * 该方法负责启动整个Spring Boot应用程序
     * 
     * @param args 命令行参数,可以在启动时传入配置参数
     *             例如:--server.port=8081 或 --spring.profiles.active=dev
     */
    public static void main(String[] args) {
        /**
         * SpringApplication.run() 方法执行以下关键操作:
         * 1. 创建Spring应用上下文(ApplicationContext)
         * 2. 启用自动配置(根据classpath中的依赖自动配置Bean)
         * 3. 启动内嵌的Web服务器(如Tomcat、Jetty等)
         * 4. 扫描并注册所有使用Spring注解的组件
         * 5. 启动完成后,应用程序进入运行状态,等待HTTP请求
         * 
         * 参数说明:
         * - ServerApplication.class: 主配置类,Spring Boot会从这个类所在的包开始扫描组件
         * - args: 命令行参数,用于覆盖默认配置
         * 
         * 返回值:ConfigurableApplicationContext对象,代表整个Spring应用上下文
         * 可以用于获取Bean、关闭应用等操作
         */
        SpringApplication.run(ServerApplication.class, args);
    }
}

这个过程体现了spring boot生态的核心价值 - 配置自动化,开发人员可以通过pom引入下依赖包(内部包也一样),然后直接改改yml配置,就可以轻松写出生产级的代码了。

SpringBootApplication注解工作的机制中,还有不少细节,属于用到了可以现查资料的知识,此处先不展开。 spring框架还有很多AOP、事务管理等其他优势,此处先不展开。

lombok

解决Java样板代码的利器,通过**@注解**的方式,在编译期自动生成对应的代码,减少了样板代码的编写,提高了开发效率。

解决了哪些样板代码的编写? 比如:getter/setter、toString、equals、hashCode等。这些方法在大多数Java类中都需要,但是编写逻辑高度重复啰嗦,通过lombok注解,就可以自动生成这些代码,减少了开发人员的机械工作量。举例:

@Data // @Data组合注解,自动为该类生成getter/setter/toString/equals/hashCode等方法,节省了约50行左右的代码量。
// 相当于以下注解组合
// @Getter、@Setter、@ToString、@EqualsAndHashCode等
public class User {
    private Long id;
    private String name;
    private Integer age;
}

为什么每个Java类都需要这些方法 ?

因为这些方法是Java类的基础(所有类都直接或间接的继承了Object类),没有它们,大多数Java类就不能正常工作。

// 所有类都继承 Object
public class User {  // 隐式继承 Object
    // 自动拥有 Object 的方法:toString(), equals(), hashCode() 等
}

比如:

  • 没有getter/setter方法,就不能通过对象的属性来访问和修改属性值,Java封装思想的具体实现。
  • 没有toString方法,就不能通过System.out.println()方法来打印对象的信息。
  • 没有 equals 方法,虽然仍然可以使用equals方法(继承自Object),只是比较的是引用相等性,不能用于比较对象内容是否相等。
  • 没有 hashCode 方法,就不能将对象存储在HashSet、HashMap等集合中。
Map<User, String> map = new HashMap<>();
User user = new User();
map.put(user, "value");  // 需要拥有正确的 hashCode() 和 equals()

Set<User> set = new HashSet<>();
set.add(user);  // 同样需要正确的 hashCode() 和 equals()

确保了 Java 中所有对象都具有统一的基本行为,也是哈希集合(如 HashMap、HashSet)等功能的基础。