Code Generation
Coco code generation (coco-feature-codegen) is a template-based CRUD scaffolding capability. It takes the description of "a business resource" (package name, resource name, data table, fields) and renders it into maintainable, plain source code through FreeMarker templates, without registering any runtime dynamic CRUD behavior. It is a development-time capability, and the generated output consists of source files owned by the business project itself. The module binds the coco.codegen namespace, is enabled by default, participates in auto-configuration as a Coco Feature (CocoFeature.CODEGEN), and loads after the MyBatis-Plus auto-configuration (the repository and Mapper generated by the built-in CRUD templates depend on MyBatis-Plus).
Feature overview
CocoCodeGenerator: the code generator SPI. It takes aCocoCodegenRequest(template group, target package name, extension context) and returns aCocoCodegenResult(the set of generated files held in memory). The default implementation isFreeMarkerCocoCodeGenerator.CocoCrudSpec: the default CRUD generation spec. It describes a single business resource and, before entering the templates, normalizes and validates the package name, resource name, table name, fields, and Java types, then converts them into a request for the built-incrudtemplate group viatoRequest().FreeMarkerCocoCodeGenerator: the template engine implementation. It reads the template resources and output paths declared in each template group's<group>/manifest.propertiesfrom the template root directory, and returns only the in-memory file results, never implicitly creating directories or writing to disk.
How to enable and integrate
The module is enabled by default. Once enabled, the framework registers two beans: CocoCodeGenerator (the default FreeMarkerCocoCodeGenerator, which reads coco.codegen.templates.location and encoding) and CocoGeneratedFileWriter (used when explicitly writing to disk). Just inject the generator:
@Component
public class CrudScaffolder {
private final CocoCodeGenerator codeGenerator;
private final CocoGeneratedFileWriter fileWriter;
public CrudScaffolder(CocoCodeGenerator codeGenerator, CocoGeneratedFileWriter fileWriter) {
this.codeGenerator = codeGenerator;
this.fileWriter = fileWriter;
}
}
Usage example
Describe a resource with CocoCrudSpec, convert it into a request, and hand it to the generator; the generated result is a set of in-memory files, and whether to write them to disk is decided explicitly by the caller:
CocoCrudSpec spec = CocoCrudSpec.builder("com.example.order", "Order", "t_order")
.id("id", "id", Long.class, CocoCrudIdStrategy.AUTO)
.field("orderNo", "order_no", String.class, true)
.field("amount", "amount", java.math.BigDecimal.class, true)
.field("remark", "remark", String.class, false)
.apiPath("/orders") // when omitted, derived from the resource name, e.g. Order -> /orders
.build();
CocoCodegenResult result = codeGenerator.generate(spec.toRequest());
for (CocoGeneratedFile file : result.files()) {
System.out.println(file.path()); // relative output path
// call fileWriter to write to the target directory only when persistence is needed
}
CocoCrudSpec performs extensive safety validation during the build phase: package names and field names must be valid Java identifiers and must not be keywords; table names and column names must match safe SQL identifiers; apiPath must be an absolute path composed of safe segments; the primary key type may not be a primitive type; field names and column names must not be duplicated; and the resource name and field types must not conflict with the template's built-in generated types (such as Controller, Mapper, Service, etc.). These checks guarantee that the rendered source is compilable and free of injection risks.
Template mechanism
FreeMarkerCocoCodeGenerator organizes templates by template group. Each template group has a <group>/manifest.properties under the template root directory, declaring the number of templates and the source file and output path for each template:
group=crud
template.count=2
template.0.source=Entity.java.ftl
template.0.output=${basePackagePath}/entity/${resourceName}Entity.java
template.1.source=Controller.java.ftl
template.1.output=${basePackagePath}/web/${resourceName}Controller.java
- Template paths go through normalization checks that reject traversal attempts such as absolute paths, drive-letter prefixes, and
./..segments; for template reads under afile:/ plain-path root directory, the target path is additionally checked to ensure it does not escape the template root. - The output path is itself a FreeMarker expression, normalized after rendering by
CocoGeneratedPathValidator.normalizeRelativePath; producing duplicate output paths within the same template group raises an error. - The template model's reserved fields
_coco,templateGroup, andtargetPackagemay not be overridden by request attributes. - FreeMarker is configured in strict mode (
RETHROW_HANDLER,localizedLookupdisabled, empty loop-variable fallback forbidden), and template errors throwCocoCodegenExceptiondirectly.
The business side can point coco.codegen.templates.location at its own template root directory to replace or extend the built-in crud template group. The template location supports three prefixes: classpath:, file:, and plain file paths.
Key configuration properties
Bound under the prefix coco.codegen (corresponding to CocoCodegenProperties):
| Property | Type | Default | Description |
|---|---|---|---|
coco.codegen.enabled | boolean | true | Whether to enable the code generation infrastructure |
coco.codegen.templates.location | String | classpath:/coco/codegen/templates | The FreeMarker template root location; supports classpath: / file: / plain paths |
coco.codegen.templates.encoding | String | UTF-8 | The template file encoding |
Boundaries and caveats
- This is a development-time capability: it generates plain source code the business project can keep maintaining, does not register dynamic CRUD behavior at runtime, and does not read database metadata.
- The repository and Mapper generated by the built-in
crudtemplates depend on MyBatis-Plus, so the auto-configuration is declared afterCocoMybatisPlusAutoConfiguration; projects using the built-in templates need MyBatis-Plus available. - The generator only computes files and does not implicitly write to disk. Whether to persist and to which directory is controlled explicitly by the caller through
CocoGeneratedFileWriter, avoiding overwriting existing source code. - The strict validation in
CocoCrudSpecmeans non-compliant package names, table names, and field names throw exceptions right at build time, rather than producing code that will not compile.