SpringBoot Lombok使用详解:从入门到精通(注解最全)
liuian 2025-07-14 18:27 4 浏览
一、Lombok概述与基础使用
1.1 Lombok是什么
Lombok是一个Java库,它通过注解的方式自动生成Java代码(如getter、setter、toString等),从而减少样板代码的编写,提高开发效率。根据我的项目经验,Lombok可以显著减少约30%-50%的JavaBean代码量。
Lombok的核心原理是在编译时通过注解处理器(Annotation Processor)动态修改抽象语法树(AST),生成对应的字节码。这意味着使用Lombok不会增加运行时负担,因为它只在编译阶段工作。
1.2 Lombok的优势与劣势
下表对比了使用Lombok与传统Java开发的差异:
对比维度 | 传统Java开发 | 使用Lombok开发 |
代码量 | 需要手动编写大量样板代码 | 自动生成,代码简洁 |
可读性 | 业务逻辑被样板代码淹没 | 聚焦业务逻辑,更清晰 |
维护性 | 修改字段需同步修改多个方法 | 修改字段自动同步 |
学习曲线 | 无需额外学习 | 需要学习注解含义 |
团队协作 | 无需特殊配置 | 需要统一开发环境配置 |
调试难度 | 直接查看完整代码 | 需IDE插件支持查看生成代码 |
第三方依赖 | 无 | 需要引入Lombok依赖 |
1.3 环境准备与基础配置
1.3.1 引入Lombok依赖
在Spring Boot项目中引入Lombok非常简单,只需在pom.xml中添加以下依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version> <!-- 使用最新稳定版本 -->
<scope>provided</scope>
</dependency>
1.3.2 IDE插件安装
由于Lombok在编译时生成代码,为了让IDE能正确识别这些生成的代码,需要安装对应的插件:
- IntelliJ IDEA: 通过File -> Settings -> Plugins搜索"Lombok Plugin"安装
- Eclipse: 下载lombok.jar并双击运行安装
1.3.3 基础注解使用
让我们从一个最简单的JavaBean开始,展示Lombok如何简化代码:
传统JavaBean写法:
public class User {
private Long id;
private String username;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
// 其他getter/setter...
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
// 冗长的equals实现...
}
@Override
public int hashCode() {
// hashCode实现...
}
}
使用Lombok后的写法:
import lombok.Data;
@Data
public class User {
private Long id;
private String username;
private String password;
}
这个简单的例子展示了Lombok的强大之处 - 一个@Data注解就替代了数十行代码。@Data是一个复合注解,它包含了以下功能:
- 所有字段的getter(final字段除外)
- 所有非final字段的setter
- toString()方法
- equals()和hashCode()方法
- 必要的构造函数
二、Lombok核心注解详解
2.1 常用基础注解
2.1.1 @Getter/@Setter
@Getter和@Setter是最基础的注解,用于生成getter和setter方法。
public class User {
@Getter @Setter private Long id;
@Getter(
value = AccessLevel.PROTECTED // 设置访问级别为protected
)
@Setter(
value = AccessLevel.PRIVATE, // 设置访问级别为private
onMethod_ = @Deprecated // 在生成的方法上添加@Deprecated注解
)
private String password;
}
生成的代码相当于:
public class User {
private Long id;
private String password;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
protected String getPassword() { return password; }
@Deprecated
private void setPassword(String password) { this.password = password; }
}
2.1.2 @ToString
@ToString注解自动生成toString()方法,默认包含所有非静态字段。
@ToString(
exclude = {"password"}, // 排除password字段
includeFieldNames = false, // 不包含字段名
callSuper = true // 包含父类的toString结果
)
public class User extends BaseEntity {
private Long id;
private String username;
private String password;
}
生成的toString()方法输出类似:
User(super=BaseEntity(createdAt=2023-01-01), id=1, username=admin)
2.1.3 @EqualsAndHashCode
@EqualsAndHashCode生成equals()和hashCode()方法实现。
@EqualsAndHashCode(
exclude = {"createdAt"}, // 排除createdAt字段
callSuper = true // 包含父类的equals和hashCode
)
public class User extends BaseEntity {
private Long id;
private String username;
private Date createdAt;
}
2.1.4 @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
Lombok提供了三种构造器生成方式:
@NoArgsConstructor // 无参构造器
@RequiredArgsConstructor // 为所有final或@NonNull字段生成构造器
@AllArgsConstructor // 全参构造器
public class User {
private Long id;
@NonNull private String username;
private final String role = "USER";
}
2.2 实用注解
2.2.1 @Data
@Data是一个复合注解,相当于:
- @Getter
- @Setter
- @ToString
- @EqualsAndHashCode
- @RequiredArgsConstructor
@Data
public class Product {
private Long id;
private String name;
private BigDecimal price;
}
2.2.2 @Builder
@Builder实现了建造者模式,提供了一种更优雅的对象创建方式。
@Builder
public class Order {
private Long id;
private String orderNo;
private BigDecimal amount;
}
// 使用方式
Order order = Order.builder()
.id(1L)
.orderNo("20230101123456")
.amount(new BigDecimal("100.00"))
.build();
2.2.3 @Slf4j
@Slf4j自动生成日志对象,避免了手动创建Logger的繁琐。
@Slf4j
public class OrderService {
public void createOrder() {
log.info("Creating order...");
try {
// 业务逻辑
log.debug("Order created successfully");
} catch (Exception e) {
log.error("Failed to create order", e);
}
}
}
2.3 高级注解
2.3.1 @Value
@Value是不变类(immutable class)的快捷方式,相当于:
- final字段
- 只有getter
- 全参构造器
- toString/equals/hashCode
@Value
public class ImmutableUser {
Long id;
String username;
String role;
}
2.3.2 @SneakyThrows
@SneakyThrows可以偷偷抛出受检异常而不在方法签名中声明。
public class FileUtil {
@SneakyThrows
public static String readFile(String path) {
return Files.readString(Paths.get(path));
}
}
2.3.3 @Cleanup
@Cleanup自动管理资源,确保资源使用后被正确关闭。
public class FileUtil {
@SneakyThrows
public static void copyFile(String src, String dest) {
@Cleanup InputStream in = new FileInputStream(src);
@Cleanup OutputStream out = new FileOutputStream(dest);
in.transferTo(out);
}
}
三、Lombok在Spring Boot中的实战应用
3.1 实体类(Entity)开发
在Spring Data JPA中,实体类通常需要大量样板代码,Lombok可以极大简化这一过程。
@Entity
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String name;
@Column(precision = 10, scale = 2)
private BigDecimal price;
@Column(updatable = false)
private LocalDateTime createdAt;
@Version
private Integer version;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}
3.2 DTO/VO对象开发
数据传输对象(DTO)和值对象(VO)通常只包含数据和简单逻辑,非常适合使用Lombok。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
private Long id;
private String username;
private String email;
private String phone;
@Data
public static class Address {
private String province;
private String city;
private String detail;
}
}
3.3 控制器(Controller)开发
在Controller中,我们经常需要记录日志,@Slf4j可以简化这一过程。
@RestController
@RequestMapping("/api/users")
@Slf4j
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
log.debug("Fetching user with id: {}", id);
return ResponseEntity.ok(userService.getUserById(id));
}
@PostMapping
public ResponseEntity<Void> createUser(@Valid @RequestBody UserDTO userDTO) {
log.info("Creating new user: {}", userDTO.getUsername());
userService.createUser(userDTO);
return ResponseEntity.created(URI.create("/api/users")).build();
}
}
3.4 服务层(Service)开发
服务层通常需要事务管理和日志记录,Lombok也能提供帮助。
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final ProductRepository productRepository;
@Override
public OrderDTO createOrder(OrderCreateDTO createDTO) {
log.info("Creating order for user: {}", createDTO.getUserId());
Product product = productRepository.findById(createDTO.getProductId())
.orElseThrow(() -> {
log.error("Product not found: {}", createDTO.getProductId());
return new ResourceNotFoundException("Product not found");
});
Order order = Order.builder()
.userId(createDTO.getUserId())
.productId(product.getId())
.amount(product.getPrice())
.status(OrderStatus.CREATED)
.build();
return convertToDTO(orderRepository.save(order));
}
private OrderDTO convertToDTO(Order order) {
// 转换逻辑
}
}
四、Lombok高级特性与最佳实践
4.1 自定义Lombok配置
在项目根目录下创建lombok.config文件,可以自定义Lombok行为:
# 将生成的构造器设为protected
lombok.anyConstructor.suppressConstructorProperties = true
# 禁止使用@EqualsAndHashCode的callSuper=true警告
config.stopBubbling = true
lombok.equalsAndHashCode.callSuper = warn
# 全局配置@ToString不包含字段名
lombok.toString.includeFieldNames = false
4.2 与MapStruct集成
MapStruct是一个代码生成器,用于Java Bean之间的映射,与Lombok配合使用效果极佳。
@Mapper(componentModel = "spring")
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "username", target = "name")
UserDTO toDTO(User user);
@Mapping(source = "name", target = "username")
User toEntity(UserDTO userDTO);
}
@Data
public class User {
private Long id;
private String username;
private String password;
}
@Data
public class UserDTO {
private Long id;
private String name;
private String email;
}
4.3 与Jackson集成
Lombok与Jackson JSON库可以很好地配合使用,但需要注意一些细节。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL) // 只包含非null字段
public class ApiResponse<T> {
private Integer code;
private String message;
private T data;
@JsonIgnore // 忽略这个getter
public boolean isSuccess() {
return code == 200;
}
}
4.4 常见问题与解决方案
4.4.1 与JPA的兼容性问题
当使用JPA的懒加载(Lazy Loading)时,直接调用toString()可能导致异常。解决方案:
@Entity
@Data
@ToString(exclude = {"orders"}) // 排除关联集合
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders;
}
4.4.2 与继承体系的问题
在继承体系中,正确配置callSuper非常重要:
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AdminUser extends User {
private String privilege;
}
4.4.3 性能考虑
虽然Lombok在编译时生成代码,但某些复杂注解(如@Builder)生成的代码量较大,在性能敏感的场景需要权衡使用。
五、Lombok原理深入解析
5.1 Lombok工作原理
Lombok的工作流程可以用以下序列图表示:
Java CompilerLombok Annotation ProcessorIDEDeveloperJava CompilerLombok Annotation ProcessorIDEDeveloper编写带有Lombok注解的Java文件请求解析AST修改AST返回修改后的AST提供增强的代码视图显示增强后的代码效果编译代码再次处理AST生成最终字节码
5.2 注解处理流程
Lombok的注解处理发生在编译期的以下阶段:
- 解析阶段:Java编译器解析源代码生成AST
- 注解处理阶段:Lombok的注解处理器修改AST
- 分析生成阶段:基于修改后的AST生成字节码
5.3 与IDE的集成原理
IDE通过Lombok插件实现以下功能:
- 在编辑器中显示生成的代码
- 提供代码补全建议
- 支持导航到"虚拟"的生成方法
六、Lombok替代方案与对比
6.1 手动编写代码
优点:
- 完全控制代码细节
- 无需额外依赖
- 更好的可调试性
缺点:
- 代码冗长
- 维护成本高
- 容易出错
6.2 IDE代码生成
优点:
- 可视化的生成过程
- 可以选择性生成
缺点:
- 生成的代码仍需保留在源文件中
- 字段变更时需要重新生成
- 不同IDE生成风格可能不一致
6.3 其他代码生成工具
工具 | 特点 | 适用场景 |
Lombok | 注解驱动,编译时生成,无运行时依赖 | 日常JavaBean开发 |
AutoValue | 专注于生成不可变类 | 需要不可变对象的场景 |
Immutables | 类似AutoValue但功能更丰富 | 复杂不可变对象需求 |
Kotlin | 语言层面支持数据类(data class) | 使用Kotlin的项目 |
Record类(Java14+) | Java语言内置的简化数据载体定义 | Java 14+的简单数据传输对象 |
七、Lombok在企业项目中的实践建议
7.1 项目规范制定
- 注解使用规范:
- 实体类:使用@Data+@Builder+@NoArgsConstructor+@AllArgsConstructor
- DTO:使用@Value或@Data+@Builder
- 工具类:使用@UtilityClass
- 代码风格统一:
- 统一配置lombok.config
- 规定toString的格式
- 统一equals/hashCode的实现方式
7.2 团队协作注意事项
- 新成员培训:
- 基础注解的含义
- IDE插件的安装配置
- 常见问题的解决方法
- 代码审查要点:
- 检查是否过度使用Lombok
- 验证callSuper的正确设置
- 确认排除字段的合理性
7.3 性能优化建议
- 避免在性能关键路径使用复杂注解:
- @ToString在大对象上可能影响性能
- @EqualsAndHashCode在大型集合比较时注意
- 合理选择注解组合:
- 只使用需要的注解,而不是简单的@Data
- 考虑使用@Value替代@Data创建不可变对象
八、完整实战案例
8.1 电商系统用户模块
// 实体类
@Entity
@Table(name = "users")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = {"password", "orders"})
@EqualsAndHashCode(callSuper = true, exclude = {"orders"})
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
@Column(name = "phone_number", length = 20)
private String phoneNumber;
@Enumerated(EnumType.STRING)
private UserStatus status;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Order> orders = new ArrayList<>();
@Embedded
private Address address;
@Version
private Integer version;
}
// DTO类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserDTO {
private Long id;
private String username;
private String email;
private String phoneNumber;
private UserStatus status;
private AddressDTO address;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class AddressDTO {
private String province;
private String city;
private String street;
private String zipCode;
}
}
// 服务类
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserMapper userMapper;
@Override
public UserDTO getUserById(Long id) {
log.debug("Fetching user with id: {}", id);
return userRepository.findById(id)
.map(userMapper::toDTO)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
}
@Override
@Transactional
public UserDTO createUser(UserCreateDTO createDTO) {
log.info("Creating new user: {}", createDTO.getUsername());
if (userRepository.existsByUsername(createDTO.getUsername())) {
throw new BusinessException("Username already exists");
}
User user = User.builder()
.username(createDTO.getUsername())
.password(passwordEncoder.encode(createDTO.getPassword()))
.email(createDTO.getEmail())
.phoneNumber(createDTO.getPhoneNumber())
.status(UserStatus.ACTIVE)
.address(convertAddress(createDTO.getAddress()))
.build();
return userMapper.toDTO(userRepository.save(user));
}
private Address convertAddress(UserDTO.AddressDTO addressDTO) {
// 转换逻辑
}
}
8.2 RESTful API响应封装
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResult<T> {
private long timestamp;
private String status;
private String code;
private String message;
private T data;
private String path;
public static <T> ApiResult<T> success() {
return success(null);
}
public static <T> ApiResult<T> success(T data) {
return ApiResult.<T>builder()
.timestamp(System.currentTimeMillis())
.status("success")
.code("200")
.data(data)
.build();
}
public static ApiResult<?> error(String code, String message) {
return error(code, message, null);
}
public static ApiResult<?> error(String code, String message, String path) {
return ApiResult.builder()
.timestamp(System.currentTimeMillis())
.status("error")
.code(code)
.message(message)
.path(path)
.build();
}
}
// 使用示例
@RestController
@RequestMapping("/api/users")
@Slf4j
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/{id}")
public ApiResult<UserDTO> getUser(@PathVariable Long id) {
return ApiResult.success(userService.getUserById(id));
}
@ExceptionHandler(BusinessException.class)
public ApiResult<?> handleBusinessException(BusinessException e,
HttpServletRequest request) {
log.error("Business exception: {}", e.getMessage(), e);
return ApiResult.error("400", e.getMessage(),
request.getRequestURI());
}
}
九、Lombok的未来与发展
9.1 Java Record类与Lombok
Java 14引入的Record类在某些场景下可以替代Lombok:
// 使用Record
public record UserRecord(Long id, String username, String email) {}
// 使用Lombok
@Data
@AllArgsConstructor
public class UserLombok {
private Long id;
private String username;
private String email;
}
比较:
- Record更简洁,但功能有限(不可变,有限的自定义能力)
- Lombok更灵活,支持可变对象和各种自定义
9.2 Lombok的未来发展方向
- 更好的Java新版本兼容性
- 更智能的代码生成策略
- 增强与其他工具链的集成
- 可能的元注解支持
9.3 何时选择/不选择Lombok
适合使用Lombok的场景:
- 大量的POJO/DTO/VO类
- 需要快速原型开发
- 团队统一规范并接受Lombok
不适合使用Lombok的场景:
- 对字节码有严格要求的场景
- 需要精细控制生成的代码
- 项目成员不熟悉Lombok且学习成本高
十、总结与个人建议
经过多年的Java开发和Lombok使用经验,我认为:
- 适度使用:Lombok是很好的生产力工具,但不要滥用。在简单DTO/实体类上使用,在复杂业务类上谨慎使用。
- 团队共识:确保团队成员都理解并接受Lombok,统一配置和风格。
- 了解原理:理解Lombok的工作原理有助于解决遇到的问题。
- 结合其他工具:与MapStruct、Jackson等工具配合使用效果更佳。
- 关注替代方案:随着Java语言发展(如Record类),评估是否可以用语言特性替代Lombok。
Lombok不是银弹,但在正确使用的场景下,它能显著提高开发效率,减少样板代码,让我们更专注于业务逻辑的实现。希望这篇全面的指南能帮助你在Spring Boot项目中更好地利用Lombok。
关注我?别别别,我怕你笑出腹肌找我赔钱。
头条对markdown的文章显示不太友好,想了解更多的可以关注微信公众号:“Eric的技术杂货库”,有更多的干货以及资料下载。
相关推荐
- Optional是个好东西,如果用错了就太可惜了
-
原文出处:https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be我们都知道,在Java8新增了一个类-Optional,主要是用来解决程...
- IDEA建议:不要在字段上使用@Autowire了!
-
在使用IDEA写Spring相关的项目的时候,在字段上使用@Autowired注解时,总是会有一个波浪线提示:Fieldinjectionisnotrecommended.纳尼?我天天用,咋...
- Spring源码|Spring实例Bean的方法
-
Spring实例Bean的方法,在AbstractAutowireCapableBeanFactory中的protectedBeanWrappercreateBeanInstance(String...
- Spring技巧:深入研究Java 14和SpringBoot
-
在本期文章中,我们将介绍Java14中的新特性及其在构建基于SpringBoot的应用程序中的应用。开始,我们需要使用Java的最新版本,也是最棒的版本,Java14,它现在还没有发布。预计将于2...
- Java开发200+个学习知识路线-史上最全(框架篇)
-
1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...
- 年末将至,Java 开发者必须了解的 15 个Java 顶级开源项目
-
专注于Java领域优质技术,欢迎关注作者:SnailClimbStar的数量统计于2019-12-29。1.JavaGuideGuide哥大三开始维护的,目前算是纯Java类型项目中Sta...
- 字节跨平台框架 Lynx 开源:一个 Web 开发者的原生体验
-
最近各大厂都在开源自己的跨平台框架,前脚腾讯刚宣布计划四月开源基于Kotlin的跨平台框架「Kuikly」,后脚字节跳动旧开源了他们的跨平台框架「Lynx」,如果说Kuikly是一个面向...
- 我要狠狠的反驳“公司禁止使用Lombok”的观点
-
经常在其它各个地方在说公司禁止使用Lombok,我一直不明白为什么不让用,今天看到一篇文章列举了一下“缺点”,这里我只想狠狠地反驳,看到列举的理由我竟无言以对。原文如下:下面,结合我自己使用Lomb...
- SpringBoot Lombok使用详解:从入门到精通(注解最全)
-
一、Lombok概述与基础使用1.1Lombok是什么Lombok是一个Java库,它通过注解的方式自动生成Java代码(如getter、setter、toString等),从而减少样板代码的编写,...
- Java 8之后的那些新特性(六):记录类 Record Class
-
Java是一门面向对象的语言,而对于面向对象的语言中,一个众所周知的概念就是,对象是包含属性与行为的。比如HR系统中都会有雇员的概念,那雇员会有姓名,ID身份,性别等,这些我们称之为属性;而雇员同时肯...
- 为什么大厂要求安卓开发者掌握Kotlin和Jetpack?优雅草卓伊凡
-
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡一、Kotlin:Android开发的现代语言选择1.1Kotlin是什么?Kotlin是由...
- Kotlin这5招太绝了!码农秒变优雅艺术家!
-
Kotlin因其简洁性、空安全性和与Java的无缝互操作性而备受喜爱。虽然许多开发者熟悉协程、扩展函数和数据类等特性,但还有一些鲜为人知的特性可以让你的代码从仅仅能用变得真正优雅且异常简洁。让我们来看...
- 自行部署一款免费高颜值的IT资产管理系统-咖啡壶chemex
-
在运维时,ICT资产太多怎么办,还是用excel表格来管理?效率太低,也不好多人使用。在几个IT资产管理系统中选择比较中,最终在Snipe-IT和chemex间选择了chemex咖啡壶。Snip...
- PHP对接百度语音识别技术(php对接百度语音识别技术实验报告)
-
引言在目前的各种应用场景中,语音识别技术已经越来越常用,并且其应用场景正在不断扩大。百度提供的语音识别服务允许用户通过简单的接口调用,将语音内容转换为文本。本文将通过PHP语言集成百度的语音识别服务,...
- 知识付费系统功能全解析(知识付费项目怎么样)
-
开发知识付费系统需包含核心功能模块,确保内容变现、用户体验及运营管理需求。以下是完整功能架构:一、用户端功能注册登录:手机号/邮箱注册,第三方登录(微信、QQ)内容浏览:分类展示课程、文章、音频等付费...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
python使用fitz模块提取pdf中的图片
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)