前言
记录学习过程中中途碰到问题及解决办法和查找相关资料的。
现在公司比较流行SpringCloud或阿里的dobbo,这2个技术都需要Springboot,因为Springboot在微服务的基础。
Beetl模板与thymeleaf模板各有什么优势,应该用哪种呢?留后期调研。
微框架,与Spring4一起诞生,比如SpringMVC中的@RestController,使用它可以在web页面直接放回一个json到web页面或app
Springboot可以快速上手,整合了一下子项目(开源框架或第三方开源库)
Springboot可以依赖很少的配置,就可以十分快速的搭建并运行项目。
这里主要采用的基础项目
Spring Boot 2多模块 Spring Boot 项目--之多模块重构
项目下载地址
https://yunpan.360.cn/surl_yFtZK2c74HC (提取码:a184)
涉及到的知识点
- 构建Springboot项目
- Springboot接口返回json及jackson使用
- Springboot热部署
- Springboot资源属性配置
- Springboot模板引擎--thymeleaf
- springboot异常处理
Springboot特点
- 基于Spring,使开发者快速入门,门槛很低。spring的全家桶提供了非常强大的功能。
- Springboot可以创建独立运行的应用,而不依赖与容器。
- 不需要打包成war包,可以放入tomcat中直接运行
- Springboot提供manven极简配置,不用在看过多的xml(默认配置),切点是会引入狠多你不需要的包
- Springboot提供可视化的相关功能,方便监控,比如性能,应用的健康程度。
- Springboot微微服务SpringCloud铺路,SpringBoot可以整合很多各式各样的框架来构建微服务,比如dubbo,thrift
构建Springboot项目(参考之前的文章)
#官方地址
https://spring.io/
SpringBoot接口返回json及Jackson的基本演绎
#添加数据web api方式返回POST
localhost:8080/person/saveApi?name=牵手生活22&age=18&password=123456
#获取模拟数据返回GET
localhost:8080/person/getUserDemo
先看看实现的效果时什么样的,实现中功能修改了2个文件User.java、UserController.java,并增加了Api接口外的类IYounghareJsonResult.java(来自github的LeeJSONResult.java)
修改的User.java实体如下
public class User {
private int id;
private String name;
private int age; //年龄
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss a",locale = "zh",timezone = "GMT+8")
private Date birthday; //出生年月
@JsonIgnore //json返回时,忽略
private String password; //密码
@JsonInclude(JsonInclude.Include.NON_NULL) //当数据为空时不返回
private String desc; //描述
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
", password='" + password + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
UserController.java增加了saveApi接口
@RestController //表示是一个Rest接口 ==@Controller+ @ResponseBody(方法位置)
public class UserController {
private final UserRepostory userRepostory; //没有初始化,这里采用构造器注入的方式
@Autowired
public UserController(UserRepostory userRepostory){
this.userRepostory = userRepostory;
}
@PostMapping("/person/save")
//@RequestBody ==采用@RestController 不再需要这个
public User save(@RequestParam String name,@RequestParam int age,@RequestParam String password){
User user = new User();
user.setName(name);
user.setAge(age);
user.setPassword(password);
user.setBirthday(new Date());
user.setDesc(null);
if (userRepostory.save(user)){
System.out.printf("用户对象:%s 保存成功\n",user);
}
return user;
}
@PostMapping("/person/saveApi")
public IYounghareJSONResult saveApi(@RequestParam String name,@RequestParam int age){
User user = new User();
user.setName(name);
user.setAge(age);
user.setBirthday(new Date());
user.setDesc(null);
if (userRepostory.save(user)){
System.out.printf("用户对象:%s 保存成功\n",user);
}
//return user;
return IYounghareJSONResult.ok(user);
}
@GetMapping("/person/getUserDemo")
//@RequestBody ==采用@RestController 不再需要这个
public User getUserDemo(){
User user = new User();
user.setName("牵手");
user.setAge(18);
user.setPassword("123456");
user.setBirthday(new Date());
user.setDesc(null);
return user;
}
}
IYounghareJsonResult.java 工具类代码
/**
*
* @Title: LeeJSONResult.java
* @Package com.lee.utils
* @Description: 自定义响应数据结构
* 这个类是提供给门户,ios,安卓,微信商城用的
* 门户接受此类数据后需要使用本类的方法转换成对于的数据类型格式(类,或者list)
* 其他自行处理
* 200:表示成功
* 500:表示错误,错误信息在msg字段中
* 501:bean验证错误,不管多少个错误都以map形式返回
* 502:拦截器拦截到用户token出错
* 555:异常抛出信息
* Copyright: Copyright (c) 2016
* Company:Nathan.Lee.Salvatore
*
* @author leechenxiang
* @date 2016年4月22日 下午8:33:36
* @version V1.0
*/
public class IYounghareJSONResult {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
// 响应业务状态
private Integer status;
// 响应消息
private String msg;
// 响应中的数据
private Object data;
private String ok; // 不使用
public static IYounghareJSONResult build(Integer status, String msg, Object data) {
return new IYounghareJSONResult(status, msg, data);
}
public static IYounghareJSONResult ok(Object data) {
return new IYounghareJSONResult(data);
}
public static IYounghareJSONResult ok() {
return new IYounghareJSONResult(null);
}
public static IYounghareJSONResult errorMsg(String msg) {
return new IYounghareJSONResult(500, msg, null);
}
public static IYounghareJSONResult errorMap(Object data) {
return new IYounghareJSONResult(501, "error", data);
}
public static IYounghareJSONResult errorTokenMsg(String msg) {
return new IYounghareJSONResult(502, msg, null);
}
public static IYounghareJSONResult errorException(String msg) {
return new IYounghareJSONResult(555, msg, null);
}
public IYounghareJSONResult() {
}
// public static LeeJSONResult build(Integer status, String msg) {
// return new LeeJSONResult(status, msg, null);
// }
public IYounghareJSONResult(Integer status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
public IYounghareJSONResult(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;
}
/**
*
* @Description: 将json结果集转化为LeeJSONResult对象
* 需要转换的对象是一个类
* @param jsonData
* @param clazz
* @return
*
* @author leechenxiang
* @date 2016年4月22日 下午8:34:58
*/
public static IYounghareJSONResult formatToPojo(String jsonData, Class<?> clazz) {
try {
if (clazz == null) {
return MAPPER.readValue(jsonData, IYounghareJSONResult.class);
}
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (clazz != null) {
if (data.isObject()) {
obj = MAPPER.readValue(data.traverse(), clazz);
} else if (data.isTextual()) {
obj = MAPPER.readValue(data.asText(), clazz);
}
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
/**
*
* @Description: 没有object对象的转化
* @param json
* @return
*
* @author leechenxiang
* @date 2016年4月22日 下午8:35:21
*/
public static IYounghareJSONResult format(String json) {
try {
return MAPPER.readValue(json, IYounghareJSONResult.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
*
* @Description: Object是集合转化
* 需要转换的对象是一个list
* @param jsonData
* @param clazz
* @return
*
* @author leechenxiang
* @date 2016年4月22日 下午8:35:31
*/
public static IYounghareJSONResult formatToList(String jsonData, Class<?> clazz) {
try {
JsonNode jsonNode = MAPPER.readTree(jsonData);
JsonNode data = jsonNode.get("data");
Object obj = null;
if (data.isArray() && data.size() > 0) {
obj = MAPPER.readValue(data.traverse(),
MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
return build(jsonNode.get("status").intValue(), jsonNode.get("msg").asText(), obj);
} catch (Exception e) {
return null;
}
}
public String getOk() {
return ok;
}
public void setOk(String ok) {
this.ok = ok;
}
}
代码先保存,方便之后修改或提前
https://yunpan.360.cn/surl_yFtcg3T5PvF (提取码:7df9)
SpringBoot开发环境热部署(可行我好像没有热部署成功)
开发环境中,如果修改某个文件,如修改User或、UserController需要重启服务器
Springboot使用devtools进行热部署
在pom.xml文件中添加
<!-- 热部署 -->
<!-- devtools可以实现页面热部署(即页面修改后会立即生效,
这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现) -->
<!-- 实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。 -->
<!-- 即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),
注意:因为其采用的虚拟机机制,该项重启是很快的 -->
<!-- (1)base classloader (Base类加载器):加载不改变的Class,例如:第三方提供的jar包。 -->
<!-- (2)restart classloader(Restart类加载器):加载正在开发的Class。 -->
<!-- 为什么重启很快,因为重启的时候只是加载了在开发的Class,没有重新加载第三方的jar包。 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- optional=true, 依赖不会传递, 该项目依赖devtools;
之后依赖boot项目的项目如果想要使用devtools, 需要重新引入 -->
<optional>true</optional>
</dependency>
修改application.properties 添加如下内容(#开始的是注释)
#热部署生效
spring.devtools.restart.enabled=true
#设置重启的目录,添加哪个目录的文件需要重启
spring.devtools.restart.additional-paths=src/main/java
# 为amybatis设置,生成环境可以删除
#restart.include.mapper=/mapper-[\\w-\\.]+jar
#restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar
#排除哪个目录的文件不需要重启
#spring.devtools.restart.exclude=static/**,public/**
#classpath目录下的WEB-INF文件夹内容修改不重启(静态文件的修改不需要重启)
#spring.devtools.restart.exclude=WEB-INF/**
SpringBoot 资源文件属性配置application.properties
在pom.xml文件中添加依赖
<!--#资源文件属性配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
我在使用@configurationProperties注解时 idea弹出 Spring Boot Annotion processor not found in classpath
创建czgResource.properties资源文件、创建Resouce.java类、创建ResourceController.java类
访问路径及效果
localhost:8080/resouce/getResouce
czgResource.properties
com.younghare.opensource.name=牵手生活--简书
com.younghare.opensource.website=https://www.jianshu.com/u/e09dc5872735
com.younghare.opensource.desc=笔记整理。分享是一种美德,牵手生活,携手前行
# spring使用@Value标签读取.properties文件的中文乱码问题的解决 https://blog.csdn.net/J3oker/article/details/53839210
CzgResouce.java
@Configuration //表示要引用资源文件的配置
@ConfigurationProperties(prefix="com.younghare.opensource")
@PropertySource(value= "classpath:czgResource.properties")
public class CzgResouce {
private String name;
private String webSite;
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getWebSite() {
return webSite;
}
public void setWebSite(String webSite) {
this.webSite = webSite;
}
}
ResourceController.java
@RestController //表示是一个Rest接口 ==@Controller+ @ResponseBody(方法位置)
public class ResourceController {
@Autowired
private CzgResouce czgResouce;
@RequestMapping("/resouce/getResouce")
public IYounghareJSONResult getCzgResouce(){
CzgResouce bean_Czg_resouce = new CzgResouce();
BeanUtils.copyProperties(czgResouce, bean_Czg_resouce);
return IYounghareJSONResult.ok(bean_Czg_resouce);
}
}
SpringBoot 资源文件配置server以及tomcat及端口
Springboot默认是使用tomcat,更多细节参考
修改application.properties文件如下:
#####################################################
#
# Server 服务器端相关配置
#####################################################
#配置api端口
server.port=8081
#设定应用的context-path,一般来说这个配置在正式发布的时候不配置
#server.servlet.context-path=/IYounghare
#错误页面,指定发生错误时,跳转的URL -->BasicErrorController
server.error.path=/error
#session最大超时时间(分钟),默认30分钟
server.servlet.session.timeout=60
#该服务器绑定IP地址,启动服务器时如果本机不是该IP地址则抛出异常,启动失败,
#只有特殊需求的情况下才配置,具体根据各自的业务来设置
#####################################################
#
# Server --tomcat服务器端相关配置
#####################################################
#tomcat最大的线程数,默认200
#server.tomcat.max-threads=180
#tomcat的URI编码
server.tomcat.uri-encoding=UTF-8
#存放tomcat的日志,dump等文件的临时文件夹,默认为系统的tmp文件夹
#如:c:\users\younghare\AppData\Local\Temp\
server.tomcat.accesslog.enabled=true
#server.tomcat.accesslog.pattern=
#accesslog目录,默认在basedir/logs
#server.tomcat.accesslog.directory=
#日志文件目录
logging.path= D:/czg/czgTemp/springboot-tomcat-tmp
#日志文件名称,默认为spring.log
#logging.file= myapp.log
SpringBoot 整合模板引擎freemarker(略)、thyemleaf
在resources资源下创建templates文件夹
整合thymeleaf
在pom.xml中添加依赖
<!-- 引入 thymeleaf 模板依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在application.properties 添加thymeleaf的配置信息
#####################################################
#
#thymeleaf 静态资源配置
#
#####################################################
#前缀,默认文件夹
spring.thymeleaf.prefix=classpath:/templates/
#后缀时.html
spring.thymeleaf.suffix=.html
#模式使用html5
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
#deprecated configuration property spring.thymeleaf.content-type 意思是这个属性已经过时不被使用
#spring.thymeleaf.content-type=text/html
#content-type 是text和html
spring.thymeleaf.servlet.content-type=text/html
# 关闭缓存,即时刷新,上线生成环境需要改为true
spring.thymeleaf.cache=false
springboot集成themeleaf报Namespace 'th' is not bound
ThymeleafController.java
@Controller
@RequestMapping("th_czg") //设置访问路径
public class ThymeleafController {
@RequestMapping("dynamicThymeleaf")
public String dynamicThymeleaf(ModelMap map) {
map.addAttribute("name", "thymeleaf-为什么进不了这个断点"); ////需要SpringMVC的依赖库
return "thymeleaf/dynamicThymeleaf/dynamicThymeleafDemo";
}
@RequestMapping("staticThymeleaf")
public String staticThymeleaf() {
return "thymeleaf/staticThymeleaf/thymeleafDemo";
}
}
thymeleafDemo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Thymeleaf模板引擎--静态demo
<h1>thymeleafDemo page</h1>
</body>
</html>
dynamicThymeleafDemo.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head >
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Thymeleaf模板引擎
<!-- 注意: th 等thymeleaf表达式 ,在直接用浏览器打开该html文件时,不会被加载,只有后台有返回对应的值时才有对应的内容-->
<h1 th:text="${name}">hello world~~~~~~~</h1>
</body>
</html>
测试地址
#测试静态地址
http://localhost:8080/th_czg/staticThymeleaf
#加载动态
http://localhost:8080/th_czg/dynamicThymeleaf
Whitelabel Error Page
This application has no configured error view, so you are seeing this as a fallback.
Sun Mar 31 23:30:21 CST 2019
There was an unexpected error (type=Internal Server Error, status=500).
Failed to invoke handler method with resolved arguments: [0][type=org.springframework.validation.support.BindingAwareConcurrentModel][value={}] on public java.lang.String com.younghare.springBoothelloworld.controller.ThymeleafController.dynamicThymeleaf(org.springframework.ui.ModelMap)
网络解决方案--好像也没办法解决----有哪位大神帮我留言一下
http://javaetmoi.com/2017/12/migration-spring-web-mvc-vers-spring-webflux/
【spring boot学习】Model&ModelMap&ModelAndView--csdn
在pom.xml中引入SpringMVC的支持,就解决了
<!--添加springmvc的支持 public String test(ModelMap map)//需要SpringMVC的依赖库-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
thymeleaf常用标签的使用方法
- 基本使用方式
- 对象引用方式
- 时间类型转换
- text 与utext
- URL
- 引入静态资源文件js/css
- 条件判断 th:if 或th:unless
- 循环th:each
- th:swith与th:case
thymeleaf综合例子test.html,来自github
#测试路径
http://localhost:8080/th_czg/test
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head lang="en">
<meta charset="UTF-8" />
<title></title>
<!-- <script th:src="@{/static/js/test.js}"></script> -->
</head>
<body>
<div>
用户姓名:<input th:id="${user.name}" th:name="${user.name}" th:value="${user.name}"/>
<br/>
用户年龄:<input th:value="${user.age}"/>
<br/>
用户生日:<input th:value="${user.birthday}"/>
<br/>
用户生日:<input th:value="${#dates.format(user.birthday, 'yyyy-MM-dd')}"/>
<br/>
</div>
<br/>
<div th:object="${user}">
用户姓名:<input th:id="*{name}" th:name="*{name}" th:value="*{name}"/>
<br/>
用户年龄:<input th:value="*{age}"/>
<br/>
用户生日:<input th:value="*{#dates.format(birthday, 'yyyy-MM-dd hh:mm:ss')}"/>
<br/>
</div>
<br/>
text 与 utext :<br/>
<span th:text="${user.desc}">abc</span>
<br/>
<span th:utext="${user.desc}">abc</span>
<br/>
<br/>
URL:<br/>
<a href="" th:href="@{http://www.imooc.com}">网站地址</a>
<br/>
<br/>
<form th:action="@{/th/postform}" th:object="${user}" method="post" th:method="post">
<input type="text" th:field="*{name}"/>
<input type="text" th:field="*{age}"/>
<input type="submit"/>
</form>
<br/>
<br/>
<div th:if="${user.age} == 18">十八岁的天空</div>
<div th:if="${user.age} gt 18">你老了</div>
<div th:if="${user.age} lt 18">你很年轻</div>
<div th:if="${user.age} ge 18">大于等于</div>
<div th:if="${user.age} le 18">小于等于</div>
<br/>
<br/>
<select>
<option >选择框</option>
<option th:selected="${user.name eq 'lee'}">lee</option>
<option th:selected="${user.name eq 'imooc'}">imooc</option>
<option th:selected="${user.name eq 'LeeCX'}">LeeCX</option>
</select>
<br/>
<br/>
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>年龄备注</th>
<th>生日</th>
</tr>
<tr th:each="person:${userList}">
<td th:text="${person.name}"></td>
<td th:text="${person.age}"></td>
<td th:text="${person.age gt 18} ? 你老了 : 你很年轻">18岁</td>
<td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd hh:mm:ss')}"></td>
</tr>
</table>
<br/>
<br/>
<div th:switch="${user.name}">
<p th:case="'lee'">lee</p>
<p th:case="#{roles.manager}">普通管理员</p>
<p th:case="#{roles.superadmin}">超级管理员</p>
<p th:case="*">其他用户</p>
</div>
<br/>
</body>
</html>
Springboot异常处理
Springboot配置全局的异常捕获--web页面跳转
页面跳转形式
ajax形式
统一返回异常的形式
创建模拟异常的ErrorController.java
@Controller
@RequestMapping("err")
public class ErrorController {
@RequestMapping("/error")
public String error() {
int a = 1 / 0; //除零错误
return "thymeleaf/error";//不存在的地址
}
@RequestMapping("/ajaxerror")
public String ajaxerror() {
return "thymeleaf/ajaxerror";
}
@RequestMapping("/getAjaxerror")
@ResponseBody
public IYounghareJSONResult getAjaxerror() {
int a = 1 / 0;
return IYounghareJSONResult.ok();
}
}
#故意除零异常
http://localhost:8080/err/error
springMVC出现Cannot resolve symbol 'HttpServletRequest
在pom.xml中引入SpringMVC的支持
<!--添加springmvc的支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
访问网页http://localhost:8080//err/error 发现错误如下图
编写我们自己的异常处理类IYounghareExceptionHandler.java
注意注解部分
@ControllerAdvice //spring mvc提供了ControllerAdvice注解对异常进行统一的处理,拿到这些异常信息后,可以做一些处理,比如提供一个统一的web界
public class IYounghareExceptionHandler {
public static final String YOUNGHARE_ERROR_VIEW = "thymeleaf/error";
@ExceptionHandler(value = Exception.class)
public Object errorHandler(HttpServletRequest reqest,
HttpServletResponse response, Exception e) throws Exception {
e.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", reqest.getRequestURL()); //发生错误的地址
mav.setViewName(YOUNGHARE_ERROR_VIEW); //设置错误页面
return mav;
}
}
编写自己的error.html提示页面
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head lang="en">
<meta charset="UTF-8" />
<title>捕获全局异常</title>
</head>
<body>
拦截到了我们捕获的异常,我们可以根据项目需要美化该页面
<h1 style="color: red">发生错误:</h1>
<div th:text="${url}"></div>
<div th:text="${exception.message}"></div>
</body>
</html>
Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理
SpringBoot配置全局的异常捕获 - ajax形式
测试地址:
http://localhost:8080//err/ajaxerror
在application.properties文件中为SpringMVC 设置static地址
#Spring Boot的默认静态资源的路径为:===主要是js的文件如ajaxerror.js
#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
#优先级从从高到低。
spring.mvc.static-path-pattern=/static/**
到这里先把工程做个备份
https://yunpan.360.cn/surl_yLKZ52FG45K (提取码:1a6e)
统一异常处理
定义自己的异常处理类
用@ControllerAdvice+@ExceptionHandler(ServiceException.class) //要捕获的异常类