IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    SpringBoot 笔记

    Fish (fsh267@gmail.com)发表于 2017-05-30 00:00:00
    love 0

    post-getting-started-with-spring-boot

    营销测试工具工程实现, 基于 SpringBoot , 码了一段时间, 整理一下笔记, 真的特别好用!!! 最直观的感受有下面三方面:

    • 无 xml 配置!!!
    • 内嵌 tomcat
    • 组件选配

    1. 创建工程

    1.1 使用 IDEA 创建

    个人推荐使用 IDEA 来创建, 省事. 不需要下载, 解压, 顺带还指定安装目录.

    image image image

    1.2 官网创建工程

    打开官网 http://start.spring.io/, 填入 Group Artifact 等, 勾选依赖的组件, 点击 Generate Project, 下载 ZIP. 然后导入IDEA.


    创建后的目录结构如下:

    
    ├── mvnw
    ├── mvnw.cmd
    ├── pom.xml
    ├── promoboot.iml
    ├── src
    │   ├── main
    │   │   ├── java
    │   │   └── resources
    │   └── test
    │       └── java
    └── target
        ├── classes
        │   ├── application.properties
        │   └── com
        ├── generated-sources
        │   └── annotations
        ├── generated-test-sources
        │   └── test-annotations
        └── test-classes
            └── com
    

    2. 启动工程

    在启动文件中, 添加个 Controller 并启动测试一下, 默认的 server.port=8080

    @Controller
    @SpringBootApplication
    public class PromobootApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(PromobootApplication.class, args);
        }
    
        @RequestMapping("/")
        @ResponseBody
        String home() {
            return "Hello World!";
        }
    }
    

    image

    启动工程的方式有如下几个:

    2.1 SpringBoot启动 — IDEA 启动

    直接启动main函数

    2.2 SpringBoot启动 — mvn 命令启动

    执行下面命令, 直接启动:

    mvn spring-boot:run
    

    2.3 SpringBoot启动 — 打包部署启动

    打包并部署放到生产环境

    首先创建 Jar 包

    mvn package
    

    然后使用 java -jar 命令启动

    java -jar target/promoboot-0.0.1-SNAPSHOT.jar
    

    image

    2.4 SpringBoot 热部署

    改完代码, 每次启动工程挺费时的, SpringBoot 支持热部署需要添加 spring-boot-devtools 依赖, 可以手动添加, 也可以在创建 SpringBoot 时勾选 DevTools, 如下图:

    image

    下面说一下手工添加的方式:

    • 手动添加 devtools 依赖, 设置 optional = true
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional> 
    </dependency>
    
    • 使用 mvn spring-boot:run 启动
    mvn spring-boot:run
    

    可以看到多了一条监听 class 变更的日志

    18:26:43.631 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/fish/AliDrive/git/springbootdemo/target/classes/]
    

    此时修改代码, 就可以无脑看执行结果了!

    另外, IDEA 设置一下字段编译, 如下图

    image

    2.5 开启本地 Debug 端口

    参考文档Debug the application , 开启本地 Debug 端口使用如下方法:

    mvn spring-boot:run -Drun.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
    

    3. 配置文件读取与环境隔离

    初始化的工程, 自带了application.properties配置文件.

    3.1 使用 yaml 替换 properties

    其实框架默认支持 Ruby on Rails 惯用的 yaml 格式配置文件, 非常建议大家使用 yaml 来替换掉 properties , 配置文件看起来短多了, 对比如下:

    • 使用 properties
    server.port = 8080
    server.context-path = '/boot'
    
    • 使用 yaml
    server:
      port: 8080
      context-path: '/boot'
    

    此外, yaml 语法还支持变量定义等, 详细用法可以查看文档 YAML Syntax

    3.2 开发/测试/生产 环境配置隔离

    对于程序员, 手动改配置是一项不能忍的工作, 好在 spingboot 完美的支持配置文件隔离. 操作如下:

    新增 开发/测试/生产 配置文件:

    image

    配置 application.properties 中的内容, 比如启用 dev 环境, 设置 active = dev, 就启用了 application-dev.yaml :

    spring:
      profiles:
        active: dev
    

    本地开发时, 使用 mvn spring-boot:run 启动的工程, 可以自动生效.

    3.2 线上部署, 配置环境隔离

    上面提到过 Jar 包的部署方式, 同一套代码部署到不同环境时, 不需要修改配置文件, 使用下面的命令来部署:

    • 将服务部署到生产环境(prod)
    mvn install && java -jar target/promoboot-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
    

    3.4 读取配置文件中的值

    类似 sofa 框架的 @AppConfig 注解, SpringBoot 在读取配置文件时, 关键词是

    // 获取单个配置值
    @Value("${xx.oo}")
    
    // 自定义对象注入
    @Component
    @ConfigurationProperties
    

    3.4.1 读取单个配置的值

    application.yaml 配置如下:

    params:
      env: stable
    

    代码中使用如下方式获取:

    @Value("${params.env}")
    private String env;
    

    3.4.2 自定义个对象进行注入

    如果某一个类型的参数实在多, 使用上面的方式非常繁琐, SpringBoot 支持直接映射到对象中.

    apple:
      size: 20
      color: red
      weight: 300
    

    然后定义个 Apple 的类, 添加上 Component 和 ConfigurationProperties 注解.

    @Component
    @ConfigurationProperties(prefix = "apple")
    public class Apple {
    
        private String name;
    
        private int size;
    
        private String color;
    
        private int weight;
    
        public String getName() {
            return name;
        }
        ......
    

    最后, 引用方式如下:

    @Autowired
    private Apple apple;
    

    这样 Apple 的配置就映射进来了, 当然你也可以在他的属性上添加 @NotEmpty 等注解. 比如在 color 属性上添加 @NotEmpty, 配置文件中置空, 系统自动编译会直接报错的:

    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Binding to target Apple{name='lol', size=20, cllor='', weight=300} failed:
    
        Property: apple.color
        Value:
        Reason: may not be empty
    

    4. Controller

    • SpringMVC

    rcontroller-mvc

    • SpringBoot

    rcontroller-rest

    从上面两个图可见, 两个框架提供的功能是一样的, SpringBoot 更简单, 还挺好用:

    @RestController = @Controller + @ResponseBody
    
    @GetMapping(value="xx") = @RequestMapping(value = "xx", method = RequestMethod.GET)
    @PostMapping(value="xx") = @RequestMapping(value = "xx", method = RequestMethod.POST)
    

    5. 数据库操作 JPA

    操作数据库, 常见的 JDBC, MyBatis等, 不再赘述. 项目中使用的是 JPA, 非常简洁.

    5.1 什么是 JPA

    JPA(Java Persistence API)是Sun官方提出的Java持久化规范, 它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据, 目的是整合现有的 ORM 技术.
    

    5.2 工程配置

    5.2.1 添加依赖

    比如使用 mysql 数据库, 那么在 pom.xml 中添加 jpa mysql 依赖

            <!--使用spring-jpa-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
    
            <!--mysql 依赖添加-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    

    5.2.2 数据库配置

    编辑 application-dev.yaml 文件

    spring:
    # 数据库配置
      datasource:
        username: oooo
        password: xxxx
        url: jdbc:mysql://dev.lab.alipay.net:3306/promotion
        driver-class-name: com.mysql.jdbc.Driver
        # 配置 hibernate 属性
      jpa:
        hibernate:
          ddl-auto: update
        show-sql: true
    

    ddl-auto 是 hibernate 的配置属性, 对应的作用是:

    create: 删表, 重新创建表(慎用)
    update: 常用配置, ,第一次加载Hibernate时创建数据表, 以后加载HIbernate时只会根据model更新
    validate: 验证数据库表结构 
    

    5.2.3 创建实体

    比如创建一个 Robot 实体, 自定义一些属性:

    @Entity
    @Table(name = "robot")
    public class RobotDO {
    
        @Id
        @GeneratedValue
        private long id;
    
        @Column(nullable = false)
        private String robotName;
    
        private String size;
    
        @Column(name = "robot_type")
        private String type;
    
        private Date gmtCreate;
    
        private Date gmtModified;
    
    

    注解解释:

    @Id:            主键
    @GeneratedValue: 数据库表 primary key, 自增长
    
    @Column(name=""): hibernate 自动根据属性名称创建数据库列名, 也可以手动定义列名
    
    @Column(nullable = false): create table robot (robot_name varchar(255) not null,) .....
    
    @Table: 数据库表名默认和类名一直, 使用该注解自定义
    
    @CreationTimestamp: 自动填入 gmtCreate 时间
    
    @UpdateTimestamp: 自动填入更新时间
    

    5.2.4 重启工程, 生成数据表

    启动 springboot 工程, 可以看到数据库中新增了表 robot

    image

    5.2.5 测试代码

    定义个接口, 继承自 JpaRepository :

    public interface RobotDAO extends JpaRepository<RobotDO, Integer> {
    
    }
    

    查看父类源码, 自带了 CRUD, 直接写个 Controller 来测试一下 (测试代码比较偷懒, 没有写 Service, 直接注入 DAO, 不推荐):

    @SpringBootApplication
    @RestController
    public class TestController {
    
        Logger logger = Logger.getLogger(this.getClass());
    
        @Autowired
        private Apple apple;
    
        @Autowired
        private RobotDAO robotDAO;
    
        @GetMapping(value = "/test")
        public Object testController() {
            logger.info("=====================Visiting  test controller======================");
    
            logger.info("测试插入:");
            RobotDO robotDO = new RobotDO();
            robotDO.setRobotName("Wall-E");
            robotDO.setSize("Huge");
            robotDO.setType("AI");
            robotDAO.save(robotDO);
    
            logger.info("测试查找:");
            logger.info(robotDAO.findAll());
    
            return apple;
        }
    
    

    日志输出如下, 设置 show-sql: true 可以方便看出执行的具体 SQL:

    2017-05-30 15:39:41.570  INFO 16254 --- [nio-9999-exec-1] troller$$EnhancerBySpringCGLIB$$efd51a80 : =====================Visiting  test controller======================
    2017-05-30 15:39:41.570  INFO 16254 --- [nio-9999-exec-1] troller$$EnhancerBySpringCGLIB$$efd51a80 : 测试插入:
    Hibernate: insert into robot (gmt_create, gmt_modified, robot_name, size, robot_type) values (?, ?, ?, ?, ?)
    2017-05-30 15:39:41.585  INFO 16254 --- [nio-9999-exec-1] troller$$EnhancerBySpringCGLIB$$efd51a80 : 测试查找:
    2017-05-30 15:39:41.588  INFO 16254 --- [nio-9999-exec-1] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
    Hibernate: select robotdo0_.id as id1_0_, robotdo0_.gmt_create as gmt_crea2_0_, robotdo0_.gmt_modified as gmt_modi3_0_, robotdo0_.robot_name as robot_na4_0_, robotdo0_.size as size5_0_, robotdo0_.robot_type as robot_ty6_0_ from robot robotdo0_
    2017-05-30 15:39:41.598  INFO 16254 --- [nio-9999-exec-1] troller$$EnhancerBySpringCGLIB$$efd51a80 : [
    RobotDO{id=1, robotName='Wall-E', size='Huge', type='AI', gmtCreate=2017-05-30 15:38:24.0, gmtModified=2017-05-30 15:38:24.0}, 
    RobotDO{id=2, robotName='Wall-E', size='Huge', type='AI', gmtCreate=2017-05-30 15:38:55.0, gmtModified=2017-05-30 15:38:55.0}, 
    RobotDO{id=3, robotName='Wall-E', size='Huge', type='AI', gmtCreate=Tue May 30 15:39:41 CST 2017, gmtModified=Tue May 30 15:39:41 CST 2017}
    ]
    

    看一下数据库:

    image

    5.2.5 事务操作

    多表操作, 需要放在一个事务里, 在方法上面添加 @Transactional 即可.

    6. AOP

    面向切面编程, 是 spring 的一大特性, 举个 controller 切面例子, 看一下 springboot 的 aop 使用.

    参考的是这个文档 Spring AOP Example Tutorial

    6.1 AOP 依赖引入

            <!--AOP依赖添加-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    

    6.2 AOP 实现

    @Aspect
    @Component
    @Order(1)
    public class ControllerAspect {
        private Logger logger = Logger.getLogger(this.getClass());
    
        @Pointcut("execution(public * com.alipay.liuqi.controller.TestController.testController())")
        public void webController() {
    
        }
    
        @Before(value = "webController()")
        public void beforeWebController() {
            logger.info("===BeforeWebController: start visiting===");
        }
    
        @After(value = "webController()")
        public void afterWebController() {
            logger.info("====AfterWebController: end visiting===");
        }
    
        @AfterThrowing(value = "webController()")
        public void afterThrowWebController() {
            logger.info("====AfterThrowWebController: throw exception ===");
        }
    
        @AfterReturning(value = "webController()")
        public void afterReturnWebControlelr() {
            logger.info("====AfterReturnWebControlelr: return normal ===");
        }
    
        @Around(value = "webController()")
        public void aroundWebController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("====AroundWebController: start around ===");
            proceedingJoinPoint.proceed();
            logger.info("====AroundWebController: end around ===");
        }
    }
    
    

    执行一下, 日志打印出来:

    2017-05-30 17:12:21.183  INFO 16254 --- [nio-9999-exec-2] c.alipay.liuqi.aspect.ControllerAspect   : ====AroundWebController: start around ===
    2017-05-30 17:12:21.184  INFO 16254 --- [nio-9999-exec-2] c.alipay.liuqi.aspect.ControllerAspect   : ===BeforeWebController: start visiting===
    2017-05-30 17:12:21.189  INFO 16254 --- [nio-9999-exec-2] troller$$EnhancerBySpringCGLIB$$efd51a80 : I am controller
    2017-05-30 17:12:21.194  INFO 16254 --- [nio-9999-exec-2] c.alipay.liuqi.aspect.ControllerAspect   : ====AroundWebController: end around ===
    2017-05-30 17:12:21.194  INFO 16254 --- [nio-9999-exec-2] c.alipay.liuqi.aspect.ControllerAspect   : ====AfterWebController: end visiting===
    2017-05-30 17:12:21.194  INFO 16254 --- [nio-9999-exec-2] c.alipay.liuqi.aspect.ControllerAspect   : ====AfterReturnWebControlelr: return normal ===
    

    6.3 AOP 小结

    其他说明

    1. Pointcut 支持正则表达式
    2. 除了 @Before 和 @After, 还有其他的注解, 说明如下:
    @AfterReturning: 切面点正常执行后, 才执行 AfterReturning 注解下面代码
    
    @AfterThrowing: 切面点抛出异常后, 执行 AfterThrowing 注解对应的代码
    
    @Around: 可以同时在所拦截方法的前后, 执行一段逻辑, 比如可以统计一下方法执行的时间
    
    

    切面小结:

    1. 定义切面点
    2. 实现切面点前后要做的事情
    3. 排好优先级

    7. 其他

    7.1 使用 LiveReload 热刷新

    热部署提到了修改完代码, 不需要重启工程, 但是有时候前端页面还是需要刷新的, 查看热部署文档时, 看到了 LiveReload, 热刷新简直好用极了!!!

    7.1.1 添加 devtools 依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
            </dependency>
    

    7.1.2 设置 livereload 属性为 true

    修改 application-dev.profiles :

    # 配置热部署, 热刷新
    spring:
      devtools:
        livereload:
          enabled: true
    

    7.1.3 浏览器安装 livereload 插件

    插件地址

    启动工程后, 日志会显示 livereload 占用的端口:

    2017-05-30 17:12:09.505  INFO 16254 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
    

    7.2 未提到的

    健康性检查是工程非常重要的指标, 因为没怎么用到, 暂未提及.

    • 接口测试
    • 健康性检查(Spring Boot Actuator)

    参考文档

    1. SpringBoot 官方文档

    2. SpringBoot进阶之Web进阶–视频教程

    3. Spring Boot中使用Spring-data-jpa让数据访问更简单、更优雅

    4. stackoverflow–JPA 时间设置

    5. https://github.com/spring-projects/spring-boot



沪ICP备19023445号-2号
友情链接