惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
WordPress大学
WordPress大学
小众软件
小众软件
Cloudbric
Cloudbric
AWS News Blog
AWS News Blog
腾讯CDC
量子位
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Vulnerabilities – Threatpost
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
Jina AI
Jina AI
Attack and Defense Labs
Attack and Defense Labs
S
SegmentFault 最新的问题
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
Last Week in AI
Last Week in AI
博客园 - 【当耐特】
Google Online Security Blog
Google Online Security Blog
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog
罗磊的独立博客
L
LINUX DO - 最新话题
博客园 - Franky
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
The Last Watchdog
The Last Watchdog
J
Java Code Geeks
AI
AI
C
Cisco Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cyber Attacks, Cyber Crime and Cyber Security
Cisco Talos Blog
Cisco Talos Blog
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
Help Net Security
Help Net Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG
I
Intezer
S
Securelist

Maciej Walkowiak - Java & Spring

Blog Generating HTTP clients in Spring Boot application from OpenAPI spec PostgreSQL and UUID as primary key Dynamic Projections with Spring Data JPA Container logs with Spring Boot and Testcontainers Reified Generics in Java? Faster integration tests with reusable Testcontainers and Flyway Running one-time jobs with Quartz and Spring Boot The best way to use Testcontainers with Spring Boot Spring Boot & Flyway - clear database between integration tests What's new in Spring? Activate Maven Profile by Operating System Spring Boot with Thymeleaf and Tailwind CSS - Complete Guide How to publish a Java library to Maven Central - Complete Guide Docker Compose - waiting until containers are ready Single file Java applications with JBang Beautiful bash scripts with Gum Running Java on CRaC How to log PostgreSQL queries with Testcontainers Spring Boot 3.0 & GraalVM Native Image - not a free lunch Creating Spring Cloud Function projects with AWS SAM Loading classpath resources to String with a custom JUnit extension Auto-Registering JUnit 5 extensions Spring Boot component scanning without annotations Listing Maven dependencies in Spring Boot Actuator Info endpoint Spring Cloud AWS 2.3 RC2 Released How I built vlad-cli - command line interface to Vlad Mihalcea The State of Java Relational Persistence On Choosing a Tech Stack
Creating Project Templates with Cookiecutter
2022-07-28 · via Maciej Walkowiak - Java & Spring

Can you create a new Java project from scratch, manually, without copy & pasting? I don't. And probably you shouldn't as this is a pure waste of time considering how repetitive the base code and the directory structure is.


Spring team created for this reason Spring Initializr and it's web representation start.spring.io. All the other major frameworks copied this idea thanks to which it is a no brainer anymore to create a new Spring, Quarkus, Micronaut, Axon or Vaadin project (I am sure there is more).

But not every framework, not every platform offers such tool. Creating AWS Lambda, or Google Function with Java starts with copy & pasting code from documentation or sample projects. Even Maven - the most popular build tool for Java - does not offer simple mvn init command to start a fresh, non-opinionated project from scratch. Yes, I know there are archetypes but I don't think they are very user-friendly.

There are quite a few project generator frameworks - tools to create your own project templates. The most popular one is probably Yeoman, used by JHipster. Today I came across Cookiecutter, used internally by AWS SAM CLI and decided to give it a try to create a simplified "missing mvn init" with Cookiecutter.

Cookiecutter ​

CookieCutter

Cookiecutter is a cross-platform tool to create projects from project templates, for any programming language. You can also mix different languages as all it actually cares is processing files and filtering placeholders. It is built with Python, so you must have Python installed on your system to create projects from Cookiecutter templates. You probably should also have some Python knowledge but don't stress too much about it - I have very little and managed to develop what I needed.

Package management in Python is a mess, so it's the best if you consult installation page in Cookiecutter documentation.

I've installed it on MacOS Monterey with this simple command:

code

$ pip install cookiecutter

Once Cookiecutter is installed, you can create project from local or remote templates:

code

$ cookiecutter local-template # from local directory
$ cookiecutter https://github.com/user/repo # from git repository

Cookiecutter project template ​

Project template must contain following elements:

  • empty __init__.py file
  • cookiecutter.json with a list of parameters that user provides to generate a project together with default values:

json

{
  "groupId": "com.example",
  "artifactId": "demo",
  "javaVersion": "11"
}
  • generated project directory, which name can be also filtered. In our case it will be a directory named \{\{ cookiecutter.artifactId \}\} - exactly like that, with curly brackets in the name. Cookiecutter during project generation will replace this placeholder with an actual value.

During project generation, Cookiecutter replaces all placeholders like \{\{ cookiecutter.property-name \}\} in all files in the \{\{ cookiecutter.artifactId \}\} directory with a value provided by a user.

In our case complete project structure looks like this:

