SpringBoot 笔记
营销测试工具工程实现, 基于 SpringBoot
, 码了一段时间, 整理一下笔记, 真的特别好用!!! 最直观的感受有下面三方面:
- 无 xml 配置!!!
- 内嵌 tomcat
- 组件选配
1. 创建工程
1.1 使用 IDEA 创建
个人推荐使用 IDEA 来创建, 省事. 不需要下载, 解压, 顺带还指定安装目录.
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!";
}
}
启动工程的方式有如下几个:
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
2.4 SpringBoot 热部署
改完代码, 每次启动工程挺费时的, SpringBoot 支持热部署需要添加 spring-boot-devtools
依赖, 可以手动添加, 也可以在创建 SpringBoot 时勾选 DevTools, 如下图:
下面说一下手工添加的方式:
- 手动添加 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 设置一下字段编译, 如下图
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 完美的支持配置文件隔离. 操作如下:
新增 开发/测试/生产 配置文件:
配置 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
- SpringBoot
从上面两个图可见, 两个框架提供的功能是一样的, 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
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}
]
看一下数据库:
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 小结
其他说明
- Pointcut 支持正则表达式
- 除了 @Before 和 @After, 还有其他的注解, 说明如下:
@AfterReturning: 切面点正常执行后, 才执行 AfterReturning 注解下面代码
@AfterThrowing: 切面点抛出异常后, 执行 AfterThrowing 注解对应的代码
@Around: 可以同时在所拦截方法的前后, 执行一段逻辑, 比如可以统计一下方法执行的时间
切面小结:
- 定义切面点
- 实现切面点前后要做的事情
- 排好优先级
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)
参考文档
blog comments powered by Disqus