目录
定义
spring boot 特性:
1、创建一个独立的spring 应用;
2、内嵌Tomcat
3、提供 starter 依赖
4、提供第三方配置
5、提供生产就绪的特性
6、配置更简单
第一个SpringBoot应用
http://c.biancheng.net/spring_boot/example.html
SpringBoot注解
@RestController
在Spring中@RestController的作用等同于@Controller + @ResponseBody。
@GetMapping/@PostMapping
在Spring中@GetMapping()/@PostMapping等同于传统的@RequestMapping来编写应该是@RequestMapping(value = “”}, method = RequestMethod.GET/POST)
SpringBoot启动器starter
Spring Boot 将日常企业应用研发中的各种场景都抽取出来,做成一个个的 starter(启动器),starter 中整合了该场景下各种可能用到的依赖,用户只需要在 Maven 中引入 starter 依赖,SpringBoot 就能自动扫描到要加载的信息并启动相应的默认配置。starter 提供了大量的自动配置,让用户摆脱了处理各种依赖和配置的困扰。所有这些 starter 都遵循着约定成俗的默认配置,并允许用户调整这些配置,即遵循“约定大于配置”的原则。
可以看看:http://c.biancheng.net/spring_boot/starter.html
SpringBoot的yml配置文件
http://c.biancheng.net/spring_boot/yaml.html
bootstrap.yml配置文件
bootstrap.yml和application.yml差不多
区别:
- bootstrap.yml的优先级大于application.yml
- spplication.yml主要用于项目的配置
- bootstrap.yml用于:
- SpringCloud
- 加密解密
- 固定参数
devtools自动重启实现
springboot通过devtools实现了可以不用重启,自动的执行了更新后的java代码,不包括配置信息。
实现步骤:
- 引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
- 更改内容后,点击ctrl+F9
propertis配置
在springBoot里面,一般的常量配置都使用properties配置
使用步骤:
- 新建一个properties文件放在resource里面MyConfig.properties
user.userName=叼毛
user.age=20
- 在需要用的地方写入注解
package org.example.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("user")
@PropertySource(value = "classpath:MyConfig.properties", encoding = "utf-8")
public class MyConfig {
private String userName;
private Integer age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
SpringBoot-整合redis
配置步骤:
1.引入依赖
<!-- 引入 redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<!--<version>2.1.5.RELEASE</version>-->
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
2.配置yml
spring:
redis:
database: 0
host: 192.168.1.201
port: 6379
password: 123456
3.拷贝RedisOperator
4.controller测试redis中set和get
@Autowired
private RedisOperator redis;
@GetMapping("/redis")
public Object redis() {
redis.set("name", "国产凌凌漆");
return redis.get("name");
}
自定义启动logo
启动logo设置为图片
spring
banner:
image:
location: classpath:img/cat.png
pixelmode: block
启动logo设置为文本
spring:
banner:
location: classpath:banner/banner.txt
web请求静态资源
springboot里面的静态资源默认路径是
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
请求静态资源执行路径默认到static,可以直接访问,例如:http://localhost:8080/cat.png
可以通过yml设置更改默认路径
自定义请求路径配置
spring:
mvc:
static-path-pattern: /abc/** # 示例:http://localhost:8080/abc/cat.png
自定义静态资源配置路径
spring.web.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/webapp/
Restfull接口请求风格
Restfull请求规范
实际指接口请求注解
强规范
get(查询) post(新增) put:修改 Delete:删
弱规范
get(查询) post(新增 ,修改 ,修改 )
示例:
package org.example.controller;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("stu")
public class RestfulController {
@GetMapping("query")
public String query(){
return "query";
}
@PostMapping("create")
public String create(){
return "create";
}
@PutMapping("update")
public String update(){
return "update";
}
@DeleteMapping
public String delete(){
return "delete";
}
}
接口参数常用注解
- @PathVariable:用于获取路径里面的参数
- @RequestParam:用于获取URL中的请求参数,如果参数变量名保持一致,该注解可以省略
- @RequestBody:用于获取body里面的信息
- @RequestHeader:用于获取请求头里面的数据
- @CookieValue:用于获取cookie里面的信息
package org.example.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("param")
public class ParamsController {
@GetMapping("{stuId}/query")
public String query(@PathVariable("stuId") String stuId,
@RequestParam String name,
@RequestParam String age){
/**
* @PathVariable:用于获取路径里面的参数,根据如上去使用
* @RequestParam:用于获取URL中的请求参数,如果参数变量名保持一致,该注解可以省略
*/
log.error("stuId:" + stuId);
log.info("name:" + name);
log.info("age:" + age);
return "query";
}
@PostMapping("create")
public String create(@RequestBody Map<String, Object> map,
@RequestHeader("token") String token,
@CookieValue("clientId") String clientId){
/**
* @RequestBody:用于获取body里面的信息
* @RequestHeader:用于获取请求头里面的数据
* @CookieValue:用于获取cookie里面的信息
*/
log.info(map.toString());
log.info(token);
log.info(clientId);
return "create";
}
}
返回值
一般封装成一个对象
package com.imooc.utils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
*
* @Title: JSONResult.java
* @Package com.imooc.utils
* @Description: 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据(json object)后,可自行根据业务去实现相关功能
*
* 200:表示成功
* 500:表示错误,错误信息在msg字段中
* 501:bean验证错误,不管多少个错误都以map形式返回
* 502:拦截器拦截到用户token出错
* 555:异常抛出信息
* 556: 用户qq校验异常
* @Copyright: Copyright (c) 2020
* @Company: www.imooc.com
* @author liutianyu
* @version V1.0
*/
public class JSONResult {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
@JsonIgnore
private String ok; // 不使用
public static JSONResult build(Integer status, String msg, Object data) {
return new JSONResult(status, msg, data);
}
public static JSONResult build(Integer status, String msg, Object data, String ok) {
return new JSONResult(status, msg, data, ok);
}
public static JSONResult ok(Object data) {
return new JSONResult(data);
}
public static JSONResult ok() {
return new JSONResult(null);
}
public static JSONResult errorMsg(String msg) {
return new JSONResult(500, msg, null);
}
public static JSONResult errorMap(Object data) {
return new JSONResult(501, "error", data);
}
public static JSONResult errorTokenMsg(String msg) {
return new JSONResult(502, msg, null);
}
public static JSONResult errorException(String msg) {
return new JSONResult(555, msg, null);
}
public static JSONResult errorUserQQ(String msg) {
return new JSONResult(556, msg, null);
}
public JSONResult() {
}
public JSONResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public JSONResult(Integer status, String msg, Object data, String ok) {
this.status = status;
this.msg = msg;
this.data = data;
this.ok = ok;
}
public JSONResult(Object data) {
this.status = 200;
this.msg = "OK";
this.data = data;
}
public Boolean isOK() {
return this.status == 200;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getOk() {
return ok;
}
public void setOk(String ok) {
this.ok = ok;
}
}
文件上传
文件上传大小限制
spring:
servlet:
multipart:
max-file-size: 600KB
max-request-size: 2MB
自定义异常界面
自定义封装异常
package org.example.exception;
public class MyCustomException extends RuntimeException {
public MyCustomException(String message) {
super(message);
}
}
拦截器
1.创建拦截器
package org.example.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.example.exception.GraceException;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
/**
* @Description: 拦截请求,访问controller之前
* @param: [request, response, handler]
* @return: boolean
* @author: liuyong
* @date: 2022/8/3
* @time: 16:05
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId");
String userToken = request.getHeader("userToken");
if(userId == null || userId.isEmpty() || userToken == null || userToken.isEmpty()) {
log.error("用户校验不通过,信息不完整");
GraceException.display("用户校验不通过,信息不完整");
return false;
}
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("interceptor...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion");
}
}
2.注册拦截器
package org.example.config;
import org.example.interceptor.UserInfoInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public UserInfoInterceptor userInfoInterceptor() {
return new UserInfoInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInfoInterceptor()).addPathPatterns("/upload");
}
}
定时任务
package com.imooc.config;
import com.imooc.service.OrderService;
import com.imooc.utils.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class OrderJob {
@Autowired
private OrderService orderService;
/**
* 使用定时任务关闭超期未支付订单,会存在的弊端:
* 1. 会有时间差,程序不严谨
* 10:39下单,11:00检查不足1小时,12:00检查,超过1小时多余39分钟
* 2. 不支持集群
* 单机没毛病,使用集群后,就会有多个定时任务
* 解决方案:只使用一台计算机节点,单独用来运行所有的定时任务
* 3. 会对数据库全表搜索,及其影响数据库性能:select * from order where orderStatus = 10;
* 定时任务,仅仅只适用于小型轻量级项目,传统项目
*
*/
// @Scheduled(cron = "0/3 * * * * ?")
@Scheduled(cron = "0 0 0/1 * * ?")
public void autoCloseOrder() {
orderService.closeOrder();
System.out.println("执行定时任务,当前时间为:"
+ DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN));
}
}
整合高性能HicarICP数据源
1.引入依赖pom
<!-- jdbc启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.数据源配置
spring:
datasource: # 数据库的相关配置节点
type: com.zaxxer.hikari.HikariDataSource # 指定数据源类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动
url: jdbc:mysql://localhost:3306/springboot-demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true # 数据库地址
username: root # 用户名
password: root # 密码
hikari:
connection-timeout: 30000 # 等待连接池分配的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException,默认:30秒
minimum-idle: 5 # 最小连接数
maximum-pool-size: 20 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 连接超时的最大时长(毫秒),超时则被释放(retired),默认:10分钟
pool-name: DataSourceHikariCP # 连接池名称
max-lifetime: 1800000 # 连接的生命时长(毫秒),超时而且没被使用则被释放(retired),默认:30分钟
connection-test-query: SELECT 1
springboot整合mybatis配置
1.引入依赖
默认第一个依赖就行了,第二个依赖可以调整mapper接口继承通用mapper
<!-- mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- mapper启动器:myMapper还封装了常用查询语句-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
2.mybatis配置
# myBatis相关配置
mybatis:
type-aliases-package: org.example.pojo # mapper逆向生成工具后实体包存放的位置
mapper-locations: classpath:mapper/*.xml # 所有mapper映射的文件所在目录
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启mybatis日志实现
# 通用mapper配置
mapper:
mappers: org.example.my.mapper.MyMapper # 所有mapper都需要实现的接口
not-empty: true # 在进行数据库操作的时候,判断一个属性是否为空的时候,是否需要自动追加username != ""
identity: MYSQL
3.编写自己的通用mapper
package org.example.my.mapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
/**
* 继承自己的myMapper
*/
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}
4.常用mapper示例
package org.example.mapper;
import org.example.my.mapper.MyMapper;
import org.example.pojo.Teacher;
import org.springframework.stereotype.Repository;
@Repository
public interface TeacherMapper extends MyMapper<Teacher> {
}
接口参数验证框架
1.引入依赖
<!-- 引入参数验证框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.bean添加验证注解
常用验证注解有:
示例:
package org.example.pojo.bo;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class TeacherBO {
private String id;
@NotBlank
private String name;
@NotNull
private Integer age;
}
3.controller层代码配置
@PostMapping("save")
public JSONResult save(@Valid @RequestBody TeacherBO teacherBO,
BindingResult bindingResult){
String sid = UUID.randomUUID().toString().trim();
if (bindingResult.hasErrors()) {
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
HashMap<String, String> m = new HashMap<>();
for (FieldError error : fieldErrors) {
m.put(error.getField(),error.getDefaultMessage());
}
return JSONResult.error(m.toString());
}
Teacher teacher = new Teacher();
BeanUtils.copyProperties(teacherBO, teacher);
teacher.setId(sid);
// teacher.setName("展示");
// teacher.setAge(18);
teacherService.save(teacher);
return new JSONResult("1");
}
事务管理
事务的传播属性:
- required:当存在事务时沿用上一个事务,没有则新建一个事务
- suppots:如果当前存在事务则沿用,如果没有事务则不用
- mandatory:当前必须存在一个事务,如果没有则抛出异常
- required_new:如果当前存在事务则创建一个事务,如果没有同required
- not_supported:如果当前存在事务,则挂起事务,自己不用事务
- never:如果当前存在事务,则抛出异常
- nested:如果当前存在事务,则开启子事务(嵌套事务),嵌套事务是独立提交或回滚;
如果当前没有事务,则同required,如果主事务提交或回滚,会携带子事务,但子事务异常,主事务可回滚可不回滚
示例:
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public List<Teacher> queryByCondition(String name) {
Example example = new Example(Teacher.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("name",name);
List<Teacher> teachers = teacherMapper.selectByExample(example);
return teachers;
}
整合自定义阿里druid数据源
1.引入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
2.配置数据源类型
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置自定义的数据源:阿里druid
3.配置druid连接池
spring:
datasource:
druid:
connection-timeout: 30000
开启mybatis的sql执行日志打印
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启mybatis日志实现
使用AOP监控service执行时间
package org.example.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Aspect
@Slf4j
@Component
public class logAdvice {
/**
* AOP的通知类型
* 前置通知
* 后置通知
* 环绕通知
* 异常通知
* 最终通知
*/
@Around("execution(* org.example.service.impl..*.*(..))")
public Object printLogOfService(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("正在执行{}.{}", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
// 3. 记录service的方法执行之后的时间
long end = System.currentTimeMillis();
// 4. 得到service的方法执行的时间差
long takeTime = end - start;
// 5. 根据时间差来进行判断,并且打印日志的输出到控制台
if (takeTime > 3000) {
log.error("当前执行时间耗时太久了,为{}毫秒", takeTime);
} else if (takeTime > 2000) {
log.warn("当前执行时间耗时稍微有点长,应该后期考虑优化,为{}毫秒", takeTime);
} else {
log.info("当前执行时间耗时为{}毫秒", takeTime);
}
return result;
}
}