.
├── __init__.py
├── cookiecutter.json
└── \{\{cookiecutter.artifactId\}\}
    ├── pom.xml
    └── src
        └── main
            └── java
                └── demo
                    └── Main.java

Where pom.xml contains:

xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>\{\{ cookiecutter.groupId \}\}</groupId>
	<artifactId>\{\{ cookiecutter.artifactId \}\}</artifactId>
	<version>1.0</version>
	<packaging>jar</packaging>
	<properties>
		<maven.compiler.source>\{\{ cookiecutter.javaVersion \}\}</maven.compiler.source>
		<maven.compiler.target>\{\{ cookiecutter.javaVersion \}\}</maven.compiler.target>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	</properties>
</project>

And Main.java:

java

package demo;

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

Generating project from the template ​

Now if we move one directory up from my-template, we can create a new project:

$ cookiecutter my-template

We will be prompted to provide values for:

groupId [com.example]: 
artifactId [demo]: 
javaVersion [11]:

and the project will be generated 🎉

But we can do better.

  • instead of generating demo Java package, the template should create a package that corresponds to the groupId and the artifactId.
  • it would be nice if generated project had Maven Wrapper installed
  • it would be nice if right after project generation, all the dependencies be installed

For that, we will use private variables and Hooks - simple and handy way to generate project a bit more dynamically.

Private Variables ​

cookiecutter.json can contain private variables that user won't be required to fill. In our case we can use it to compute the project root package based on the groupId and the artifactId:

json

{
  "groupId": "com.example",
  "artifactId": "demo",
  "javaVersion": "11",
  "__package": "\{\{ cookiecutter.groupId + '.' + cookiecutter.artifactId \}\}"
}

Cookiecutter Hooks ​

Hooks can run either before or after project is generated and can be written either with Bash or Python. These scripts get also filtered with Cookiecutter parameters from cookiecutter.json.

Our hook will address all the issues listed above. Since we are going to create the directory structure dynamically, we can remove src/main/java/demo directory and move Main.java file to the root of the \{\{ cookiecutter.artifactId \}\} directory. Instead of hardcoding demo pacakge in Main.java, we can use now \{\{ cookiecutter.__package \}\} variable:

java

package \{\{cookiecutter.__package\}\};

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

Then:

  1. Create a directory hooks in your project template directory.

  2. Create a file hooks/post_gen_project.py

python

import os

# converts groupId like com.example and artifact id like demo into a string com/example/hello 
directory = '\{\{ cookiecutter.__package.replace('.','/') \}\}'

srcDir = 'src/main/java/' + directory

# create typical maven directory structure
os.makedirs(srcDir, exist_ok=True)

# move Main.java to Maven sources
os.rename("Main.java", srcDir + '/Main.java')

# generate Maven wrapper
os.system('mvn wrapper:wrapper')

# download dependencies
os.system('./mvnw verify')

Conditionals ​

It may and likely will happen that some pieces of projects are created or not depending on the values provided by the user. We can extend cookiecutter.json to ask if project should include JUnit, and if it does then include the dependency in pom.xml and create src/test/java directory.

json

{
  "groupId": "com.example",
  "artifactId": "demo",
  "javaVersion": "11",
  "junit": [
    "yes",
    "no"
  ],
  "__package": "\{\{ cookiecutter.groupId + '.' + cookiecutter.artifactId \}\}"
}

Cookiecutter uses Jinja2 templating system. Let's update pom.xml with:

xml

{% if cookiecutter.junit == 'yes'%}
<dependencies>
	<dependency>
		<groupId>org.junit.jupiter</groupId>
		<artifactId>junit-jupiter</artifactId>
		<version>5.9.0</version>
		<scope>test</scope>
	</dependency>
</dependencies>
		{% endif %}

Our hook, now must create src/test/java directory depending on the value of cookiecutter.junit variable:

python

if '\{\{ cookiecutter.junit \}\}' == 'yes':
	os.makedirs(testDir, exist_ok=True)

Try it out! ​

The following code is pushed to https://github.com/maciejwalkowiak/cookiecutter-maven-template repository. You can try it yourself by executing:

$ cookiecutter https://github.com/maciejwalkowiak/cookiecutter-maven-template

And just to make it clear - this project is not meant to be "the missing mvn init" - at this stage it does not offer much more than regular mvn archetype:generate, but if you think it is an idea worth pursuing - PRs are welcome!

Conclusion ​

Cookiecutter is an interesting, simple but powerful option for generating project templates. From the user point of view - it is quite rough. It misses bells and whistles, animations, interactive CLI comboboxes that you may be familiar with from tools like Yeoman. But it does the job. I like concept of hooks, thanks to which it is very easy to code more advanced use cases. The drawback - purely from personal point of view - it is written in Python, which I am not a fan of. But that's just a matter of taste.

Let's stay in touch and follow me on Twitter: @maciejwalkowiak

Subscribe to RSS feed