我的 Jetbrains IntelliJ IDEA 模版配置

2024-01-24, 星期三, 16:18

模版是一组预定义的代码,其中的内容在触发时可以根据上下文自动推断填充。在 AI 代码助手变得更聪明之前,我们可以利用 IDE 的模版功能快速插入样板代码,把更多的精力放在业务逻辑上。

本文再次编辑时使用的版本是 IntelliJ IDEA 2023.3.2 (Ultimate Edition) for macOS,以下内容仍然适用。

由于我未安装中文语言插件,以下菜单内容不做翻译,以便读者寻找。

File and Code Templates

通过 IntelliJ IDEA | Settings | Editor | File and Code Templates 设置。

你可以看到该功能通过选项卡分成了 4 个大类。

IDEA 会使用 Files 分类中的模版创建相应类型的文件,模版支持 Velocity 语法,参考该窗口中的 Description 部分的描述,可以利用 ${VARIABLE} 在代码中使用上下文信息。

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author ${USER}
 * @version 1.0
 * @since ${DATE}
 */
public class ${NAME} {
    private static final Logger logger = LoggerFactory.getLogger(${NAME}.class);
}

在这个简单例子中,创建 HelloController.java 文件将会:

  • 为类注释添加 @author@version@since 等 javadoc 标记
  • 导入 org.slf4j.Loggerorg.slf4j.LoggerFactory 依赖并初始化 Logger

${USER} 获取的是当前用户的登录名,登录名与真实姓名或版本控制系统用户名不一致时应当使用常量。

工作项目中经常使用 Mybatis Plus 作为 ORM,而我的习惯是先设计数据结构,再编写逻辑,最后考虑落入数据库,框架搭配的代码生成工具就没什么用武之地了。而且与数据库的交互多见于简单的增删改查,于是 IServiceServiceImpl 也统统省略成了 Repository

Files 分类的模版支持自己添加新的类型。例如你可以添加一个叫做 POJO 的模版,在里面写上 Lombok 和 Mybatis 的各类注解,然后在新建文件时选择它。

如法炮制可以再创建名为 MapperRepositoryController 的模版。

不过你好像并没有办法去调整它们在菜单中呈现的顺序,为了少动点鼠标,你也可以选择编辑 Java Class 模版,检测其所属的 package 判断正在创建什么用途的对象(另一种方法则是背快捷键,不过 Key Promoter X 已经就 Ctrl+D 的使用提醒我数百次了,所以你懂的)。

  • 如果 package 包含 entity,说明在创建 POJO 类。这时候就实现 java.io.Serializable 接口并添加对应的 Lombok(比如我最喜爱的 @Accessors(chain = true) )和 Mybatis-Plus 注解;
  • 如果 package 包含 repository,说明在创建 DAO 层的服务 Bean,这个类对应了一张特定表的 CRUD 操作,需要继承 com.baomidou.mybatisplus.extension.service.impl.ServiceImpl 类,最后再加上 SLF4J 作日志;
  • 如果 package 包含 controller,那就是写接口了;
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")

## 如果是与表对应的 POJO 类,获取实体类名称
#if (${PACKAGE_NAME.contains("entity")})
    #set($ENTITY_NAME = $NAME)
#elseif(${PACKAGE_NAME.contains("repository")})
    ## 同时需要获取实体类的包和 Mapper 的包
    #set($TMP_TRIM_INDEX = $NAME.lastIndexOf("Repository"))
    #set($ENTITY_NAME = $NAME.substring(0, $TMP_TRIM_INDEX))
    ## 不知道 IDEA 使用的 Velocity 版本,split() 方法似乎无效
    #set($TMP_TRIM_INDEX = $PACKAGE_NAME.lastIndexOf("."))
    #set($ENTITY_PACKAGE = ${PACKAGE_NAME.substring(0, $TMP_TRIM_INDEX)} + ".entity." + $ENTITY_NAME)
    #set($MAPPER_PACKAGE = ${PACKAGE_NAME.substring(0, $TMP_TRIM_INDEX)} + ".mapper." + $ENTITY_NAME + "Mapper")
