前言
本身应该是一个很简单的事情,新建项目,引入依赖,启动,结束。理论上不值得写笔记。
BUT!!!
最近发现,才大半年没新建项目,变化太快了,尤其是springboot 2.7.1
,和一些框架依赖会有冲突,感觉需要重新学一学,
顺便把从零配置的过程全部详细记录一下,防止以后忘记。
开发环境如下
- Windows 11
- Intellij Idea 2020.3
- Maven 3.6.3
- Oracle Java 1.8.0_251
- Tomcat 8.5.56
折腾
新建项目
开头部分都是基础中的基础了,而且新版本和老版本也没什么区别,就用截图快速介绍下,不需要的可以跳到下一个部分
配置 Maven
先配置maven环境,改为开发环境本地的maven,否则会出现maven依赖引不进来的问题。
配置 Swagger 3.0
然后添加 swagger 3.0
相关依赖
在 pom.xml
中添加这段依赖,并刷新 maven
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
XML
Copy
之后直接启动项目,就会看到这段报错
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-07-19 13:59:01.772 ERROR 1236 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.21.jar:5.3.21]
at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_251]
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.21.jar:5.3.21]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.1.jar:2.7.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.1.jar:2.7.1]
at com.example.demo.DemoApplication.main(DemoApplication.java:10) [classes/:na]
...
Java
Copy
参考网上文章
https://blog.csdn.net/Java__EE/article/details/124808044
得知 Springboot 2.6.0
之后 SpringMVC
默认路径匹配策略从 AntPathMatcher
更改为 PathPatternParser
,导致出错,解决办法是切换为原先的 AntPathMatcher
,或者降低 Springboot
版本到 2.6.0
以下。
我这里直接选择最简单的改配置文件方法解决
配置文件中添加配置解决
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
YAML
Copy
删除默认的 application.properties
新建3个文件分别是application.yml
# 通用配置
spring:
profiles:
active: dev
jackson:
time-zone: GMT+8
serialization:
write-dates-as-timestamps: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
YAML
Copy
application-dev.yml
# 开发环境
server:
port: 8080
servlet:
context-path: /demo
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
YAML
Copy
application-prod.yml
# 生产环境
server:
port: 8000
servlet:
context-path: /demo
spring:
servlet:
multipart:
max-file-size: 20MB
max-request-size: 20MB
YAML
Copy
再次启动项目就不报错了 (如果还报刚才的错可以刷新下项目 刷新下 maven
等)
这时候访问 http://localhost:8080/demo/swagger-ui/ 可以看到 swagger 3.0
的默认画面
接下来写一个最简单的接口模拟开发场景
LoginController.java
package com.example.demo.controller;
import com.example.demo.form.LoginForm;
import com.fasterxml.jackson.databind.util.JSONPObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/login")
@Api(tags = "登录接口")
public class LoginController {
private Logger log = LoggerFactory.getLogger(getClass());
@PostMapping("login")
@ApiOperation("登录测试")
public Map login(@RequestBody LoginForm form) {
// 模拟传入数据
System.out.println(form.toString());
// 模拟返回数据
return new HashMap() {{
put("code", 200);
put("message", "success");
}};
}
}
Java
Copy
LoginForm.java
package com.example.demo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "登录表单")
public class LoginForm {
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "手机号")
private Integer mobile;
@ApiModelProperty(value = "验证码")
private Integer verifyCode;
}
Java
Copy
使用swagger调用效果如图
再加个简单的配置文件
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@EnableOpenApi
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.enable(true)
.apiInfo(new ApiInfoBuilder()
.title("DEMO测试系统")
.description("DEMO测试系统 API接口文档")
.version("1.0.0")
.contact(new Contact("admin", "httsp://www.xxx.com", "admin@xxx.com"))
.build())
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
.paths(PathSelectors.any())
.build();
}
}
Java
Copy
对应页面效果图
配置 knife4j
这个可以看做 swagger 3.0
web界面简单美化
添加 maven
依赖pom.xml
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
XML
Copy
刷新 maven
以后直接重启项目即可
访问地址改为
http://localhost:8080/demo/doc.html
基础效果如下图
配置 Mybatis Plus
相关
maven相关pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
XML
Copy
idea插件 MybatisX (用于对着数据库表逆向生成代码)
配置数据库连接application-dev.yml
# 开发环境
server:
port: 8080
servlet:
context-path: /demo
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&tinyInt1isBit=false&allowPublicKeyRetrieval=true
username: root
password:
mybatis-plus:
mapper-locations: classpath*:/mapper/*.xml
type-aliases-package: com.example.demo.service
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
banner: false
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
call-setters-on-nulls: true
YAML
Copy
随便开个库创建个简单的表
在 idea 中配置数据库连接
到这里已经生成完毕了
简单贴几个生成出来的文件代码
DbUser
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 用户表
* @TableName db_user
*/
@TableName(value ="db_user")
@Data
public class DbUser implements Serializable {
/**
* 自增ID
*/
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 手机号
*/
private Integer mobile;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 软删除 0 正常 1 删除
*/
private Integer delFlag;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
Java
Copy
DbUserMapper.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.example.demo.mapper.DbUserMapper">
<resultMap id="BaseResultMap" type="com.example.demo.entity.DbUser">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="mobile" column="mobile" jdbcType="INTEGER"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="delFlag" column="del_flag" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,username,password,
mobile,create_time,update_time,
del_flag
</sql>
</mapper>
XML
Copy
之后需要在程序中指定 service
和 mapper
的位置才可以启动项目service
在刚才的配置文件 application-dev.yml
中的 type-aliases-package
一栏中配置例如 com.example.demo.service
mapper
在启动类 DemoApplication
上面加一个注解 @MapperScan(basePackages = "")
@SpringBootApplication
@MapperScan(basePackages = "com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Java
Copy
随便改造下刚才的示例LoginController
@RestController
@RequestMapping("/login")
@Api(tags = "登录接口")
public class LoginController {
private Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private DbUserService userService;
@PostMapping("login")
@ApiOperation(value = "登录测试")
public Result login(@RequestBody LoginForm form) {
DbUser user = new DbUser();
// 复制同名参数 从form到entity
BeanUtils.copyProperties(form, user);
// 保存到数据库 (MybatisPlus自带方法)
boolean save = userService.save(user);
if(!save){
throw new RuntimeException("系统错误: 保存到数据库发生异常!");
}
// 模拟返回数据
return Result.success(user);
}
}
Java
Copy
swagger在线调试效果
mysql中数据查看
(这里我发现mobile用int类型会超出范围,后全部改成string/varchar(20),与前文代码略有出入)
END
基本就是这样,可以快速实现增删改查,前端联调,只要建表生成,写接口逻辑即可
末尾再附上一些常用工具类
JSON工具类JsonUtils.java
package com.skmagic.common.utils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* JSON 工具类
* 基于Springboot自带的 Jackson
*
* @version 1.0.0
* @email admin@zzzmh.cn
* @date 2020/4/21 16:23
*/
zzzmh
* public class JsonUtils {
private static Logger logger = LoggerFactory.getLogger(JsonUtils.class);
public static ObjectMapper mapper = new ObjectMapper();
static {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
}
public static String toJSONString(Object object) {
String result = "";
try {
result = mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return result;
}
public static <T> T toObject(String json, Class<T> clazz) {
T result = null;
try {
result = mapper.readValue(json, clazz);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return result;
}
public static <T> List toArray(String json, Class<T> clazz) {
try {
return (List) mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (JsonParseException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
e.printStackTrace();
} catch (JsonMappingException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
e.printStackTrace();
} catch (IOException e) {
logger.error("decode(String, JsonTypeReference<T>)", e);
e.printStackTrace();
}
return null;
}
public static Map<String, Object> toObject(String json) {
return toObject(json, Map.class);
}
public static List<Map<String, Object>> toArray(String json) {
return toArray(json, Map.class);
}
public static void main(String[] args) {
String test = "[{\"key\":1,\"value\":1},{\"key\":1,\"value\":2},{\"key\":2,\"value\":1},{\"key\":2,\"value\":2}]";
List<Map<String, Object>> array = toArray(test);
System.out.println(array);
}
}
Java
Copy
Redis相关pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
XML
Copy
RedisConfig.java
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer(Object.class));
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
@Bean
public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
return redisTemplate.opsForValue();
}
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
Java
Copy
RedisUtils.java
@Component
public class RedisUtils {
@Autowired
private RedisTemplate redisTemplate;
@Resource(name = "redisTemplate")
private ValueOperations<String, String> valueOperations;
@Resource(name = "redisTemplate")
private HashOperations<String, String, Object> hashOperations;
@Resource(name = "redisTemplate")
private ListOperations<String, Object> listOperations;
@Resource(name = "redisTemplate")
private SetOperations<String, Object> setOperations;
@Resource(name = "redisTemplate")
private ZSetOperations<String, Object> zSetOperations;
/**
* 默认过期时长,单位:秒
*/
public final static long DEFAULT_EXPIRE = 60 * 60 * 12;
/**
* 不设置过期时长
*/
public final static long NOT_EXPIRE = -1;
/**
* 计数器
*/
public Long increment(String key, long expire) {
Long increment = valueOperations.increment(key);
// 单位时间内首次计数才设置过期时间
if (increment == 1) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return increment;
}
/**
* redis计数器 默认每次加一
*/
public Long increment(String key) {
return valueOperations.increment(key, 1);
}
public Long getIncrement(final String key) {
return (Long) redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
byte[] rowkey = serializer.serialize(key);
byte[] rowval = connection.get(rowkey);
try {
String val = serializer.deserialize(rowval);
return Long.parseLong(val);
} catch (Exception e) {
return 0L;
}
}
});
}
public void set(String key, Object value, long expire) {
valueOperations.set(key, toJson(value));
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
}
public void set(String key, Object value) {
set(key, value, DEFAULT_EXPIRE);
}
public <T> T get(String key, Class<T> clazz, long expire) {
String value = valueOperations.get(key);
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value == null ? null : fromJson(value, clazz);
}
public <T> T get(String key, Class<T> clazz) {
return get(key, clazz, NOT_EXPIRE);
}
public String get(String key, long expire) {
String value = valueOperations.get(key);
if (expire != NOT_EXPIRE) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
return value;
}
public String get(String key) {
return get(key, NOT_EXPIRE);
}
public void delete(String key) {
redisTemplate.delete(key);
}
/**
* Object转成JSON数据
*/
private String toJson(Object object) {
if (object instanceof Integer || object instanceof Long || object instanceof Float ||
object instanceof Double || object instanceof Boolean || object instanceof String) {
return String.valueOf(object);
}
return JsonUtils.toJSONString(object);
}
/**
* JSON数据,转成Object
*/
private <T> T fromJson(String json, Class<T> clazz) {
return JsonUtils.toObject(json, clazz);
}
public Map<String, String> getAllKV() {
Map<String, String> result = new HashMap<>();
Set<String> set = redisTemplate.keys("*");
for (String k : set) {
result.put(k, get(k));
}
return result;
}
}
来源:https://www.zzzmh.cn/post/6bc7c6496cf24fd8830563254b02de08