1. 今回の記事内容
開発業務で Spring Framework を使うことになりそうなので、開発ツールとしてEclipse ではなく VS Code での開発をやってみた、という記事になります。Javaの開発において現状ではEclipseを使うのが主流だと思いますが、今後は VS Code を使うケースが増えるのではないかと見ています。
2. Spring Framework に関するおさらい
本題に入る前に Spring Framework について少しだけ復習しておきます。
(1) Spring Boot ってなんだっけ?Spring Framework とどう違うの?
書籍『Spring 徹底入門』によると、Spring Boot とは「最小限の設定でプロダクションレベルのSpring アプリケーションを容易に開発するためのSpringプロジェクト」であると説明されています(p.8)。 Spring Framework を簡単に使うための「おあつらえセット」みたいなものと考えると良いと思います。したがって Spring Boot によって作ったアプリケーションは Spring Framework で作られていると言って良いでしょう。細かい設定とか面倒なことはいったん置いといてとりあえず Spring Framework を使ってみたいと言う人のために用意された、導入の敷居をさげるための簡易化の仕組みと言えます。
(2) Spring Framework って Java EE の一種なの?
最初にSpring が開発された当時(2004年頃)は JavaEE ではなくその前身である「J2EE」と呼ばれる標準仕様がありました。J2EEのバージョン5からJava EEという名称に変わりました(さらに、バージョン9からはJakarta EEに変わりました)。J2EE/Java EE/Jakarta EE はJavaのエンタープライズ向け機能の標準仕様であり、それ自体は仕様であってフレームワークではありませんので、Spring Framework が JavaEE の一種ということはなく、JavaEEに対して準拠するか/しないかという関係になります。そして完全準拠しているかというと現状で「一部準拠」しているということはできますが、完全準拠はしていません。もともとの経緯として仕様が重厚すぎて使いにくい J2EE/JavaEE に対するアンチテーゼとして軽快で使いやすいことを売りとした Spring Framework が作られ、両者がお互いの良い所を取りこんできた結果として一部機能が似たり重複したりしてきて、だんだんと近いものになってきているようです。
それでは復習はこれくらいにして本題に入ります。
2. 使用したバージョン
(1) Windows 11 Pro
- バージョン: 22H2
- OSビルド: 22621.1105
これはたまたまこれを使ったというだけで、本記事の内容に関しては Windows10 でも問題ないと思います。
(2) OpenJDK
>java -version
openjdk version "17.0.6" 2023-01-17 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.6.0+10-1) (build 17.0.6+10-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.6.0+10-1) (build 17.0.6+10-LTS, mixed mode, sharing)
今回はRed Hat を使いましたが、JDK は Oracle OpenJDKでも問題ないのではないかと思います。
なぜ Red Hat を使ったかというと、VS Codeがこれを使えとリンクを表示してきたためです。Red Hat版を勧めてくる理由ははっきりわかりませんが、VSCodeのJava Language Support 拡張がRed Hat製なのでJDKも合わせておいた方が相性が良いだろうと思われます。
(3) Spring Framework
pom.xmlより
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
これは前提というより、後でSpring Bootプロジェクトを作るときにこのバージョンを選びますので、3.0.2にする予定ということです。
(4) Visual Studio Code
バージョン: 1.75.1 (user setup)
コミット: 441438abd1ac652551dbe4d408dfcec8a499b8bf
日付: 2023-02-08T21:32:34.589Z
Electron: 19.1.9
Chromium: 102.0.5005.194
Node.js: 16.14.2
V8: 10.2.154.23-electron.0
OS: Windows_NT x64 10.0.22621
Sandboxed: Yes
3. 環境構築手順
作業の前提として、JDK と VS Code はすでにインストール済みであるものとします。
(1) VS Code の拡張機能 Extension Pack for Java をインストールする。
これはVS Code でJava開発するときにお勧めの拡張機能の詰め合わせです。以下の拡張機能が含まれています。
-
📦 Language Support for Java™ by Red Hat
補完、リファクタリング、スニペットのサポート -
📦 Debugger for Java
デバッグ機能 -
📦 Test Runner for Java
JUnit/TestNG 対応 -
📦 Maven for Java
ビルドツールのMaven を使う拡張機能です。 -
📦 Project Manager for Java
Javaプロジェクト管理機能。ここでいう「プロジェクト」はソースやリソース類を束ねたもの、という意味で、Eclipse 上のプロジェクトと同じように考えればよいです。 -
📦 Visual Studio IntelliCode
AIによるコーディング支援機能。コード補完のときにいろいろサジェストしてくれます。
インストール後です。「プレビュー」となっていて、「プレリリースバージョンへの切り替え」というボタンがありますが、これを押すと1つ前のバージョンになるだけで、ステーブル版にアップグレードするわけではないようです。
(2) VS Code の拡張機能 Spring Boot Extension Pack をインストールする。
この拡張機能パックには以下が含まれます。
- Spring Boot Tools
いわゆる STS です。 - Spring Initializr Java Support
Spring では Spring Initializr と呼ばれる、プロジェクトのテンプレートをお好みに沿って簡単に作ってくれるWebインターフェイスが提供されていますが、そのVSCode拡張機能版です。 -
Spring Boot Dashboard
Spring Boot 用のサイドバー機能です。
インストール後の画面。
これで必要な道具のインストールは完了、HelloWorld作るだけの環境構築としては終わりです。やってみたら思ったより簡単でしたが、実用的なアプリケーションの開発にはこの他にもDBMS環境を用意したりする必要があると思います。
4. Spring Boot プロジェクトの作成
ではさっそく、HelloWorld的な最小プロジェクトを作ってみたいと思います。
(1) Create Java Project
何も開いていない状態でサイドバーのエクスプローラーを開くと、画面左側に下図のような3つのボタンが表示されます。
ここで「Create Java Project」ボタンを押すと、メインメニューの真ん中あたりにプルダウンメニューが開きます。
"Select the project type" なのでプロジェクトの種類を選ぶことになります。プロジェクトタイプとしてMaven, Gradle, JavaFX はわかるとして、 Quarkusとは何でしょうか? どうやらKubernetes 対応に特化したJavaフレームワークらしいです。MicroProfile というのはマイクロサービスに特化したアーキテクチャのようで、それに沿ったプロジェクトが作られるのでしょう。ここではおそらく一番簡単であろうと思われる「Spring Boot」を選びます。
するとビルドツールに何を使うか聞いてきます。MavenとGradleが選択肢に出て来ます。
どちらでもよいと思いますが、とりあえずMavenを選びました。
次に使用するSpring Bootのバージョンを聞かれます。
一番上の3.0.2がSNAPSHOTが付いていなくて安定版だと思われるので、それを選びます。
次は使用言語の選択です。Groovyは10年くらい前にちょっと話題になっていたJava環境で動くスクリプト言語の一種ですが、今ひとつ流行らなかったという印象があります。選択肢に出てくるということはSpringでも使えるのですね。
Javaを選びます。
グループIDを決めます。これはMavenやGradleにおけるプロジェクト管理の基本的なキーとなるいわゆる「GAV3要素」と呼ばれる「グループID、アーティファクトID、バージョン」のうちの1つであるグループID(通常はプロジェクトの属する組織単位のID)を決めろということだと思います。
他と被らなければ良いだけなので適当ですが、生成したソースのパッケージ名になります。
続けてアーティファクトID。プロジェクトを識別するIDです。
これも今回はただのHello Worldなので適当です。
次はパッケージングタイプです。
ここではひとまず Jar を選択しました。
パッケージングタイプを Jar にする場合、Spring Boot アプリケーションが単一の Jar ファイルにパッケージ化されます。この Jar ファイルには、アプリケーションのすべての依存関係が含まれています。このJarを単独アプリケーションとして起動すれば、すべての必要なコンポーネントが自動的に読み込まれます。ローカル環境での実行時にアプリケーションコンテナとしてのSpring Boot Tomcat も含まれることになります。WarにするとTomcatは含まれないので、別途用意する必要があるようです。つまりローカル環境で開発、実行するときは Jar で、本番サーバへのデプロイ用には War を選択するということでしょうか。
なお JarにするかWarにするかで何が違うのかはこの辺りに書いてありました。
Java バージョンの設定です。
とりあえず一番上の17にします。
依存するライブラリの設定です。
今回は Spring Web のみとします。
以上で設定は終わりです。
プロジェクトが生成されました。おそらくWebインターフェイスの Spring Initializr が作ってくれるプロジェクトと同等のものが出来ていると思われます。
下記のようなメッセージが画面右下に出て来ます。
View Projects を押します。すると生成したプロジェクトをVSCodeが開いてくれます。
5. 生成したSpring Boot プロジェクトを開く
VS Code上でプロジェクトが開かれました。
HelloApplication.java というソースが既にできていました。これを開くと main() メソッドがありました。
(1) とりあえず Run してみる
main メソッドの上に「Run | Debug」という表示が出ています。Run を押したら走りそうに見えます。これだけで動くでしょうか?
押してみます。するとなにやら動き出し…
Tomcat がポート8080で動いているらしき出力が出ています。Spring Boot で jar ファイルを作成した場合、デプロイ工程は不要なようです。
しかしブラウザで見てみると残念ながら404
何もしないでいきなり動くほど甘くはありませんでした。
(2) 最低限のControllerクラスを書く
やはり最低限のプログラミングは必要なようで、どうやら少なくともMVCのCに当たる Controller クラスは自分で用意しないといけないようです。
【参考記事】
Controller クラスはアノテーションに @Controller
かまたは @RestController
を付けたクラスです。
ドキュメントはこちらです↓
【Javadoc】
- Controller (Spring Framework API) - Javadoc (pleiades.io)
- RestController (Spring Framework API) - Javadoc (pleiades.io)
両者の違いは、@Controller
が通常のWebアプリ用のコントローラーで、@RestController
が REST API 作成用のコントローラーということらしいです。ここで「らしい」というのは、はっきりした記述がドキュメント上に見つけられなかったためですが、当たり前すぎて書いてないのか、あるいはSpring MVCのドキュメントをよく読むと記載があるのかもしれません。上記Qiita記事を読むと @RestController
は @ResponseBody
を記述しないで使えるとあるのでとりあえず簡単に使うなら @RestController
をつけると良いようです。早速書いてみます。
package com.anestec.hello;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloApplicationController {
}
これで再度 Run してみたところ、現象(404)は変わりませんでした。もう少し何か必要なようです。上記Qiita記事を見ると @RequestMapping
アノテーションをつけたメソッドが必要なようです。公式ドキュメントを見てみると下記に記載がありました。
【参考ドキュメント】
@RequestMapping アノテーションを使用して、リクエストをコントローラーメソッドにマッピングできます。
...
@RequestMapping アノテーションは、「ルーティング」情報を提供します。/
パスを持つ HTTP リクエストはhome
メソッドにマッピングする必要があることを Spring に伝えます。@RestControllerアノテーションは、結果の文字列を呼び出し元に直接返すように Spring に指示します。
REST API なのでリクエストされたURLのルーティングをクラスのメソッドにマッピングする必要があり、そのためのアノテーションなのだろうと思います。
ルートパス("/")に対して"Hello World"を返すようなメソッドを作ってやればよさそうです。
package com.anestec.hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloApplicationController {
@RequestMapping("/") // ルートへこのメソッドをマップする
public String test() {
return "Hello World";
}
}
(3) 再度実行→表示してみる
この状態で再度 Run してみます。
これだけで表示できてしまいました。このときのhtmlソースはどうなっているのでしょうか?
ソースを見ると、HTMLというより、ただの文字列が返ってきているだけのようです。確認のため、コマンドラインから curl を叩いてみます。
C:\>curl -v localhost:8080
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.83.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 11
< Date: Thu, 16 Feb 2023 06:15:45 GMT
<
Hello World* Connection #0 to host localhost left intact
REST APIとして動いているので、レスポンスは本当にメソッドが返した文字列が返ってくるだけのようです。Content-Length:11 となっていることからもそれがわかります。本来はここで文字列として JSON を返すところでしょう。
(4) デバッグ実行してみる
今度はHelloApplicationクラスから「Debug」を押してデバッグ実行してみましょう。あらかじめ "Hello World" を返すところにブレークポイントを張っておいてブラウザからリクエストを出し、レスポンスを返す直前でブレークさせてみたところです。
こうしてみると、10個以上のスレッドが上がっていて、そのうちの一つ「http-nio-8080-exec-1]というスレッドから呼ばれているのがわかります。
HelloApplicationContoroller#test() が呼び出されているスタックトレースを調べてみると、下記のようになっています。
HelloApplicationController.test() (c:\work\hello\src\main\java\com\anestec\hello\HelloApplicationController.java:11)
NativeMethodAccessorImpl.invoke0(Method,Object,Object[])[native method] (不明なソース:-1)
NativeMethodAccessorImpl.invoke(Object,Object[]) (不明なソース:-1)
DelegatingMethodAccessorImpl.invoke(Object,Object[]) (不明なソース:-1)
Method.invoke(Object,Object[]) (不明なソース:-1)
InvocableHandlerMethod.doInvoke(Object[]) (\spring-web-6.0.4.jar\org.springframework.web.method.support\InvocableHandlerMethod.class:207)
InvocableHandlerMethod.invokeForRequest(NativeWebRequest,ModelAndViewContainer,Object[]) (\spring-web-6.0.4.jar\org.springframework.web.method.support\InvocableHandlerMethod.class:152)
ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest,ModelAndViewContainer,Object[]) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet.mvc.method.annotation\ServletInvocableHandlerMethod.class:117)
RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest,HttpServletResponse,HandlerMethod) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet.mvc.method.annotation\RequestMappingHandlerAdapter.class:884)
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest,HttpServletResponse,HandlerMethod) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet.mvc.method.annotation\RequestMappingHandlerAdapter.class:797)
AbstractHandlerMethodAdapter.handle(HttpServletRequest,HttpServletResponse,Object) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet.mvc.method\AbstractHandlerMethodAdapter.class:87)
DispatcherServlet.doDispatch(HttpServletRequest,HttpServletResponse) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet\DispatcherServlet.class:1080)
DispatcherServlet.doService(HttpServletRequest,HttpServletResponse) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet\DispatcherServlet.class:973)
FrameworkServlet.processRequest(HttpServletRequest,HttpServletResponse) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet\FrameworkServlet.class:1011)
FrameworkServlet.doGet(HttpServletRequest,HttpServletResponse) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet\FrameworkServlet.class:903)
HttpServlet.service(HttpServletRequest,HttpServletResponse) (\tomcat-embed-core-10.1.5.jar\jakarta.servlet.http\HttpServlet.class:705)
FrameworkServlet.service(HttpServletRequest,HttpServletResponse) (\spring-webmvc-6.0.4.jar\org.springframework.web.servlet\FrameworkServlet.class:885)
HttpServlet.service(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\jakarta.servlet.http\HttpServlet.class:814)
ApplicationFilterChain.internalDoFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:223)
ApplicationFilterChain.doFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:158)
WsFilter.doFilter(ServletRequest,ServletResponse,FilterChain) (\tomcat-embed-websocket-10.1.5.jar\org.apache.tomcat.websocket.server\WsFilter.class:53)
ApplicationFilterChain.internalDoFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:185)
ApplicationFilterChain.doFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:158)
RequestContextFilter.doFilterInternal(HttpServletRequest,HttpServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\RequestContextFilter.class:100)
OncePerRequestFilter.doFilter(ServletRequest,ServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\OncePerRequestFilter.class:116)
ApplicationFilterChain.internalDoFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:185)
ApplicationFilterChain.doFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:158)
FormContentFilter.doFilterInternal(HttpServletRequest,HttpServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\FormContentFilter.class:93)
OncePerRequestFilter.doFilter(ServletRequest,ServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\OncePerRequestFilter.class:116)
ApplicationFilterChain.internalDoFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:185)
ApplicationFilterChain.doFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:158)
CharacterEncodingFilter.doFilterInternal(HttpServletRequest,HttpServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\CharacterEncodingFilter.class:201)
OncePerRequestFilter.doFilter(ServletRequest,ServletResponse,FilterChain) (\spring-web-6.0.4.jar\org.springframework.web.filter\OncePerRequestFilter.class:116)
ApplicationFilterChain.internalDoFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:185)
ApplicationFilterChain.doFilter(ServletRequest,ServletResponse) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\ApplicationFilterChain.class:158)
StandardWrapperValve.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\StandardWrapperValve.class:177)
StandardContextValve.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\StandardContextValve.class:97)
AuthenticatorBase.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.authenticator\AuthenticatorBase.class:542)
StandardHostValve.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\StandardHostValve.class:119)
ErrorReportValve.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.valves\ErrorReportValve.class:92)
StandardEngineValve.invoke(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.core\StandardEngineValve.class:78)
CoyoteAdapter.service(Request,Response) (\tomcat-embed-core-10.1.5.jar\org.apache.catalina.connector\CoyoteAdapter.class:357)
Http11Processor.service(SocketWrapperBase) (\tomcat-embed-core-10.1.5.jar\org.apache.coyote.http11\Http11Processor.class:400)
AbstractProcessorLight.process(SocketWrapperBase,SocketEvent) (\tomcat-embed-core-10.1.5.jar\org.apache.coyote\AbstractProcessorLight.class:65)
AbstractProtocol$ConnectionHandler.process(SocketWrapperBase,SocketEvent) (\tomcat-embed-core-10.1.5.jar\org.apache.coyote\AbstractProtocol.class:859)
NioEndpoint$SocketProcessor.doRun() (\tomcat-embed-core-10.1.5.jar\org.apache.tomcat.util.net\NioEndpoint.class:1734)
SocketProcessorBase.run() (\tomcat-embed-core-10.1.5.jar\org.apache.tomcat.util.net\SocketProcessorBase.class:52)
ThreadPoolExecutor.runWorker(ThreadPoolExecutor$Worker) (\tomcat-embed-core-10.1.5.jar\org.apache.tomcat.util.threads\ThreadPoolExecutor.class:1191)
ThreadPoolExecutor$Worker.run() (\tomcat-embed-core-10.1.5.jar\org.apache.tomcat.util.threads\ThreadPoolExecutor.class:659)
TaskThread$WrappingRunnable.run() (\tomcat-embed-core-10.1.5.jar\org.apache.tomcat.util.threads\TaskThread.class:61)
Thread.run() (不明なソース:-1)
とても追っかける気にはなりませんが、直前の呼出しである InvocableHandlerMethod#doInvoke(Object[]) だけでも見てみます。
public class InvocableHandlerMethod extends HandlerMethod {
// (中略)
/**
* Invoke the handler method with the given argument values.
*/
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return invokeSuspendingFunction(method, getBean(), args);
}
return method.invoke(getBean(), args); // ←ここから呼ばれている
}
catch (IllegalArgumentException ex) {
assertTargetBean(method, getBean(), args);
String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ?
"Illegal argument" : ex.getMessage();
throw new IllegalStateException(formatInvokeError(text, args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getCause();
if (targetException instanceof RuntimeException runtimeException) {
throw runtimeException;
}
else if (targetException instanceof Error error) {
throw error;
}
else if (targetException instanceof Exception exception) {
throw exception;
}
else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
リフレクションを使ってこちらの書いたメソッドが呼び出されているのがわかります。なにやら引数が渡されているのも見えます。おそらくURLにGETパラメータを付ければここで渡ってくるのだと思います。スタックをさらに1つ上に遡ってみます。
public class InvocableHandlerMethod extends HandlerMethod {
// (中略)
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); // ←引数はここで取得しているらしい
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args); // ←ここで呼出し
}
(5) GETパラメータ渡してみる
ならば試しにこんなことをやってみましょうか… @RequestParam
アノテーションをつけて引数を取得できるはずです。
package com.anestec.hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloApplicationController {
@RequestMapping("/")
public String test(@RequestParam("id") String id) { // 引数 id を貰ってみる。
return "Hello World id=[" + id + "]";
}
}
C:\>curl -v localhost:8080?id=12345
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /?id=12345 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.83.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 22
< Date: Thu, 16 Feb 2023 07:20:16 GMT
<
Hello World id=[12345]* Connection #0 to host localhost left intact
6. POM の設定しなくていいの?
今回、pom.xml を手でいじることはまったくありませんでした。最初のプロジェクト生成時に設定した内容が反映されていると思われます。念のため内容を確認するとこんな具合になっていました。
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.anestec</groupId>
<artifactId>hello</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>hello</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
7. その他のポイント
(1) Spring Boot Dashboard
今回拡張機能として入った Spring Boot Dashboard の画面はこんな感じです。
情報がすっきりまとまっていていい感じです。
(2) デバッグ実行時の変数表示
デバッグ時に引数やローカル変数の値を自動的にソースにオーバーラップして表示してくれるのもなかなかイケてると思いました。
Eclipse だと変数にマウスカーソルを当てるとツールチップで値を表示してくれますが、こっちの方が見やすいように思います。もちろん普通にサイドバーのローカル/ウォッチのパネルから変数を指定してインスペクトすることもできます。
8. まとめ
ということで、ここまでまとめます。
-
JDK17 以上をあらかじめ入れて置けば、VS Code + 拡張機能で Spring Boot の最小限のアプリケーション(今回はREST API)はほぼあっという間に作れる。
-
具体的には、以下をやるだけで良い。
- 拡張機能 Extension Pack for Java をインストール
- 拡張機能 Spring Boot Extension Pack をインストール
- Explorer の「Create Java Project」からプロンプトに従って必要事項を入力する。
- Controller クラスを作成する。
- Application クラスのmainメソッドから Run、または Debug で実効する。
-
VSCode のSpring開発体験は全体的によく洗練されていて、Eclipse みたいにゴチャゴチャしていない(個人の感想です)
ということで、VSCodeによるSpring開発で全然OKなのでは?という印象を受けました。Docker コンテナや WSL を使った開発が今後Java開発でも増えていくと思われ、その点でも Eclipse より VS Code での開発が有利になると思います。今後はこちらが主流になっていくのではないかと思いました。
また時間があれば Thymeleaf を使った画面系開発などにもトライしてみたいと思います。
ここまで読んでいただきありがとうございました。