一、springboot 2.x的基本编写
注:本文皆为本人自己实际开发中的了解所得经验,如有不对之处欢迎诸位批评指点
springboot的基础了解
springboot是JavaEE开发中目前主流的一种框架,非常适合敏捷项目、大型项目的开发,他将我们最早时期开发所用的框架进行了简化整理,对于一些重要却又繁琐的配置进行了编写可以让使用者十分简单的上手开发,不需要在进行大量的配置文件编写,并且在springboot的兼容性非常高,几乎所有的开发所用到的jar包他都可以不排斥,并且如果你使用的是Scala编写后台,springboot也可以作为scala的JavaEE框架使用。
1、Web框架使用的阶梯变化
在我大学的时候初学JavaEE,了解到的Web框架就是最早的web servlet,他是我记忆中最早的主流框架,也应该是包含了所有Web框架的基础,包括后期会用到的三大域:request、session、application。在使用servlet作为web框架的时候还没有前后端分离的说法,依旧是使用servlet+jsp进行网站的搭建,目前主要的通信手段Http接口在servlet中编写也非常费时,需要使用@Webservlet注解表明编写。
过度期间是在servlet的基础上逐渐添加了hibernate进行持久层编写,抛弃的最早的手动创建数据库连接,后加入了Struts2作为控制器进行建立服务于jsp页面的数据交互,最后加入了耳熟能详却又在当时功能较为简单的spring框架,对servlet项目进行切面化(AOP)开发,对项目中的配置等等进行了对象管理,将对象都实例化为Been,在使用构造器或setter依赖注入到指定类中进行使用。这就是早期的SSH项目,但是他的配置繁多,hibernate的配置文件,spring的配置文件,Struts2的配置文件,搭建框架的时候费时费力。
后来spring的开发团队开发的springMVC成功的替代了Struts2的地位,在springMVC出现后项目的框架搭建难度急剧下降,springMVC相比Struts2更加方便的配置以及与spring的完美配合,很快获取的开发人员的喜欢和依赖,成为了新一代的SSH框架,但是随着互联网的进步,持久层的开发越来越复杂而hibernate已经无法满足开发人员的需求,一个新的数据库连接框架Mybatis出现在大众眼中,它相比hibernate其实各有优势但是因为在他在数据库调用,sql的执行方法比hibernate具有更大的自由性和便利性所有更受欢迎。同一时期随着项目开发所引入的jar包越来越多,出现了jar包管理的混乱情况,所以开发人员开始使用maven以及gradle进行jar包的管理。
springData或者说是springData+JPA的出现在web框架的使用上出现了一个短暂的体系SSSP(Spring,SpringMVC,SpringData,JPA)他的出现甚至可以说是昙花一现很少有人去了解,因为由这个SSSP框架搭建的WEB项目已经全部由spring的研发团队开发,所有他们干脆基于SSSP框架进行简化由一开始的使用XML进行配置各个组件转变为使用注解进行配置,之后便开发出了我们所了解的web框架——Springboot。
2、SpringBoot项目的简易搭建
项目编辑工具:idea
- 官网生成项目
打开官网项目生成网站https://start.spring.io/
完后更加网站描述的选项进行填充,填写完后选择导出项目即可获取springboot项目的压缩包,解压后便可以在项目中进行编写了。 - idea创建项目
打开idea,File=》New=》Project
选择Spring Initialzr=》Project SDK要选择java1.8以上,完后直接点击下一步
根据提示填写完选项后直接点击下一步
选择你所需要的组件在生成时就添加到pom文件中
Lombok这个简化代码jar包在后续编写代码时特别方便,他的作用就是可以通过添加注解的方法来减少代码书写量也提高了代码的整洁度如后面代码的@Slf4j添加后下面的代码就可以直接使用log.info()来进行注解了,但是需要在idea中安装一个lombok的插件不然idea无法解析会报错
选择你要生成的位置点击结束后项目便生成了
3、Springboot的配置文件
Springboot的配置文件是在src->main->resources内编写,通常默认的是.properties配置文件,配置编写格式如下:
server.port=8080
也可以使用.yml配置文件,配置编写格式如下:
server:
port: 8088
spring:
# 项目服务名,注册中心以及其他微服务识别名称,要与配置中心SVN上传文件保持文件名相同
application:
name: dome
Springboot项目中配置文件的启动顺序是先读取bootstrap.yml后读取application.yml,读取文件地址先读取打包后项目外config文件夹中的配置文件,其次为当前目录下的配置文件,完后读取项目中resources文件夹中config文件夹中的配置文件,最后读取resources文件夹中的配置文件
在resources文件夹中可以放一个以banner命名的文件可以修改项目启动后日志中打印的logo
如果项目中配置了日志配置文件就需要在springboot的配置文件中指定一下日志配置文件的地址:
logging:
config: classpath:config/logback-spring.xml
如果项目中需要在配置文件中自定义一些值,完后再程序中调用的话可以使用注解@Value,但是必须在Been中才可以调用到配置文件中的值:
@Value("${server.port}")
private String serverPort;
4、Springboot 启动加载类
在Springboot项目启动后,我们通常会需要进行一些初始化之类的操作,所有就需要我们编写一个springboot的启动类进行操作,一下代码为我编写的启动后打印启动日志并告知启动ip和端口的信息:
@Slf4j
@Component
public class StartLog implements ApplicationRunner {
@Value("${server.port}")
private String serverPort;
@Value("${spring.application.name}")
private String serverName;
@Override
public void run(ApplicationArguments args) throws Exception {
InetAddress addr = InetAddress.getLocalHost();
String ip=addr.getHostAddress();//获得本机IP
String str="\n-----------------------------------\n" +
"\n" +
"\n" +
"\t"+serverName+"\t服务已启动\t\t\t\t\n" +
"\t服务本机地址:localhost:"+serverPort+"\t\n" +
"\t服务对外地址:"+ip+":"+serverPort+"\t\n" +
"\t\n" +
"\t\n" +
"-----------------------------------";
log.info(str);
}
}
5、Springboot添加Swagger以及Mybatis配置
1. 配置Swagger
导入Swagger的jar包:
<!--swaggerAPI的ui界面包-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--swaggerAPI核心包,注:因为2.9.2版本会出现定义int类型为空的报错,所以将俩个依赖包替换为1.5.21版本的-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
<exclusions>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.21</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-models</artifactId>
<version>1.5.21</version>
</dependency>
依赖注入之后需要我们手动编写配置并使用注解开启swagger才可以使用,我这里是是使用了自定义配置变量进行配置的:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${swagger.title}")
String title;
@Value("${swagger.description}")
String description;
@Value("${swagger.version}")
String version;
@Value("${swagger.basePackage}")
String basePackage;
@Bean
public Docket swaggerApi() {
//增加发送header
// ParameterBuilder authorizationPar = new ParameterBuilder();
// List<Parameter> pars = new ArrayList<>();
// authorizationPar.name("Authorization").description("Authorization")
// .modelRef(new ModelRef("string")).parameterType("header")
// .required(false).build();
// pars.add(authorizationPar.build());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//swagger文档API扫描项目controller的路径
.apis(RequestHandlerSelectors.basePackage(basePackage))
.paths(PathSelectors.any())
.build();
//增加发送header
// .globalOperationParameters(pars);
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//swagger文档的标题
.title(title)
//swagger文档的介绍
.description(description)
//swagger文档的版本
.version(version)
.build();
}
}
在application.yml配置文件中添加
swagger:
title: "${spring.application.name} API"
description: "${spring.application.name} by test"
version: "0.0.1"
basePackage: "com.xi.test.controller"
配置成功后就可以在controller中添加注解了
@RestController
@Slf4j
@RequestMapping("/api")
@Api(value = "demo测试接口", description ="demo测试接口相关类")
public class DomeController{
@GetMapping("/logTest")
@ApiOperation(value="日志测试", notes="测试admin管理日志是否生效")
public String logTest(){
log.trace("日志测试:这是trace");
log.debug("日志测试:这是debug");
log.info("日志测试:这是info");
log.warn("日志测试:这是warn");
log.error("日志测试:这是error");
return "ok";
}
}
项目启动后在浏览器地址栏输入http://127.0.0.1:8080/swagger-ui.html 就可以看到swagger的APIDoc页面了
2. 配置MyBatis
首先先在项目中导入MyBatis所需要的jar包
<!--mysql连接包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis与springboot的整合包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--可以用于mybatis的分页工具,简单好用-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.3</version>
</dependency>
依赖包弄好以后就可以进行配置了,在application.yml文件中加入以下配置:
spring:
#配置mysql连接地址
datasource:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
#使用hikari数据库连接池,springboot2.0后自带连接池
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 5
maximum-pool-size: 15
auto-commit: true
idle-timeout: 30000
pool-name: DatebookHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-init-sql: select 1
mybatis:
#引用mybatis的配置文件(如果不使用分页工具可以不配)
config-location: classpath:config/mybatis_config.xml
#配置mybatis的mapperXML文件的映射位置(如果统一使用注解的方式不需要配)
mapper-locations: classpath:config/mapper/*.xml
mybatis_config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
这样mybatis的分页就配置好了,只要在查询mapper之前使用
PageHelper.startPage(pageNum,pageSize);
List<Users> userList = usersMapper.selectAll();
PageInfo<Users> pageInfo = new PageInfo<>(userList);
return pageInfo ;
便会返回分页结果,第一个参数为第几页,第二个参数为一页几条数据
至此Mybatis就配置完毕,我们就可以写一个mapper进行数据库操作了。
首先先在数据库中创建一个用户表
CREATE TABLE `users` (
`userId` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`userId`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
并且在项目中创建包和文件:
Users.java:
package com.xi.demo.bean;
import lombok.Data;
/**
* @author : xixiaobo@gmail.com
* @date : 2019/7/12 10:45
* @Description:
*/
@Data
public class Users {
Integer userId;
String userName;
}
UsersMapper.java:
package com.xi.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import com.xi.demo.bean.Users;
import org.apache.ibatis.annotations.Select;
/**
* @author : xixiaobo@gmail.com
* @date : 2019/7/12 10:45
* @Description:
*/
@Mapper
public interface UsersMapper {
@Select("select count(*)from users")
int getUserCount();
List<Users> getAllUsers();
}
UsersMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xi.demo.mapper.UsersMapper">
<!--数据库与实体类的映射关系-->
<resultMap id="BaseResultMap" type="com.xi.demo.bean.Users">
<result column="userId" property="userId" jdbcType="INTEGER"/>
<result column="userName" property="userName" jdbcType="VARCHAR"/>
</resultMap>
<select id="getAllUsers" resultMap="BaseResultMap">
<!--切记不要用分号结尾会影响分页-->
select userId,userName from users
</select>
</mapper>
接下来我们就可以在controller中注入Mapper类进行调用了
@Resource
UsersMapper usersMapper;
@RequestMapping(value = "/getUserCount",method = RequestMethod.GET)
@ApiOperation(value="数据库查询测试", notes="测试数据库查询返回是否正常")
public String getUserCount(){
int i = usersMapper.getUserCount();
return "用户表用户数量为:"+i;
}
@ApiOperation(value = "数据库查询分页测试", notes = "测试数据库查询分页返回是否正常")
@RequestMapping(value = "getAllUsers", method = RequestMethod.GET)
@ApiImplicitParams({
@ApiImplicitParam(name = "ispage", value = "是否使用分页", required = true, dataType = "boolean", paramType = "query"),
@ApiImplicitParam(name = "pageNum", value = "查询页数", required = false, dataType = "int", paramType = "query"),
@ApiImplicitParam(name = "pageSize", value = "每页条数", required = false, dataType = "int", paramType = "query"),
})
public JSONObject getAllUsers(@RequestParam(name = "ispage") boolean ispage,
@RequestParam(name = "pageNum", required = false) Integer pageNum,
@RequestParam(name = "pageSize", required = false) Integer pageSize)
{
if (ispage) {
/**
* mybatis分页使用配置方法
*/
PageHelper.startPage(pageNum, pageSize);
}
JSONObject k = new JSONObject();
try {
/**
* 使用PageInfo将正常查询所有数据进行包装
* 则执行查询所有时mybatis会自动在执行sql后加上limit,页数和条数通过上面的配置方法配置
* 如果不使用PageInfo进行包装则返回所有数据
*/
List<Users> users=usersMapper.getAllUsers();
k.put("code", 1);
k.put("message", "查询成功");
k.put("result", users);
if (ispage) {
PageInfo<Users> pageInfo = new PageInfo<>(users);
k.put("result", pageInfo);
}
}catch (Exception e){
k.put("code", 0);
k.put("message", "查询失败");
k.put("result", new JSONObject());
k.put("Exception",e);
}
return k;
}
在swagger中进行测试:
6、Springboot解决跨域问题
springboot2.以后,跨域问题可以使用coes来解决
@Configuration
@Slf4j
@Order(1)
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
log.info("初始化 CorsFilter 配置");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
//可以使前端读取的信息头
corsConfiguration.addExposedHeader("Authorization");
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
7、Springboot定义全局异常处理
在项目中常有需求需要捕获各种异常进行返回日志或者操作,以下是捕获异常的拦截器代码,添加到SpringBoot项目中即可:
GlobalExceptionHandler.java:
@CrossOrigin
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(HttpStatusCodeException.class)
public JSONObject httpStatusCodeExceptionHandler(HttpStatusCodeException ex) {
return exceptionFormat("HTTPCode异常", ex);
}
//运行时异常
@ExceptionHandler(RuntimeException.class)
public JSONObject runtimeExceptionHandler(RuntimeException ex) {
return exceptionFormat("运行时异常", ex);
}
//空指针异常
@ExceptionHandler(NullPointerException.class)
public JSONObject nullPointerExceptionHandler(NullPointerException ex) {
return exceptionFormat("空指针异常", ex);
}
//类型转换异常
@ExceptionHandler(ClassCastException.class)
public JSONObject classCastExceptionHandler(ClassCastException ex) {
return exceptionFormat("类型转换异常", ex);
}
//IO异常
@ExceptionHandler(IOException.class)
public JSONObject iOExceptionHandler(IOException ex) {
return exceptionFormat("IO异常", ex);
}
//未知方法异常
@ExceptionHandler(NoSuchMethodException.class)
public JSONObject noSuchMethodExceptionHandler(NoSuchMethodException ex) {
return exceptionFormat("未知方法异常", ex);
}
//数组越界异常
@ExceptionHandler(IndexOutOfBoundsException.class)
public JSONObject indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
return exceptionFormat("数组越界异常", ex);
}
//400错误
@ExceptionHandler({HttpMessageNotReadableException.class})
public JSONObject requestNotReadable(HttpMessageNotReadableException ex) {
log.error("400..requestNotReadable");
return exceptionFormat("400..requestNotReadable", ex);
}
//400错误
@ExceptionHandler({TypeMismatchException.class})
public JSONObject requestTypeMismatch(TypeMismatchException ex) {
log.error("400..TypeMismatchException");
return exceptionFormat("400..TypeMismatchException", ex);
}
//400错误
@ExceptionHandler({MissingServletRequestParameterException.class})
public JSONObject requestMissingServletRequest(MissingServletRequestParameterException ex) {
log.error("400..MissingServletRequest");
return exceptionFormat("400..MissingServletRequest", ex);
}
//405错误
@ExceptionHandler({HttpRequestMethodNotSupportedException.class})
public JSONObject request405(HttpRequestMethodNotSupportedException ex) {
return exceptionFormat("405错误", ex);
}
//406错误
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
public JSONObject request406(HttpMediaTypeNotAcceptableException ex) {
log.error("406...");
return exceptionFormat("406错误", ex);
}
//500错误
@ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
public JSONObject server500(RuntimeException ex) {
log.error("500...");
return exceptionFormat("500错误", ex);
}
//栈溢出
@ExceptionHandler({StackOverflowError.class})
public JSONObject requestStackOverflow(StackOverflowError ex) {
return exceptionFormat("栈溢出", ex);
}
//其他错误
@ExceptionHandler({Exception.class})
public JSONObject exception(Exception ex) {
return exceptionFormat("其他错误", ex);
}
//自定义异常捕获
@ExceptionHandler({MyException.class})
public JSONObject myException(MyException ex) {
log.error("1111111111111111111");
return exceptionFormat("自定义异常", ex);
}
private <T extends Throwable> JSONObject exceptionFormat(String message,T ex) {
JSONObject result =new JSONObject();
result.put("code",0);
result.put("message",message);
result.put("errorMags",ex);
log.error(result.toJSONString());
return result;
}
}
MyException.java:
@Data
public class MyException extends Exception {
private String code;
private String msg;
public MyException() {}
public MyException(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
这样便实现了全局异常拦截,出现问题也会正常JSON格式返回
8、Springboot常用注解
//微服务启动注解
@SpringBootApplication
//用于定义配置类
@Configuration
//被用在要被自动扫描和装配的类上
@Component
//主要被用在方法上,来显式声明要用生成的类
@Bean
//Controller类注解
@Controller
//REST接口的Controller类注解包含了@Controller和@ResponseBody
@RestController
//用来处理请求地址映射的注解,可以指定映射地址和请求的method类型
@RequestMapping
//用于获取HTTP请求中的主体Body的数据,只能注解一个参数
@RequestBody
//用于获取HTTP请求中url的 ‘?’ 后携带的参数,可以注解多个参数
@RequestParam
//以下4个都是用来处理请求地址映射的注解,但是不需要在指定请求的method类型
@PutMapping
@DeleteMapping
@PostMapping
@GetMapping
//以下两是用于自动注入装配bean,区别在于@Autowired是springboot自定义的注解,按been的Type自动注入,@Resource是由JSR-250规范定义的注解默认按 been的Name自动注入
@Resource
@Autowired
//一般作为@Autowired()的修饰用,用于指定been的name
@Qualifier
//Service类注解
@Service
//Mybatis的DAO层注解
@Mapper
//springboot的自定义注解,可以将配置文件中指定值赋值给指定值,但是不可以是静态变量
@Value
//重写注解,用于检测是否是重写的方法
@Override
//指定接口配置cros
@CrossOrigin
//定义Spring容器加载Bean的顺序,数字越小加载优先级越高,默认为最大值
@Order
Dome项目GITHUB地址:Dome项目