Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Spring Frameworkでイチからアプリを作ってみる(1) Hello World

という勉強会をやることになった。そういうことをやってみたい、教えてほしいという若者が多いのである。

そういう若者を相手にすると、勉強になるのは私である。若者は私がちょちょいでそんなことが出来ると思っているようだが、そんなことやる機会は普通に仕事していたらないのである。だいたい困っている現場に呼ばれるだけだからな。だいたいの知識はあるつもりだけども断片的だし、最初からやるとしたらどうするのがベストとか、よく知らないのである。というわけで、一番ためになっているのは私であり、若者をダシに知識の整理をして楽しもう。

とはいえ、プログラミングの学習も、環境を作ってHello Worldするまでが一番難しい。一度、ビルド出来てしまえばそこからは買ってきた本の通りになるけど、ビルド出来るようになるまでに起きることは自分の環境に依存した出来事だからだ。

というわけで、なーんもよくわかってない若者前提で、手順を考える。今日はWindows環境前提でやる。ぶっちゃけ私もなんもわかっていない。

まずは、Javaをインストールする。これは記事に書いた。好きなものをwingetしてくれればよい。GitもWingetすればいいはず。ここまでは私のPCにも入ってた。

次はGradleかな。これはwingetできない。というかダウンロードしてフォルダに展開するだけ。公式はC:\Gradleを作ってそこに入れろとしているが、趣味でホームディレクトリの下に入れる事にする。~/bin/gradleの下に公式からダウンロードしたzipを解凍したものをそのまま入れる。

そして、これを環境変数のPATHへセットする。どうやっても難しいが、PowerShellでやる方法をここでは書いておこう。

PS C:\Users\Tambourine> [System.Environment]::SetEnvironmentVariable('PATH', $Env:PATH + 'C:\Users\Tambourine\bin\gradle\gradle-8.6\bin;', 'User')

PowerShellはホント、しっくりこないよ・・・

これで、ターミナルを再起動すればPATHが設定されているはず。設定したターミナルでは$Env:PATHの値は変化しないので注意。

PS C:\Users\Tambourine> gradle -v

Welcome to Gradle 8.6!

Here are the highlights of this release:
 - Configurable encryption key for configuration cache
 - Build init improvements
 - Build authoring improvements

For more details see https://docs.gradle.org/8.6/release-notes.html


------------------------------------------------------------
Gradle 8.6
------------------------------------------------------------

Build time:   2024-02-02 16:47:16 UTC
Revision:     d55c486870a0dc6f6278f53d21381396d0741c6e

Kotlin:       1.9.20
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          20.0.2 (Eclipse Adoptium 20.0.2+9)
OS:           Windows 11 10.0 amd64

JDKとGradleが入ったらいよいよSpringの出番だ。Getting Started的なドキュメントを読むと、とりあえずSpring Initializrでひな形を作れと書いてある。Springはただでさえ黒魔術が多いのに、Spring Bootすると何がどういう仕組みで動いているのかが黒魔術ラッパーで覆われてしまうので気が重いが、初心者は公式ドキュメントの通りにやるのが基本だ。

というわけで、 https://spring.pleiades.io/guides/gs/spring-boot を見ながら進もう。

まずはSpring Initializrでポチポチする。依存ライブラリは3つだけ入れた

Spring Initializr

この選択があっているのかどうかはよくわからない。生成されたコードをダウンロードして、vscodeで開いてみる。開くとJavaとかGradleのプラグインをいれたらどうかねと言われるので、入れる。言われるがままである。

では、コントローラーを作ってみよう。

といっても、ドキュメントをまんまパクる

package one.tmbrms.readingsns;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloWorldController {
    
    @GetMapping("/")
    public String index(){
        return "Hello World!";
    }
    
}

とりあえず、これだけ作れば、GradleでbootRunタスクを実行すると、localhost:8080 でHello Worldは表示される。素晴らしい。

ただし、ドキュメントはアプリケーションクラスをもっとちゃんとせいと書いてある。で、CommandLineRunnerメソッドを追加して、beanの一覧を取得している。

 @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {

            System.out.println("Let's inspect the beans provided by Spring Boot:");

            String[] beanNames = ctx.getBeanDefinitionNames();
            Arrays.sort(beanNames);
            for (String beanName : beanNames) {
                System.out.println(beanName);
            }

        };
    }

これをアプリケーションクラスに追加する。すると、起動時に以下が出力される。

