簡(jiǎn)單 Spring Boot

本文的目的是簡(jiǎn)單介紹Spring Boot的一些入門配置的Quick Start,幫助快速上手;

不涉及Spring Boot的概念的介紹講解谐鼎,如需了解請(qǐng)參考Spring Boot 官方文檔佛致;

有關(guān)Spring Boot的優(yōu)缺點(diǎn)和適用場(chǎng)景的文章已經(jīng)很多,還請(qǐng)自行Baidu/Google惜浅;


  • 開始

Spring Boot采用約定優(yōu)于配置的方式,相比原來Spring框架,配置有了大幅的簡(jiǎn)化伏嗜,不僅配置文件寫起來簡(jiǎn)單多了赡矢,而且pom.xml的依賴配置也進(jìn)行了優(yōu)化,具體請(qǐng)參考Spring Boot Starter


<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.BUILD-SNAPSHOT</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

只需要這樣幾行簡(jiǎn)單的配置阅仔,就能寫基于Spring MVC的Hello World了

@RestController
class HelloWorld { 
  @RequestMapping("/")
  String home() { 
    return "Hello World!"; 
  }
}

然后寫個(gè)入口

@SpringBootApplication
@EnableAutoConfiguration
public class Application {
  private static final Logger logger = LoggerFactory.getLogger(Application.class);
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

啟動(dòng)main方法吹散,運(yùn)行、調(diào)試都很簡(jiǎn)單

  • 加上日志

開始一個(gè)項(xiàng)目的時(shí)候八酒,日志是比不可少的空民,先把日志加上,避免了System.out.println的泛濫羞迷。
Spring Boot的默認(rèn)配置文件是 application.properties界轩, 不使用默認(rèn)配置的選項(xiàng)全都在這里定義
Spring Boot 默認(rèn)使用logback,無需任何配置就可以使用了衔瓮,并提供了

logging.config= # Location of the logging configuration file. For instance classpath:logback.xml for Logback
logging.exception-conversion-word=%wEx # Conversion word used when logging
exceptions.logging.file= # Log file name. For instance myapp.log
logging.level.*= # Log levels severity mapping. For instancelogging.level.org.springframework=DEBUG
logging.path= # Location of the log file. For instance /var/log
logging.pattern.console= # Appender pattern for output to the console. Only supported with the default logback setup.logging.pattern.file= # Appender pattern for output to the file. Only supported with the default logback
setup.logging.pattern.level= # Appender pattern for log level (default %5p). Only supported with the default logback
setup.logging.register-shutdown-hook=false # Register a shutdown hook for the logging system when it is initialized.

如果有更具體配置浊猾,如async appender,remote appender等還需使用原有的XML配置文件热鞍,如果使用默認(rèn)配置文件名logback-spring.xml葫慎,則可以直接生效,無需添加一行配置
如果想使用log4j2或其他的請(qǐng)參考howto logging

  • 寫個(gè)測(cè)試

注:standalone的Spring Boot程序只允許在jar中存在一個(gè)main方法

所以原來習(xí)慣寫個(gè)main方法來測(cè)試代碼的同學(xué)薇宠,是時(shí)候了解怎樣寫測(cè)試了偷办,請(qǐng)移步到Junit深造
首先,添加依賴

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

測(cè)試的starter已經(jīng)包含了測(cè)試工具:Junit Hamcrest Mockito等澄港,可以直接上手寫case了

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestDatabase
public class UserVehicleControllerApplicationTests {   
  @Autowired   
  private MockMvc mvc;   
  @Autowired   
  private ApplicationContext applicationContext;   
  @MockBean   
  private UserVehicleService userVehicleService;   
  @Test   
  public void getVehicleWhenRequestingTextShouldReturnMakeAndModel() throws Exception {
    given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new
    VehicleDetails("Honda", "Civic"));
     this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
      .andExpect(status().isOk()).andExpect(content().string("Honda Civic"));   
   } 
  @Test
  public void welcomeCommandLineRunnerShouldBeAvailable() throws Exception {
     assertThat(this.applicationContext.getBean(WelcomeCommandLineRunner.class).isNotNull();
  }
}

測(cè)試?yán)诱?qǐng)看 spring-boot-sample-test

  • 數(shù)據(jù)庫(kù)連接池

數(shù)據(jù)庫(kù)連接池也是大多數(shù)項(xiàng)目的標(biāo)配椒涯,在Spring Boot中引入也十分簡(jiǎn)單
以mysql為例,pom中添加依賴:

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.4.7</version>
<scope>compile</scope>
</dependency>

