模版是一组预定义的代码,其中的内容在触发时可以根据上下文自动推断填充。在 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.Logger
和org.slf4j.LoggerFactory
依赖并初始化Logger
${USER}
获取的是当前用户的登录名,登录名与真实姓名或版本控制系统用户名不一致时应当使用常量。
工作项目中经常使用 Mybatis Plus 作为 ORM,而我的习惯是先设计数据结构,再编写逻辑,最后考虑落入数据库,框架搭配的代码生成工具就没什么用武之地了。而且与数据库的交互多见于简单的增删改查,于是 IService
和 ServiceImpl
也统统省略成了 Repository
。
Files 分类的模版支持自己添加新的类型。例如你可以添加一个叫做 POJO
的模版,在里面写上 Lombok 和 Mybatis 的各类注解,然后在新建文件时选择它。
如法炮制可以再创建名为 Mapper
,Repository
和 Controller
的模版。
不过你好像并没有办法去调整它们在菜单中呈现的顺序,为了少动点鼠标,你也可以选择编辑 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 代码中,可以快速生成 Logger
、ObjectMapper
的初始化语句,以及其他一些样板代码,不同作用域 Abbreviation
不会冲突。
也可以为 POJO 类添加 Lombok 注解:
你也可以创建自己的 Template Group,例如一个 Spring Boot Configuration
分组匹配 YAML 文件,当用户输入 datasource-mysql
时添加一组测试用 MySQL 数据库的连接信息。