#end
## import 语句
#if(${PACKAGE_NAME.contains("controller")})
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#elseif(${PACKAGE_NAME.contains("service")})
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
#elseif(${PACKAGE_NAME.contains("repository")})
import $ENTITY_PACKAGE;
import $MAPPER_PACKAGE;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
#elseif(${PACKAGE_NAME.contains("entity")})
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serial;
import java.io.Serializable;
#end

#if(${PACKAGE_NAME.contains("controller")})
@RestController
@RequestMapping("")
public class ${NAME} {
    private static final Logger logger=LoggerFactory.getLogger(${NAME}.class);
#elseif(${PACKAGE_NAME.contains("service")})
@Service
public class ${NAME} {
    private static final Logger logger=LoggerFactory.getLogger(${NAME}.class);
#elseif(${PACKAGE_NAME.contains("repository")})
@Service
public class ${NAME} extends ServiceImpl<${ENTITY_NAME}Mapper, ${ENTITY_NAME}> {
#elseif(${PACKAGE_NAME.contains("entity")})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("tbl_${NAME.toLowerCase()}")
public class ${NAME} implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
#else
public class ${NAME} {
#end
}

再编辑 Java Interface 模版处理 Mapper 就完事。

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")

#if(${PACKAGE_NAME.contains("mapper")})
    #set($TMP_TRIM_INDEX = $NAME.lastIndexOf("Mapper"))
    #set($ENTITY_NAME = $NAME.substring(0, $TMP_TRIM_INDEX))
    ## 同时需要获取实体类的包
    ## 不知道 IDEA 使用的 Velocity 版本,split() 方法似乎无效
    #set($TMP_TRIM_INDEX = $PACKAGE_NAME.lastIndexOf("."))
    #set($ENTITY_PACKAGE = ${PACKAGE_NAME.substring(0, $TMP_TRIM_INDEX)} + ".entity." + $ENTITY_NAME)
#end
## import 语句
#if(${PACKAGE_NAME.contains("mapper")})
import $ENTITY_PACKAGE;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
#end

#if(${PACKAGE_NAME.contains("mapper")})
public interface ${NAME} extends BaseMapper<${ENTITY_NAME}> {
#else
public interface ${NAME} {
#end
}

顺便一提,我非常喜欢这个枚举反推的流式写法,一并展示:

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
#parse("File Header.java")

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.stream.Stream;

@Getter
@AllArgsConstructor
public enum ${NAME} {
    ;

    private final int code;

    public static ${NAME} of(int code) {
        return Stream.of(${NAME}.values())
                .filter(p -> p.getCode() == code)
                .findFirst()
                .orElseThrow(IllegalArgumentException::new);
    }
}

看到上面模版里的 #parse("File Header.java") 了吗?如果你要处理的东西太多,可以切割出来放在 Includes 分类里,然后使用 #parse 指令读取。

Files 类的模版只能在创建文件时使用,如果想在编辑过程中为类和方法添加模版化注释或其他内容,可在 Code 分类中设置。

不过这个分类下的内容是固定的,操作的余地不大。

Live Templates

通过 IntelliJ IDEA | Settings | Editor | Live Templates 设置。

在类定义中输入 psvm 加回车,IntelliJ 会自动补充 public static void main(String[] args) {}。该功能就是通过 Live Templates 配置实现的。

在 Maven 配置中,可用于添加常用依赖、设置 Java 版本等:

Maven 条目中添加常用的依赖,在编辑 pom.xml 时快速输入:

在 Java 代码中,可以快速生成 LoggerObjectMapper 的初始化语句,以及其他一些样板代码,不同作用域 Abbreviation 不会冲突。

也可以为 POJO 类添加 Lombok 注解:

你也可以创建自己的 Template Group,例如一个 Spring Boot Configuration 分组匹配 YAML 文件,当用户输入 datasource-mysql 时添加一组测试用 MySQL 数据库的连接信息。