請(qǐng)使用tomcat 和 C3p0 連接池的同學(xué)移駕 HikariCP 補(bǔ)課

application.properties 中添加:

datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
datasource.username=user
datasource.password=password
spring.datasource.url=${datasource.url}
spring.datasource.username=${datasource.username}
spring.datasource.password=${datasource.password}
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.max-lifetime=30000
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.data-source-properties.prepStmtCacheSize=250
spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=2048
spring.datasource.hikari.data-source-properties.cachePrepStmts=true
spring.datasource.hikari.data-source-properties.useServerPrepStmts=true

這樣Spring Boot會(huì)自動(dòng)識(shí)別這些配置并加載jdbcTemplate和dataSource的bean

  • Mybatis

Hibernate,JPA,JOOQ的同學(xué)請(qǐng)參考 [Working With Database](29. Working with SQL databases)
Mybatis 開發(fā)了自己的starter依賴

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>

然后就可以像往常一樣編寫Dao了

@Mapper
public interface DemoDao {    
  @Select("select * from test")    
  public List<TestDomain> get();    
  @Insert("insert into test (name,age) values (#{name},#{age})")    
  void insert(@Param("name") String name, @Param("age") String age);
}

當(dāng)然如果遇到要寫一些復(fù)雜的SQL或者需要include SQL的話回梧,使用xml文件來寫SQL會(huì)更方便废岂,只需給定xml文件的未知即可 mybatis-mapper-locations=classpath*:**/mapper/*
詳細(xì)Mybatis的請(qǐng)參考 mybatis-spring-boot-autoconfigure

  • 數(shù)據(jù)庫(kù)管理 Flyway

Flyway的具體使用方法請(qǐng)移步官網(wǎng)參考
添加pom依賴

<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>

配置中加入:

flyway.enabled=true
flyway.schemas=test

腳本默認(rèn)位置:classpath:db\migration
搞定,程序啟動(dòng)自動(dòng)執(zhí)行數(shù)據(jù)庫(kù)腳本

  • Redis

廢話不說了
pom

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

properties

spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.pool.max-active=-1
spring.redis.pool.max-idle=100
spring.redis.port=6379
spring.redis.timeout=0

code

@AutowiredRedisTemplate redisTemplate;
  • 異常處理之ExceptionHandler

當(dāng)Controller拋出異常后需要進(jìn)行處理狱意,以返回友好信息給clients

  @ControllerAdvice(basePackageClasses = CoreApplication.class)
  public class DemoControllerAdvice {
  @ExceptionHandler(DemoException.class)
  @ResponseBody
  ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
  HttpStatus status = getStatus(request);
  return new ResponseEntity<DemoError>(new DemoError(status.value(), ex.getMessage()), status);
  }
  @ExceptionHandler(Exception.class)
  @ResponseBody
  ResponseEntity<?> handleException(HttpServletRequest request, Throwable ex) {
  HttpStatus status = getStatus(request);
  return new ResponseEntity<DemoError>(new DemoError(status.value(), ex.getMessage()), status);
  }
  private HttpStatus getStatus(HttpServletRequest request) {
  Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  if (statusCode == null) {
    return HttpStatus.INTERNAL_SERVER_ERROR;
  }
  return HttpStatus.valueOf(statusCode);
  }
  }
  • 異常處理之ErrorController

容器處理失敗的請(qǐng)求會(huì)自動(dòng)轉(zhuǎn)到 /error 來處理湖苞,實(shí)現(xiàn)ErrorController接口來自定義該異常的處理機(jī)制
@RestController
public class DemoErrorController implements ErrorController {
@Autowired
ErrorAttributes errorAttributes;

    @RequestMapping(value = "/error")
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = this.getStatus(request);
        return new ResponseEntity(body, status);
    }
    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
       return false;
    }
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
        return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
    }
    protected HttpStatus getStatus(HttpServletRequest request) {
        Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
        if(statusCode == null) {
            return HttpStatus.INTERNAL_SERVER_ERROR;
        } else {
            try {
                return HttpStatus.valueOf(statusCode.intValue());
            } catch (Exception var4) {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }
        }
    }
    @Override
    public String getErrorPath() {
        return "/error";
    }
  }
  • 自定義攔截器

與Spring MVC一樣,定義一個(gè)攔截器

public class DemoInterceptor  implements HandlerInterceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("pre handle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        logger.info("post handle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {
        logger.info("after completion");
    }
  }

然后在WebMvcConfigurerAdapter的子類中把定義好的攔截器添加到容其中

    @Configuration
    public class DemoConfiguration  extends WebMvcConfigurerAdapter {

        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
        }
    }

其他的與Spring MVC一樣

開發(fā)過程中髓涯,總會(huì)遇到本機(jī)環(huán)境袒啼、CI環(huán)境哈扮,測(cè)試環(huán)境纬纪、UAT環(huán)境蚓再、生產(chǎn)環(huán)境等等不同環(huán)境間的切換,往往因?yàn)榄h(huán)境的不同需要寫不同的配置文件包各,又要有不同的腳本去替換這些配置文件摘仅,然后又要有一套版本管理系統(tǒng)把配置文件、腳本管理起來问畅。
在Spring Boot中娃属,這些問題統(tǒng)統(tǒng)可以自動(dòng)處理。我們可以通過定義一個(gè)啟動(dòng)參數(shù)來指定當(dāng)前啟動(dòng)的環(huán)境是那個(gè)护姆,那么該環(huán)境的配置文件就會(huì)被加載執(zhí)行矾端。
啟動(dòng)參數(shù):java -jar -Dspring.profiles.active=production app.jar
Spring Boot 首先會(huì)加載默認(rèn)的配置文件application.properties,然后會(huì)根據(jù)指定的參數(shù)加載特定環(huán)境的配置文件application-*.properties卵皂,例如:application-production.properties
而我們要做的只是將配置文件放到指定的config路徑下即可秩铆。

目錄結(jié)構(gòu)
  • 定時(shí)器

增加定時(shí)器也很容易,使用@EnableScheduling注解

    @Configuration
    @EnableScheduling
    public class DemoJob {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Scheduled(cron="${Demo.Cron.Expression}")
        public void fire() {
            logger.info("scheduler fired");
        }
    }

定義Cron Expression
Demo.Cron.Expression=0 0/1 * * * ?

如果需要在不同的環(huán)境有不同的執(zhí)行策略灯变,可以通過在不同環(huán)境的配置文件中定義不同Expression來做
如果只需要在某些環(huán)境中執(zhí)行殴玛,還可以指定bean的加載環(huán)境:

@EnableScheduling
@Profile({ "UAT", "Production" })
public class DemoJob{...}

這樣,只有在指定了環(huán)境是UAT或Production時(shí)添祸,Spring Boot才會(huì)初始化這個(gè)bean

  • 加個(gè)開機(jī)提示

No Bug

在配置文件中加入
# BANNER banner.charset=UTF-8 banner.location=classpath:banner.txt

Spring Boot提供了一些監(jiān)控管理應(yīng)用程序的功能滚粟,例如JVM運(yùn)行時(shí)資源監(jiān)控、應(yīng)用負(fù)載等
請(qǐng)自行查閱文檔吧刃泌。
注:這些監(jiān)控管理功能一定要配合Security使用凡壤,要有權(quán)限控制,建議生產(chǎn)環(huán)境時(shí)將這些權(quán)限控制在本機(jī)或內(nèi)網(wǎng)耙替。

  • 最后

到這里鲤遥,一個(gè)簡(jiǎn)單的Spring Boot Web 應(yīng)用應(yīng)該可以順利跑起來了,請(qǐng)?jiān)谑褂们罢J(rèn)真閱讀官方文檔林艘。

  • Demo 下載

整理好會(huì)提供盖奈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狐援,隨后出現(xiàn)的幾起案子钢坦,更是在濱河造成了極大的恐慌,老刑警劉巖啥酱,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爹凹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡镶殷,警方通過查閱死者的電腦和手機(jī)禾酱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人颤陶,你說我怎么就攤上這事颗管。” “怎么了滓走?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵垦江,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我搅方,道長(zhǎng)比吭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任姨涡,我火速辦了婚禮衩藤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涛漂。我一直安慰自己慷彤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布怖喻。 她就那樣靜靜地躺著底哗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锚沸。 梳的紋絲不亂的頭發(fā)上跋选,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音哗蜈,去河邊找鬼前标。 笑死,一個(gè)胖子當(dāng)著我的面吹牛距潘,可吹牛的內(nèi)容都是我干的炼列。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼音比,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼俭尖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起洞翩,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稽犁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后骚亿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體已亥,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年来屠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虑椎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片震鹉。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捆姜,靈堂內(nèi)的尸體忽然破棺而出传趾,到底是詐尸還是另有隱情,我是刑警寧澤娇未,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布墨缘,位于F島的核電站星虹,受9級(jí)特大地震影響零抬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宽涌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一平夜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卸亮,春花似錦忽妒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溶诞,卻和暖如春鸯檬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背螺垢。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工喧务, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枉圃。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓功茴,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親孽亲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坎穿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容