1. Gambaran keseluruhan
Ringkasnya, projek Spring Shell menyediakan shell interaktif untuk memproses perintah dan membina CLI berfitur lengkap menggunakan model pengaturcaraan Spring.
Dalam artikel ini, kami akan meneroka ciri, kelas utama, dan anotasinya, serta melaksanakan beberapa perintah dan penyesuaian tersuai.
2. Ketergantungan Maven
Pertama, kita perlu menambahkan kebergantungan shell-spring ke pom.xml kita :
org.springframework.shell spring-shell 1.2.0.RELEASE
Versi terbaru artifak ini boleh didapati di sini.
3. Mengakses Shell
Terdapat dua cara utama untuk mengakses shell dalam aplikasi kami.
Yang pertama adalah dengan mengikat shell pada titik masuk aplikasi kami dan membiarkan pengguna memasukkan arahan:
public static void main(String[] args) throws IOException { Bootstrap.main(args); }
Yang kedua adalah mendapatkan JLineShellComponent dan melaksanakan perintah secara program:
Bootstrap bootstrap = new Bootstrap(); JLineShellComponent shell = bootstrap.getJLineShellComponent(); shell.executeCommand("help");
Kami akan menggunakan pendekatan pertama kerana sangat sesuai untuk contoh dalam artikel ini, namun, dalam kod sumber, anda boleh menemui kes ujian yang menggunakan borang kedua.
4. Perintah
Sudah ada beberapa perintah bawaan di shell, seperti jelas , help , exit , dll., Yang menyediakan fungsi standard setiap CLI.
Perintah kustom dapat didedahkan dengan menambahkan metode yang ditandai dengan anotasi @CliCommand di dalam komponen Spring yang menerapkan antara muka CommandMarker .
Setiap argumen kaedah itu mesti ditandai dengan anotasi @CliOption , jika kita gagal melakukan ini, kita akan menghadapi beberapa kesalahan ketika cuba melaksanakan perintah tersebut.
4.1. Menambah Perintah ke Shell
Pertama, kita perlu memberitahu shell di mana perintah kita. Untuk ini, ia memerlukan fail META-INF / spring / spring-shell-plugin.xml untuk hadir dalam projek kami, di sana, kami dapat menggunakan fungsi pengimbasan komponen Spring:
Setelah komponen didaftarkan dan diperkenalkan oleh Spring, komponen tersebut didaftarkan dengan pengurai shell, dan anotasinya diproses.
Mari buat dua arahan mudah, satu untuk mengambil kandungan URL dan memaparkannya, dan yang lain untuk menyimpan kandungan tersebut ke dalam fail:
@Component public class SimpleCLI implements CommandMarker { @CliCommand(value = { "web-get", "wg" }) public String webGet( @CliOption(key = "url") String url) { return getContentsOfUrlAsString(url); } @CliCommand(value = { "web-save", "ws" }) public String webSave( @CliOption(key = "url") String url, @CliOption(key = { "out", "file" }) String file) { String contents = getContentsOfUrlAsString(url); try (PrintWriter out = new PrintWriter(file)) { out.write(contents); } return "Done."; } }
Perhatikan bahawa kita boleh lulus lebih daripada satu rentetan kepada nilai dan utama sifat-sifat @CliCommand dan @CliOption masing-masing, permit ini kita mendedahkan beberapa perintah dan hujah-hujah yang berkelakuan yang sama.
Sekarang, mari kita periksa sama ada semuanya berfungsi seperti yang diharapkan:
spring-shell>web-get --url //www.google.com web-save --url //www.google.com --out contents.txt Done.
4.2. Ketersediaan Perintah
Kita boleh menggunakan anotasi @CliAvailabilityIndicator pada kaedah mengembalikan boolean untuk berubah, pada waktu runtime, jika perintah harus terkena shell.
Pertama, mari buat kaedah untuk mengubah ketersediaan arahan simpan web :
private boolean adminEnableExecuted = false; @CliAvailabilityIndicator(value = "web-save") public boolean isAdminEnabled() { return adminEnableExecuted; }
Sekarang, mari buat arahan untuk menukar pemboleh ubah adminEnableExecuted :
@CliCommand(value = "admin-enable") public String adminEnable() { adminEnableExecuted = true; return "Admin commands enabled."; }
Akhirnya, mari kita mengesahkannya:
spring-shell>web-save --url //www.google.com --out contents.txt Command 'web-save --url //www.google.com --out contents.txt' was found but is not currently available (type 'help' then ENTER to learn about this command) spring-shell>admin-enable Admin commands enabled. spring-shell>web-save --url //www.google.com --out contents.txt Done.
4.3. Hujah yang diperlukan
Secara lalai, semua argumen perintah adalah pilihan. Walau bagaimanapun, kami dapat menjadikannya wajib dengan atribut wajib anotasi @CliOption :
@CliOption(key = { "out", "file" }, mandatory = true)
Sekarang, kita dapat menguji bahawa jika kita tidak memperkenalkannya, akan menghasilkan ralat:
spring-shell>web-save --url //www.google.com You should specify option (--out) for this command
4.4. Hujah Lalai
Nilai kunci kosong untuk @CliOption menjadikan argumen itu sebagai lalai. Di sana, kami akan menerima nilai yang diperkenalkan di shell yang bukan merupakan sebahagian daripada argumen bernama:
@CliOption(key = { "", "url" })
Sekarang, mari kita pastikan ia berfungsi seperti yang diharapkan:
spring-shell>web-get //www.google.com
4.5. Helping Users
@CliCommand and @CliOption annotations provide a help attribute that allows us to guide our users when using the built-in help command or when tabbing to get auto-completion.
Let's modify our web-get to add custom help messages:
@CliCommand( // ... help = "Displays the contents of an URL") public String webGet( @CliOption( // ... help = "URL whose contents will be displayed." ) String url) { // ... }
Now, the user can know exactly what our command does:
spring-shell>help web-get Keyword: web-get Keyword: wg Description: Displays the contents of a URL. Keyword: ** default ** Keyword: url Help: URL whose contents will be displayed. Mandatory: false Default if specified: '__NULL__' Default if unspecified: '__NULL__' * web-get - Displays the contents of a URL. * wg - Displays the contents of a URL.
5. Customization
There are three ways to customize the shell by implementing the BannerProvider, PromptProvider and HistoryFileNameProvider interfaces, all of them with default implementations already provided.
Also, we need to use the @Order annotation to allow our providers to take precedence over those implementations.
Let's create a new banner to begin our customization:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class SimpleBannerProvider extends DefaultBannerProvider { public String getBanner() { StringBuffer buf = new StringBuffer(); buf.append("=======================================") .append(OsUtils.LINE_SEPARATOR); buf.append("* Baeldung Shell *") .append(OsUtils.LINE_SEPARATOR); buf.append("=======================================") .append(OsUtils.LINE_SEPARATOR); buf.append("Version:") .append(this.getVersion()); return buf.toString(); } public String getVersion() { return "1.0.1"; } public String getWelcomeMessage() { return "Welcome to Baeldung CLI"; } public String getProviderName() { return "Baeldung Banner"; } }
Note that we can also change the version number and welcome message.
Now, let's change the prompt:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class SimplePromptProvider extends DefaultPromptProvider { public String getPrompt() { return "baeldung-shell"; } public String getProviderName() { return "Baeldung Prompt"; } }
Finally, let's modify the name of the history file:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class SimpleHistoryFileNameProvider extends DefaultHistoryFileNameProvider { public String getHistoryFileName() { return "baeldung-shell.log"; } public String getProviderName() { return "Baeldung History"; } }
The history file will record all commands executed in the shell and will be put alongside our application.
With everything in place, we can call our shell and see it in action:
======================================= * Baeldung Shell * ======================================= Version:1.0.1 Welcome to Baeldung CLI baeldung-shell>
6. Converters
So far, we've only used simple types as arguments to our commands. Common types such as Integer, Date, Enum, File, etc., have a default converter already registered.
By implementing the Converter interface, we can also add our converters to receive custom objects.
Let's create a converter that can transform a String into an URL:
@Component public class SimpleURLConverter implements Converter { public URL convertFromText( String value, Class requiredType, String optionContext) { return new URL(value); } public boolean getAllPossibleValues( List completions, Class requiredType, String existingData, String optionContext, MethodTarget target) { return false; } public boolean supports(Class requiredType, String optionContext) { return URL.class.isAssignableFrom(requiredType); } }
Finally, let's modify our web-get and web-save commands:
public String webSave(... URL url) { // ... } public String webSave(... URL url) { // ... }
As you may have guessed, the commands behave the same.
7. Conclusion
In this article, we had a brief look at the core features of the Spring Shell project. We were able to contribute our commands and customize the shell with our providers, we changed the availability of commands according to different runtime conditions and created a simple type converter.
Complete source code for this article can be found over on GitHub.