sdkman(gvmの頃からですが)を使って、Spring Bootをインストールすることができます。
$ sdk install springboot
ここでインストールされるspringコマンド(Spring Boot CLIって言ったらいいんでしょうか?)で、簡単にSpring Bootアプリケーションが書けるようなのですが、最近になってやっと試してみたのでメモ。
インストール
sdkman、もしくはzip、tar.gzを展開してインストールします。
Installing the Spring Boot CLI
今回は、sdkmanを使用しました。「springboot」をインストールします。
$ sdk install springboot Downloading: springboot 1.3.2.RELEASE % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 9098k 100 9098k 0 0 749k 0 0:00:12 0:00:12 --:--:-- 1028k Installing: springboot 1.3.2.RELEASE Done installing! Do you want springboot 1.3.2.RELEASE to be set as default? (Y/n): y Setting springboot 1.3.2.RELEASE as default.
確認。
$ spring version Spring CLI v1.3.2.RELEASE
これで、インストールは完了です。
簡単なSpring Bootアプリケーションを書いてみる
ドキュメントに習って、サンプルアプリケーションを動かしてみます。
Running applications using the CLI
アプリケーションは、Groovyで記述します。
書いたスクリプトはこちら。
hello-mvc.groovy
@RestController class WebApplication { @RequestMapping('helloworld') def hello() { 'Hello World' } }
実行。
$ spring run hello-mvc.groovy
初回は依存関係の解決に時間がかかったりしますが、恐ろしいことにこれだけでSpring MVCのアプリケーションが組み込みTomcatと共に起動します…。
Resolving dependencies................................ . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.3.2.RELEASE) 2016-02-14 22:37:51.380 INFO 124992 --- [ runner-0] o.s.boot.SpringApplication : Starting application on node with PID 124992 (/path/to/.m2/repository/org/springframework/boot/spring-boot/1.3.2.RELEASE/spring-boot-1.3.2.RELEASE.jar started by xxxxx in //path/to) 2016-02-14 22:37:51.473 INFO 124992 --- [ runner-0] o.s.boot.SpringApplication : No active profile set, falling back to default profiles: default 2016-02-14 22:37:52.733 INFO 124992 --- [ runner-0] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@695ddd49: startup date [Sun Feb 14 22:37:52 JST 2016]; root of context hierarchy 2016-02-14 22:37:55.156 INFO 124992 --- [ runner-0] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]] 2016-02-14 22:37:56.740 INFO 124992 --- [ runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http) 2016-02-14 22:37:56.817 INFO 124992 --- [ runner-0] o.apache.catalina.core.StandardService : Starting service Tomcat 2016-02-14 22:37:56.819 INFO 124992 --- [ runner-0] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.30 2016-02-14 22:37:57.136 INFO 124992 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader : Unknown loader org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader@6c5060ab class org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader 2016-02-14 22:37:57.255 INFO 124992 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2016-02-14 22:37:57.255 INFO 124992 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 4523 ms 2016-02-14 22:37:58.359 INFO 124992 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2016-02-14 22:37:58.369 INFO 124992 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2016-02-14 22:37:58.502 INFO 124992 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2016-02-14 22:37:58.503 INFO 124992 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2016-02-14 22:37:58.503 INFO 124992 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2016-02-14 22:37:58.781 INFO 124992 --- [ost-startStop-1] o.a.c.util.SessionIdGeneratorBase : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [212] milliseconds. 2016-02-14 22:37:59.380 INFO 124992 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@695ddd49: startup date [Sun Feb 14 22:37:52 JST 2016]; root of context hierarchy 2016-02-14 22:37:59.490 INFO 124992 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/helloworld]}" onto public java.lang.Object WebApplication.hello() 2016-02-14 22:37:59.494 INFO 124992 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2016-02-14 22:37:59.495 INFO 124992 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2016-02-14 22:37:59.543 INFO 124992 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-02-14 22:37:59.543 INFO 124992 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-02-14 22:37:59.616 INFO 124992 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-02-14 22:38:00.901 INFO 124992 --- [ runner-0] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2016-02-14 22:38:01.298 INFO 124992 --- [ runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2016-02-14 22:38:01.325 INFO 124992 --- [ runner-0] o.s.boot.SpringApplication : Started application in 11.18 seconds (JVM running for 50.941)
確認。
$ curl http://localhost:8080/helloworld Hello World
コマンドライン引数を渡す場合は、「--」の後ろに入力するのだとか。例えば、組み込みTomcatのリッスンポートを変える場合はこちら。
$ spring run hello-mvc.groovy -- --server.port=9000
「--」の後に、「--server.port=9000」が入ります。
JavaVMにオプションを渡す場合は、JAVA_OPTSを使って設定するそうです。
$ JAVA_OPTS=-Xmx1024m spring run hello-mvc.groovy
プロキシ設定が必要な場合なども、これを使えばいいかな?
デフォルトでimportされているもの
先ほどのスクリプトで、いきなり@RestControllerや@RequestMappingを使用しましたが、@Component、@RestController、@RequestMappingはデフォルトでimportされているそうです。
Grapeに依存関係を推測させる
こちらにも記載がありますが、特定のクラスを使うことで、Grapeにヒントを与えていくつかの自動importが有効になるようです。
このあたりの仕掛けは、以下のパッケージを見ればよさそうです。
例えば、Spring MVCの場合は@Controller、@RestController、@EnableWebMvcのいずれかを見付けると、Spring MVC関連のものをimportに加えます。
この他、JdbcTemplateや@EnableTransactionManagementなどにも反応するようなので、必要に応じて見ていくとよいでしょう。
また、@Grabアノテーションに以下のようにartifactIdを書くだけでも、Spring Bootのデフォルトの依存関係の中から推測してくれるようです。
@Grab('freemarker')
まあ、将来この動きは変わるかもよ、みたいなことが書かれていますが…。
その他
「spring test」でテスト、「spring jar [JARファイル名] [Groovyスクリプト]」でJARファイルが作れたり、「spring init」、「spring shell」などが使えるようですが、今回は割愛。
SpringのCache機能を使ったサンプル
というわけで、ここまでに紹介してきた機能を使ったサンプルを書いてみたいと思います。
Spring Cacheを使って、先ほどのSpring MVCのサンプルにCacheを入れてみます。追加するCacheはJCache(実装はHazelcast)とします。
で、書いたスクリプトはこちら。
hello-mvc-cache.groovy
import java.util.concurrent.TimeUnit @Grab('cache-api') @Grab('hazelcast') import javax.cache.Caching import javax.cache.configuration.MutableConfiguration import org.springframework.cache.jcache.JCacheCacheManager @EnableCaching @Configuration class Config { @Bean public CacheManager cacheManager() { def cacheManager = Caching.getCachingProvider().getCacheManager() cacheManager.createCache("simpleCache", new MutableConfiguration()) new JCacheCacheManager(cacheManager) } } @RestController @CacheConfig(cacheNames = 'simpleCache') class WebApplication { @RequestMapping('helloworld') @Cacheable def hello(@RequestParam message) { TimeUnit.SECONDS.sleep(5L) "Hello $message".toString() } }
CacheManagerの作成を追加したのと、RestControllerにはスリープを入れています。
で、Configurationの方ですが、@EnableCachingアノテーションがあるので以下の部分が動作します。
これにより、いくつかのorg.springframework.cache関連のパッケージがimportされます。
JCacheとHazelcastは、artifactIdだけで引き込んでいます。
@Grab('cache-api') @Grab('hazelcast')
では、起動。
$ spring run hello-mvc-cache.groovy
起動途中で、Hazelcastのログが現れるようになります。
2016-02-14 23:31:50.539 INFO 126713 --- [ runner-0] com.hazelcast.config.XmlConfigLocator : Loading 'hazelcast-default.xml' from classpath. 2016-02-14 23:31:51.344 INFO 126713 --- [ runner-0] c.h.instance.DefaultAddressPicker : [LOCAL] [dev] [3.5.4] Prefer IPv4 stack is true. 2016-02-14 23:31:51.357 INFO 126713 --- [ runner-0] c.h.instance.DefaultAddressPicker : [LOCAL] [dev] [3.5.4] Picked Address[172.17.0.1]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true 2016-02-14 23:31:51.691 INFO 126713 --- [ runner-0] com.hazelcast.spi.OperationService : [172.17.0.1]:5701 [dev] [3.5.4] Backpressure is disabled 2016-02-14 23:31:51.748 INFO 126713 --- [ runner-0] c.h.s.i.o.c.ClassicOperationExecutor : [172.17.0.1]:5701 [dev] [3.5.4] Starting with 4 generic operation threads and 8 partition operation threads. 2016-02-14 23:31:52.451 INFO 126713 --- [ runner-0] com.hazelcast.system : [172.17.0.1]:5701 [dev] [3.5.4] Hazelcast 3.5.4 (20151125 - 56676b2) starting at Address[172.17.0.1]:5701 2016-02-14 23:31:52.451 INFO 126713 --- [ runner-0] com.hazelcast.system : [172.17.0.1]:5701 [dev] [3.5.4] Copyright (c) 2008-2015, Hazelcast, Inc. All Rights Reserved. 2016-02-14 23:31:52.460 INFO 126713 --- [ runner-0] com.hazelcast.instance.Node : [172.17.0.1]:5701 [dev] [3.5.4] Creating MulticastJoiner 2016-02-14 23:31:52.465 INFO 126713 --- [ runner-0] com.hazelcast.core.LifecycleService : [172.17.0.1]:5701 [dev] [3.5.4] Address[172.17.0.1]:5701 is STARTING 2016-02-14 23:31:54.797 INFO 126713 --- [ runner-0] c.h.cluster.impl.MulticastJoiner : [172.17.0.1]:5701 [dev] [3.5.4] Members [1] { Member [172.17.0.1]:5701 this } 2016-02-14 23:31:54.858 INFO 126713 --- [ runner-0] com.hazelcast.core.LifecycleService : [172.17.0.1]:5701 [dev] [3.5.4] Address[172.17.0.1]:5701 is STARTED
動作確認。
1回目は低速ですが、2回目以降は高速になります。
## 1回目 $ time curl http://localhost:8080/helloworld?message=Groovy Hello Groovy real 0m5.451s user 0m0.009s sys 0m0.000s ## 2回目 $ time curl http://localhost:8080/helloworld?message=Groovy Hello Groovy real 0m0.065s user 0m0.006s sys 0m0.006s