第3章 一鍵啟動應(yīng)用程序
3.1 SpringBoot版的Restful Hello,World
3.1.1 Spring Boot CLI groovy版Hello World
首先安裝Spring Boot CLI,參考文檔:http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-installing-the-cli
在命令行驗(yàn)證spring環(huán)境安裝成功:
$ spring --version
Spring CLI v1.3.6.RELEASE
隨便打開編輯器,敲入如下代碼:
@Controller
class HelloController{
@RequestMapping("/hello")
@ResponseBody
String hello(){
return "Hello World!"
}
}
然后在命令行執(zhí)行:
$ spring run HelloController.groovy
你將看到如下輸出日志:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.3.6.RELEASE)
2017-04-04 21:37:29.685 INFO 87317 --- [ runner-0] o.s.boot.SpringApplication : Starting application on jacks-MacBook-Air.local with PID 87317 (/Users/jack/.m2/repository/org/springframework/boot/spring-boot/1.3.6.RELEASE/spring-boot-1.3.6.RELEASE.jar started by jack in /Users/jack/book)
2017-04-04 21:37:29.699 INFO 87317 --- [ runner-0] o.s.boot.SpringApplication : No active profile set, falling back to default profiles: default
2017-04-04 21:37:30.367 INFO 87317 --- [ runner-0] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30cc0018: startup date [Tue Apr 04 21:37:30 CST 2017]; root of context hierarchy
2017-04-04 21:37:33.757 INFO 87317 --- [ runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-04-04 21:37:33.790 INFO 87317 --- [ runner-0] o.apache.catalina.core.StandardService : Starting service Tomcat
2017-04-04 21:37:33.793 INFO 87317 --- [ runner-0] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.36
2017-04-04 21:37:33.986 INFO 87317 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader : Unknown loader org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader@67cc18dd class org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader
2017-04-04 21:37:34.037 INFO 87317 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-04-04 21:37:34.038 INFO 87317 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3673 ms
2017-04-04 21:37:34.538 INFO 87317 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-04-04 21:37:34.552 INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-04-04 21:37:34.553 INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-04-04 21:37:34.553 INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-04-04 21:37:34.553 INFO 87317 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-04-04 21:37:35.309 INFO 87317 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@30cc0018: startup date [Tue Apr 04 21:37:30 CST 2017]; root of context hierarchy
2017-04-04 21:37:35.482 INFO 87317 --- [ runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello]}" onto public java.lang.String HelloController.hello()
2017-04-04 21:37:35.485 INFO 87317 --- [ 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)
2017-04-04 21:37:35.486 INFO 87317 --- [ 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)
2017-04-04 21:37:35.523 INFO 87317 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:35.524 INFO 87317 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:35.606 INFO 87317 --- [ runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 21:37:36.225 INFO 87317 --- [ runner-0] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-04-04 21:37:36.397 INFO 87317 --- [ runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-04 21:37:36.403 INFO 87317 --- [ runner-0] o.s.boot.SpringApplication : Started application in 8.263 seconds (JVM running for 12.897)
一個極簡的Restful Hello World就搞定了。瀏覽器訪問:http://localhost:8080/hello(或者curl http://localhost:8080/hello)
你會看到輸出:
Hello World!
3.1.2 常規(guī)的Java版的Hello World
1.命令行輸入:
$ spring init -dweb --build gradle
Using service at https://start.spring.io
Content saved to 'demo.zip'
2.解壓zip何址,導(dǎo)入idea中
你將看到如下一個Demo Application
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── DemoApplication.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
└── test
└── java
└── com
└── example
└── DemoApplicationTests.java
14 directories, 8 files
其中DemoApplication.java代碼如下:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
build.gradle配置文件如下:
buildscript {
ext {
springBootVersion = '1.5.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
OK,我們直接Run
同樣的位仁,你講看到如下日志:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.2.RELEASE)
2017-04-04 22:51:58.995 INFO 89676 --- [ main] com.example.DemoApplication : Starting DemoApplication on jacks-MacBook-Air.local with PID 89676 (/Users/jack/book/demo/build/classes/main started by jack in /Users/jack/book/demo)
2017-04-04 22:51:58.998 INFO 89676 --- [ main] com.example.DemoApplication : No active profile set, falling back to default profiles: default
2017-04-04 22:51:59.191 INFO 89676 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6950e31: startup date [Tue Apr 04 22:51:59 CST 2017]; root of context hierarchy
2017-04-04 22:52:02.718 INFO 89676 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-04-04 22:52:02.750 INFO 89676 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2017-04-04 22:52:02.754 INFO 89676 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-04-04 22:52:02.974 INFO 89676 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2017-04-04 22:52:02.974 INFO 89676 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3788 ms
2017-04-04 22:52:03.173 INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2017-04-04 22:52:03.198 INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-04-04 22:52:03.199 INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-04-04 22:52:03.199 INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-04-04 22:52:03.200 INFO 89676 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2017-04-04 22:52:03.758 INFO 89676 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6950e31: startup date [Tue Apr 04 22:51:59 CST 2017]; root of context hierarchy
2017-04-04 22:52:03.931 INFO 89676 --- [ main] 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)
2017-04-04 22:52:03.932 INFO 89676 --- [ main] 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)
2017-04-04 22:52:03.986 INFO 89676 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:03.987 INFO 89676 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:04.076 INFO 89676 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-04-04 22:52:04.334 INFO 89676 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2017-04-04 22:52:04.447 INFO 89676 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-04 22:52:04.455 INFO 89676 --- [ main] com.example.DemoApplication : Started DemoApplication in 6.41 seconds (JVM running for 7.608)
通過日志蛇券,我們可以看到SpringBootApplication大致的啟動流程煤率。這個過程中,很大一部分工作是在做Spring應(yīng)用的初始化蜕依。
Initializing Spring embedded WebApplicationContext
同時,嵌入式的servlet容器tomcat的啟動,org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext是關(guān)鍵類样眠。對細(xì)節(jié)感興趣的可以直接debug一下源碼友瘤。
上面的代碼雖然看不出什么效果。但SpringBootApplication的基本架構(gòu)流程已經(jīng)基本在里面了檐束。SpringBoot通過注解@SpringBootApplication辫秧,完成了自動配置的工作。(源碼參見: org.springframework.boot.autoconfigure.SpringBootApplication)
3.寫個HelloWorldController
package com.example.controller;
import java.util.Date;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by jack on 2017/4/4.
*/
@RestController
public class HelloWorldController {
@RequestMapping("hello")
String hello() {
return "Hello World! " + new Date();
}
}
直接Run DemoApplication被丧,我們可以看到有如下的日志:
...
Mapped "{[/hello]}" onto java.lang.String com.example.controller.HelloWorldController.hello()
...
瀏覽器訪問:http://localhost:8080/hello
我們將看到如下輸出:
Hello World! Tue Apr 04 23:08:33 CST 2017
另外盟戏,如果是在命令行運(yùn)行,使用SpringBoot gradle插件的執(zhí)行:
$gradle bootRun
使用SpringBoot maven插件的執(zhí)行:
$mvn spring-boot:run
3.2 啟動Springboot的自動配置@EnableAutoConfiguration
SpringBoot AutoConfiguration的原理是通過Spring的@Conditional注解實(shí)現(xiàn)甥桂。@Conditional以編程的方式確定的bean的狀態(tài)柿究,來決定哪些bean是要初始化到容器中(根據(jù)外界條件注冊不同Bean)。
@EnableAutoConfiguration的意思是啟用Spring應(yīng)用程序上下文的自動配置黄选,通過掃描CLASSPATH里面所有的組件蝇摸,然后基于條件來決定是否注冊bean來使得Spring的ApplicationContext自動配置。
比如說办陷,Spring檢測jdbcTemplate是否在classpath貌夕?if true, 看看bean容器里面是否有DataSource ? if true, 配置一個JdbcTemplate的Bean到容器中。
看EnableAutoConfiguration源碼:
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以發(fā)現(xiàn)民镜,這里Import了EnableAutoConfigurationImportSelector啡专。
Import主要是配合Configuration來使用的,用來導(dǎo)出更多的Configuration類殃恒,ConfigurationClassPostProcessor會讀取Import的內(nèi)容來實(shí)現(xiàn)具體的邏輯植旧。
EnableAutoConfigurationImportSelector實(shí)現(xiàn)了DeferredImportSelector接口,并實(shí)現(xiàn)了selectImports方法,用來導(dǎo)出Configuration類离唐。
String[] selectImports(AnnotationMetadata importingClassMetadata);
導(dǎo)出的類是通過SpringFactoriesLoader.loadFactoryNames()讀取了ClassPath下面的META-INF/spring.factories文件病附,這個文件內(nèi)容大致如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
......
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
其中EmbeddedServletContainerAutoConfiguration是實(shí)現(xiàn)web服務(wù)的主要的配置類。這個類會根據(jù)當(dāng)前存在的類的信息注入必要的EmbeddedServletContainerFactory類亥鬓。
spring-boot-starter-tomcat引入了tomcat的依賴完沪,所以EmbeddedServletContainerAutoConfiguration發(fā)現(xiàn)存在Tomcat.class就會注入TomcatEmbeddedServletContainerFactory來內(nèi)置web容器。
當(dāng)然嵌戈,如果你想排除一些bean的自動注入覆积,你可以用Class<?>[] exclude() 或者String[] excludeName()。
SpringBoot在spring-boot-autoconfigure-{版本號}.jar里面提供了很多AutoConfiguration的類來負(fù)責(zé)注冊各種不同的組件熟呛。這些組件幾乎涵蓋了Spring開發(fā)中需要的絕大部分的配置宽档。
本章源代碼
https://github.com/EasySpringBoot/HelloWorld/tree/hello_world_2017.4.4