简单 Spring Boot

本文的目的是简单介绍Spring Boot的一些入门配置的Quick Start,帮助快速上手;

不涉及Spring Boot的概念的介绍讲解,如需了解请参考Spring Boot 官方文档

有关Spring Boot的优缺点和适用场景的文章已经很多,还请自行Baidu/Google;


  • 开始

Spring Boot采用约定优于配置的方式,相比原来Spring框架,配置有了大幅的简化,不仅配置文件写起来简单多了,而且pom.xml的依赖配置也进行了优化,具体请参考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>

只需要这样几行简单的配置,就能写基于Spring MVC的Hello World了

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

然后写个入口

@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);
  }
}

启动main方法,运行、调试都很简单

  • 加上日志

开始一个项目的时候,日志是比不可少的,先把日志加上,避免了System.out.println的泛滥。
Spring Boot的默认配置文件是 application.properties, 不使用默认配置的选项全都在这里定义
Spring Boot 默认使用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配置文件,如果使用默认配置文件名logback-spring.xml,则可以直接生效,无需添加一行配置
如果想使用log4j2或其他的请参考howto logging

  • 写个测试

注:standalone的Spring Boot程序只允许在jar中存在一个main方法

所以原来习惯写个main方法来测试代码的同学,是时候了解怎样写测试了,请移步到Junit深造
首先,添加依赖

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

测试的starter已经包含了测试工具: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();
  }
}

测试例子请看 spring-boot-sample-test

  • 数据库连接池

数据库连接池也是大多数项目的标配,在Spring Boot中引入也十分简单
以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>

请使用tomcat 和 C3p0 连接池的同学移驾 HikariCP 补课

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会自动识别这些配置并加载jdbcTemplate和dataSource的bean

  • Mybatis

Hibernate,JPA,JOOQ的同学请参考 [Working With Database](29. Working with SQL databases)
Mybatis 开发了自己的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);
}

当然如果遇到要写一些复杂的SQL或者需要include SQL的话,使用xml文件来写SQL会更方便,只需给定xml文件的未知即可 mybatis-mapper-locations=classpath*:**/mapper/*
详细Mybatis的请参考 mybatis-spring-boot-autoconfigure

Flyway的具体使用方法请移步官网参考
添加pom依赖

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

配置中加入:

flyway.enabled=true
flyway.schemas=test

脚本默认位置:classpath:db\migration
搞定,程序启动自动执行数据库脚本

  • 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

当Controller抛出异常后需要进行处理,以返回友好信息给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

容器处理失败的请求会自动转到 /error 来处理,实现ErrorController接口来自定义该异常的处理机制
@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一样,定义一个拦截器

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一样

开发过程中,总会遇到本机环境、CI环境,测试环境、UAT环境、生产环境等等不同环境间的切换,往往因为环境的不同需要写不同的配置文件,又要有不同的脚本去替换这些配置文件,然后又要有一套版本管理系统把配置文件、脚本管理起来。
在Spring Boot中,这些问题统统可以自动处理。我们可以通过定义一个启动参数来指定当前启动的环境是那个,那么该环境的配置文件就会被加载执行。
启动参数:java -jar -Dspring.profiles.active=production app.jar
Spring Boot 首先会加载默认的配置文件application.properties,然后会根据指定的参数加载特定环境的配置文件application-*.properties,例如:application-production.properties
而我们要做的只是将配置文件放到指定的config路径下即可。

目录结构
  • 定时器

增加定时器也很容易,使用@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 * * * ?

如果需要在不同的环境有不同的执行策略,可以通过在不同环境的配置文件中定义不同Expression来做
如果只需要在某些环境中执行,还可以指定bean的加载环境:

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

这样,只有在指定了环境是UAT或Production时,Spring Boot才会初始化这个bean

  • 加个开机提示

No Bug

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

Spring Boot提供了一些监控管理应用程序的功能,例如JVM运行时资源监控、应用负载等
请自行查阅文档吧。
注:这些监控管理功能一定要配合Security使用,要有权限控制,建议生产环境时将这些权限控制在本机或内网。

  • 最后

到这里,一个简单的Spring Boot Web 应用应该可以顺利跑起来了,请在使用前认真阅读官方文档

  • Demo 下载

整理好会提供。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容