2026年IT就业市场:开发者应投资哪些技能
1. 概览
IT就业市场正朝着对每位开发者提出更多要求的方向发展。仅能写出功能特性是不够的。你还需要理解这个功能在生产环境中如何运行、如何故障以及需要花费多少成本。
本文不提供薪资数据或预测。相反,本文分析了五个基础技能组,这些技能组目前受到关注,解释了原因,并通过Java 21/Spring Boot 3.x代码示例说明每个技能组。读完后,你将知道应该优先学习什么、如何立即应用到项目中,以及什么时候不应该盲目跟风。
2. 为什么技能要求在转变
有三个主要驱动力,你可以在大多数招聘信息中观察到它们:
- AI编码辅助工具已经普及。 “样板”代码生成得更快了。开发者的价值转向了设计、审查、测试和对结果的责任。
- 企业注重运维成本。 一个资源使用量较少且更容易诊断错误的服务会直接节省成本。
- 团队更小,所有权更广。 “你构建它,你运维它”模式让开发者需要了解打包、监控和事故处理。
由此,以下五个技能组成为长期高收益投资的候选。
3. 以受控的方式将AI集成到应用中
目前最常提到的技能不是“会用chatbot”。而是能够将LLM引入真实系统的同时保持稳定性、可测性和备选方案。
核心原则:将LLM视为一个不可信的外部依赖。它的输出可能格式错误、缓慢或网络错误。
3.1. 在接口后面隐藏LLM
@FunctionalInterface
public interface LlmClient {
String complete(String prompt);
}
public enum TicketCategory { BILLING, TECHNICAL, OTHER }
public class TicketClassifier {
private final LlmClient llm;
public TicketClassifier(LlmClient llm) {
this.llm = llm;
}
public TicketCategory classify(String message) {
String prompt = """
将以下支持请求分类到单一标签中:BILLING、TECHNICAL、OTHER。
只返回标签,不要解释。
请求:%s
""".formatted(message);
try {
String raw = llm.complete(prompt).trim().toUpperCase();
return TicketCategory.valueOf(raw);
} catch (RuntimeException e) {
return TicketCategory.OTHER;
}
}
}
解释:
LlmClient是你自己的接口。真实实现可以调用任何提供商的HTTP接口或使用像Spring AI这样的库。业务逻辑不依赖于特定的提供商。- Prompt要求输出是唯一的标签。格式限制帮助你解析结果。
TicketCategory.valueOf(raw)同时进行解析和验证。如果模型返回长句子,IllegalArgumentException被捕获,系统回退到OTHER。网络错误(通常是RuntimeException)也进入这个分支。
3.2. 不需要调用真实模型就进行测试
由于LlmClient是functional interface,你可以传入lambda作为fake:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class TicketClassifierTest {
@Test
void givenValidLabel_whenClassify_thenReturnCategory() {
TicketClassifier classifier = new TicketClassifier(prompt -> " billing\n");
assertThat(classifier.classify("我被扣了两次款"))
.isEqualTo(TicketCategory.BILLING);
}
@Test
void givenUnexpectedOutput_whenClassify_thenFallbackToOther() {
TicketClassifier classifier = new TicketClassifier(prompt -> "可能是 BILLING");
assertThat(classifier.classify("我被扣了两次款"))
.isEqualTo(TicketCategory.OTHER);
}
@Test
void givenClientFailure_whenClassify_thenFallbackToOther() {
TicketClassifier classifier = new TicketClassifier(prompt -> {
throw new IllegalStateException("timeout");
});
assertThat(classifier.classify("应用崩溃了")).isEqualTo(TicketCategory.OTHER);
}
}
第一个测试展示了代码能处理空格和小写。后两个测试证明系统在模型“说太多”或连接失败时不会崩溃。这正是招聘者想看到的:你知道LLM会出错,并且为此设计了系统。
注意: 不应该用LLM做那些用规则、正则表达式或SQL就能精确且便宜地完成的工作。也避免用LLM做那些需要确定结果并可以审计的决策,比如计费或权限分配。
4. 使用虚拟线程进行现代并发
大多数后端是I/O密集型的:调用数据库、调用其他API。Java 21将虚拟线程作为正式功能推出,允许你用熟悉的阻塞式代码编写同时能服务大量并发请求。知道什么时候应该以及不应该使用它是面试中的一个明显加分项。
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class PriceAggregator {
public static void main(String[] args) throws Exception {
List<String> suppliers = List.of("SupplierA", "SupplierB", "SupplierC");
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = suppliers.stream()
.map(s -> executor.submit(() -> fetchPrice(s)))
.toList();
for (Future<String> f : futures) {
System.out.println(f.get());
}
}
System.out.println("总耗时:" + (System.currentTimeMillis() - start) + " ms");
}
static String fetchPrice(String supplier) throws InterruptedException {
Thread.sleep(Duration.ofSeconds(1)); // 模拟缓慢的API调用
return supplier + " -> 100000 VND, virtual=" + Thread.currentThread().isVirtual();
}
}
newVirtualThreadPerTaskExecutor()为每个任务创建一个虚拟线程。你不需要调整池的大小。try-with-resources块确保executor等待所有任务完成后再关闭。- 三个耗时一秒的调用并行运行,所以总时间约为一秒:
SupplierA -> 100000 VND, virtual=true
SupplierB -> 100000 VND, virtual=true
SupplierC -> 100000 VND, virtual=true
总耗时:1015 ms
在Spring Boot 3.2及更高版本,用一行配置启用虚拟线程进行请求处理:
spring.threads.virtual.enabled=true
注意: 虚拟线程不会使CPU密集型代码更快。对围绕长I/O操作的
synchronized块要小心,它们可能会将虚拟线程固定在carrier线程上。实际影响取决于JDK版本。另外,同时运行更多任务可能会耗尽数据库连接池。你仍然需要在后端层限制资源。
5. 可观测性:了解你的系统在做什么
当你负责运维时,“为什么慢?“这个问题需要用数据回答。Metrics、logs和traces是三个支柱。在Spring生态中,Micrometer和Actuator是自然的起点。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加一个自定义的业务metric:
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final Counter placedOrders;
public OrderService(MeterRegistry registry) {
this.placedOrders = Counter.builder("orders.placed")
.description("成功下单数")
.tag("channel", "web")
.register(registry);
}
public void placeOrder(String orderId) {
// ... 保存订单,调用 PaymentService ...
placedOrders.increment();
}
}
management.endpoints.web.exposure.include=health,metrics
MeterRegistry由Spring Boot自动配置并注入到bean中。- Tag
channel允许你在查看仪表板时按销售渠道分离指标。 - 配置行只暴露两个端点
health和metrics,不暴露所有端点。
经过几个订单后,调用GET /actuator/metrics/orders.placed:
{
"name": "orders.placed",
"description": "成功下单数",
"measurements": [ { "statistic": "COUNT", "value": 3.0 } ],
"availableTags": [ { "tag": "channel", "values": [ "web" ] } ]
}
注意: 不要使用数量大且无限制的值(如
orderId或customerId)作为标签。每个标签值都会创建一个新的时间序列,所以指标系统会很快膨胀。也不要把整个actuator端点暴露到互联网。
6. 使用Testcontainers用真实基础设施进行测试
当AI能快速生成代码时,测试是证明代码正确的东西。在真实数据库上运行集成测试,而不是不同方言的内存数据库,有助于捕获mock不能捕获的错误。
以下依赖由Spring Boot管理版本:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
最小的Entity和repository:
@Entity
public class Customer {
@Id @GeneratedValue
private Long id;
private String email;
protected Customer() {}
public Customer(String email) { this.email = email; }
public Long getId() { return id; }
public String getEmail() { return email; }
}
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Optional<Customer> findByEmail(String email);
}
集成测试:
@SpringBootTest
@Testcontainers
class CustomerRepositoryIT {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired
CustomerRepository repository;
@Test
void givenSavedCustomer_whenFindByEmail_thenReturnCustomer() {
repository.save(new Customer("lan@example.com"));
assertThat(repository.findByEmail("lan@example.com"))
.isPresent()
.get()
.extracting(Customer::getEmail)
.isEqualTo("lan@example.com");
}
}
@Testcontainers和@Container在测试运行前启动Docker中的真实PostgreSQL。@ServiceConnection(从Spring Boot 3.1开始提供)自动将容器的URL、用户名和密码注入到DataSource中。你不需要@DynamicPropertySource。- Query
findByEmail由Spring Data生成,在生产环境使用的完全相同的数据库类型上运行。
注意: Testcontainers需要在开发机器和CI上有Docker。测试也比单元测试慢。将大部分逻辑保留在纯单元测试中,仅对repository、messaging和migration这样的集成层使用Testcontainers。
7. 容器打包和运维
理解应用如何打包和运行是使用Kubernetes或云平台的最少技能。一个整洁的多阶段Dockerfile表明你关心镜像大小和安全性:
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
USER 1000
ENTRYPOINT ["java", "-jar", "app.jar"]
build阶段有JDK和Maven wrapper用于编译。- 最终阶段只包含JRE和jar文件,所以镜像更小,攻击面更小。
USER 1000帮助容器不以root身份运行,这是一个非常常见的错误。
你可以结合第5章的/actuator/health端点作为Kubernetes liveness/readiness probe。
注意: 如果团队已经使用Buildpacks(例如通过
./mvnw spring-boot:build-image),你不需要自己写Dockerfile。重要的是理解镜像包含什么、以哪个用户运行,以及内存限制如何影响JVM。
8. 什么时候不应该跟风
投资技能也需要保持理智:
- 不要忽视基础知识。 数据结构、SQL、HTTP、事务、错误处理仍然是每次面试都会问的东西。AI和框架变化比这些知识快得多。
- 不要学得太宽。 选择与你当前工作相关的一两项技能,应用到真实项目中,然后再扩展。一个添加metrics和集成测试的PR比十个未使用的证书更有价值。
- 不要仅为了在简历中出现而引入技术。 一个小系统、少量用户不需要Kubernetes、微服务或LLM。知道什么时候说“不需要”并给出原因也是一项受高度评价的技能。
- 不要完全交给代码生成工具。 你仍然负责。阅读、理解并为所有生成的代码编写测试。
9. 结论
IT市场奖励那些掌握功能完整生命周期的开发者:设计、编码、测试、打包和运维。本文介绍了五个值得投资的技能组:
- 在接口后面集成LLM,具有验证和回退功能。
- 对I/O密集型工作负载使用虚拟线程。
- 使用Micrometer和Actuator添加可观测性。
- 使用Testcontainers在真实基础设施上编写集成测试。
- 安全地打包容器。
每项技能都有其局限性,了解这些局限性和知道如何使用它们一样重要。本周就选择其中一项,立即应用到你正在处理的服务中。这是将趋势转化为真实经验的最快方式。