Let's inspect the beans provided by Spring Boot:
applicationAvailability
applicationTaskExecutor
basicErrorController
beanNameHandlerMapping
beanNameViewResolver
characterEncodingFilter
classPathFileSystemWatcher
classPathRestartStrategy
commandLineRunner
conditionEvaluationDeltaLoggingListener
conventionErrorViewResolver
defaultServletHandlerMapping
defaultTemplateResolver
defaultViewResolver
dispatcherServlet
dispatcherServletRegistration
error
errorAttributes
errorPageCustomizer
errorPageRegistrarBeanPostProcessor
fileSystemWatcherFactory
fileWatcher
flashMapManager
forceAutoProxyCreatorToUseClassProxying
formContentFilter
handlerExceptionResolver
handlerFunctionAdapter
helloWorldController
httpMessageConvertersRestClientCustomizer
httpRequestHandlerAdapter
jacksonObjectMapper
jacksonObjectMapperBuilder
jsonComponentModule
jsonMixinModule
jsonMixinModuleEntries
lifecycleProcessor
liveReloadServer
liveReloadServerEventListener
localeCharsetMappingsCustomizer
localeResolver
mappingJackson2HttpMessageConverter
messageConverters
multipartConfigElement
multipartResolver
mvcContentNegotiationManager
mvcConversionService
mvcHandlerMappingIntrospector
mvcPathMatcher
mvcPatternParser
mvcResourceUrlProvider
mvcUriComponentsContributor
mvcUrlPathHelper
mvcValidator
mvcViewResolver
optionalLiveReloadServer
org.springframework.aop.config.internalAutoProxyCreator
org.springframework.boot.autoconfigure.AutoConfigurationPackages
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration$ClassProxyingConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration
org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration
org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonMixinConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$ParameterNamesModuleConfiguration
org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$SimpleAsyncTaskExecutorBuilderConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorBuilderConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$TaskExecutorConfiguration
org.springframework.boot.autoconfigure.task.TaskExecutorConfigurations$ThreadPoolTaskExecutorBuilderConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$SimpleAsyncTaskSchedulerBuilderConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$TaskSchedulerBuilderConfiguration
org.springframework.boot.autoconfigure.task.TaskSchedulingConfigurations$ThreadPoolTaskSchedulerBuilderConfiguration
org.springframework.boot.autoconfigure.thymeleaf.TemplateEngineConfigurations$DefaultTemplateEngineConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$DefaultTemplateResolverConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration$ThymeleafWebMvcConfiguration$ThymeleafViewResolverConfiguration
org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration$TomcatWebServerFactoryCustomizerConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
org.springframework.boot.context.internalConfigurationPropertiesBinder
org.springframework.boot.context.properties.BoundConfigurationProperties
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.boot.context.properties.EnableConfigurationPropertiesRegistrar.methodValidationExcludeFilter
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration
org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$RestartConfiguration
org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer$DependsOnDatabaseInitializationPostProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.event.internalEventListenerFactory
org.springframework.context.event.internalEventListenerProcessor
parameterNamesModule
preserveErrorControllerTargetClassPostProcessor
propertySourcesPlaceholderConfigurer
readingsnsApplication
requestContextFilter
requestMappingHandlerAdapter
requestMappingHandlerMapping
resourceHandlerMapping
restClientBuilder
restClientBuilderConfigurer
restClientSsl
restTemplateBuilder
restTemplateBuilderConfigurer
restartingClassPathChangedEventListener
routerFunctionMapping
server-org.springframework.boot.autoconfigure.web.ServerProperties
servletWebServerFactoryCustomizer
simpleAsyncTaskExecutorBuilder
simpleAsyncTaskSchedulerBuilder
simpleControllerHandlerAdapter
spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties
spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties
spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties
spring.lifecycle-org.springframework.boot.autoconfigure.context.LifecycleProperties
spring.mvc-org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties
spring.servlet.multipart-org.springframework.boot.autoconfigure.web.servlet.MultipartProperties
spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties
spring.ssl-org.springframework.boot.autoconfigure.ssl.SslProperties
spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties
spring.task.scheduling-org.springframework.boot.autoconfigure.task.TaskSchedulingProperties
spring.thymeleaf-org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties
spring.web-org.springframework.boot.autoconfigure.web.WebProperties
sslBundleRegistry
sslPropertiesSslBundleRegistrar
standardJacksonObjectMapperBuilderCustomizer
stringHttpMessageConverter
taskExecutorBuilder
taskSchedulerBuilder
templateEngine
themeResolver
threadPoolTaskExecutorBuilder
threadPoolTaskSchedulerBuilder
thymeleafViewResolver
tomcatServletWebServerFactory
tomcatServletWebServerFactoryCustomizer
tomcatWebServerFactoryCustomizer
viewControllerHandlerMapping
viewNameTranslator
viewResolver
webServerFactoryCustomizerBeanPostProcessor
websocketServletWebServerCustomizer
welcomePageHandlerMapping
welcomePageNotAcceptableHandlerMapping

この長いリストを見ると、だいぶ気分が悪くなる。が、まあ、仕方がないね。ちなみに、この@Beanを付けたメソッドは、HelloWorldControllerに最初間違えて付けちゃった。でも、動く。キモいねー

次が単体テストなんだが・・・エンドポイントのテストをしようとしている。キモい。個人的なことを言えば、RESTのエンドポイントのテストは単体テストだとは思わない。これはE2Eとユニットテストの中間に位置するもので、Postmanとかでテストしたい。まあ、Postman使わずにテストできたら便利じゃないかと言われればそうだけども、若者にこれをユニットテストだと思ってほしくないので、ここはとりあえず飛ばす。こういうものがあるんだということは覚えておこう。

というか、build.gradleの依存のところに

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

勝手に入っているspring-boot-starter-testを消したいなあ・・・

さて、その次には監視とかのアクチュエーターを追加するところ。 あー、こういうのは便利だね。でも、とりあえず飛ばしても良いだろう。

starterと仲良くなるには、だいぶ時間がかかりそうだなー

というわけで、いったんここまでで終わりにしようか。