Skip to content

Build One Push

yaoyasong edited this page May 11, 2016 · 4 revisions

Spring Boot + Gradle + Websocket 构建推送服务

介绍

Spring Boot经过长期的发展,已经逐渐成为我微服务开发的首选,本文以构建推送微服务为例,演示如何使用spring boot进行高效的微服务开发。

##使用的相关技术

  • gradle
  • spring boot及其starters
  • websocket
  • mongodb
  • apns
  • docker

Gradle

作为后起之秀,既支持原有的Maven仓库,又拥有简洁的语法,不仅仅可以做依赖管理,更是一个便捷的构建工具。可以说是不二之选。

build.gradle

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

repositories {
    jcenter()
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

Sprnig Boot

Starter

使用Spring boot比较简单,只要引用需要的starter即可。

  • spring-boot-starter-web web项目必选,自动包含了tomcat及spring-webmvc
  • spring-boot-starter-data-mongodb 我们使用mongodb数据库,直接使用spring data的mongodb支持,声明式的数据库访问,效率大大提升。
  • spring-boot-starter-websocket websocket支持
  • spring-boot-starter-security spring security支持,通用选择
  • spring-boot-starter-actuator 生产特性支持,这个也是利器,devops首选。

Dev tools

优秀的框架首先是对开发者友好,spring boot在这方面做的不错,Hot swapping特性使得开发时不用频繁启动服务。 本人使用的是IntelliJ IDEA,稍微麻烦些,IDEA的自动编译功能在程序运行或调试时不起作用,只能用Ctrl+F9 Hot swapping 在IDEA的配置如下: build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.3.3.RELEASE"
        classpath "org.springframework:springloaded:1.2.6.RELEASE"
    }
}
...
apply plugin: 'idea'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

这样,每次修改代码后手动Ctrl+F9就直接生效了。不过如果涉及到方法接口改变等还是需要重启服务的。

应用配置

spring boot支持properties和yaml两种方式,本人推荐使用yaml方式,缩进方式更易读。 application.yaml

spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/onepush
pushserver:
  env: DEV #开发或正式
server:
  port: 8080
...

引用自定义配置也特别容易,直接使用@ConfigurationProperties即可。 如:PushServerConfig.java

@ConfigurationProperties(prefix="pushserver")
public class PushServerConfig {
    private String env;
    get,set methods...
}

就会自动引用pushserver下相同名称的配置项。

validation

Spring 提供了Validator支持,提供了一些辅助类帮助开发者实现自定义校验逻辑。 实现步骤:

  • 实现自定义校验类 PushValidator.java
@Component
public class PushValidator implements Validator{
    @Override
    public boolean supports(Class<?> clazz) {
        return UserDevice.class.equals(clazz) || PushRequest.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmptyOrWhitespace(e, "appId", "field.required", new String[]{"appId"});
        ...
    }
}
  • 在controller中启用,将validator增加到binder中,使用@Valid ... BindingResult bindResult即可 PushController.java
    @InitBinder
    public void initBinder(DataBinder binder) {
        binder.setValidator(validator);
    }

    @RequestMapping(value="/register", method= RequestMethod.POST)
    Map<String,String> register(@Valid @RequestBody UserDevice userDevice, BindingResult bindResult) {
        if (bindResult.hasErrors()) {
            throw new PushParameterException(getErrorString(bindResult));
        }
        ...

异常处理

定义两类应用异常: PushParameterException -- 请求参数错误类异常 PushServiceException -- 服务处理类异常 区分这两类异常的主要作用是方便客户端针对不同异常可能会采用不同的错误处理机制。 如对于参数错误类异常提醒用户修改参数重新请求,对于服务处理类异常可以采用重试机制。

针对这两类异常,实现一个统一的应用异常处理程序。 PushExceptionAdvice.java

@ControllerAdvice
public class PushExceptionAdvice extends ResponseEntityExceptionHandler{
    @ExceptionHandler(PushParameterException.class)
    public ResponseEntity<ErrorResponse> handleParameterException(PushParameterException e) {
        ErrorResponse er = null;
        if (pushServerConfig.isProd()) {
            er = new ErrorResponse(e.getCode(),e.getMessage(),null);
        } else {
            er = new ErrorResponse(e.getCode(),e.getMessage(),e.getCause());
        }
        return new ResponseEntity<ErrorResponse>(er, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(PushServiceException.class)
    public ResponseEntity<ErrorResponse> handleServiceException(PushServiceException e) {
        ErrorResponse er = null;
        if (pushServerConfig.isProd()) {
            er = new ErrorResponse(e.getCode(),e.getMessage(),null);
        } else {
            er = new ErrorResponse(e.getCode(),e.getMessage(),e.getCause());
        }
        return new ResponseEntity<ErrorResponse>(er, HttpStatus.SERVICE_UNAVAILABLE);
    }

}