spriingBoot简介
SpringBoot是由Pivotal团队研发的,SpringBoot并不是一门新技术,只是将之前常用的Spring,SpringMVC,data-jpa等常用的框架封装到了一起,帮助你隐藏这些框架的整合细节,实现敏捷开发。
Spring Boot是基于约定优于配置的,主要作用就是用来简化Spring应用的初始搭建以及开发过程!
后期要学习的微服务框架SpringCloud需要建立在SpringBoot的基础上。
1.2 SpringBoot的特点
1.基于Spring的开发提供更快的入门体验。
2.开箱即用,没有代码生成,也无需XML配置,同时也可以修改默认值来满足特定的需求。
3.提供了一些大型项目中常见的非功能性特性,外部配置等。
4.SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式。
5.SpringBoot中整合第三方框架时,只需要导入相应的starter依赖包,就自动整合了
6.SpringBoot默认只有一个.properties的配置文件,不推荐使用xml,后期会采用.yml的文件去编写配置信息。
7.SpringBoot工程在部署时,采用的是jar包的方式,内部自动依赖Tomcat容器,提供了多环境的配置。
8.后期要学习的微服务框架SpringCloud需要建立在SpringBoot的基础上。
1.3 SpringBoot的核心功能
1.起步依赖
起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。
2.自动配置
Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。
二、SpringBoot快速入门
网站创建地址:https://start.spring.io/
项目创建完成! 此时pom.xml文件中会自动导入springboot所需依赖,并且在src下会生成一个配置类。 注意:若pom.xml中依赖无法下载,需要修改maven工程对应的settings.xml文件,找到settings.xml文件中的镜像配置,原因是maven中央仓库下载不下来springboot关联的架包,所以建议使用阿里云的镜像. <mirrors> <!-- mirror | Specifies a repository mirror site to use instead of a given repository. The repository that | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used | for inheritance and direct lookup purposes, and must be unique across the set of mirrors. | --> <mirror> <id>nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus-aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>
运行配置类,看到如下页面,表示启动成功!
手动编写Controller进行进一步测试(注意:需要将controller类,放在启动类的子包中或者同级包下)
package com.qf.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @RequestMapping("/login") public String login(){ System.out.println("登录"); return "success"; } }
重新启动配置类,访问:http://localhost:8080/login
三、SpringBoot热部署配置
为了方便开发,可以在创建项目时手动勾选热部署,或导入该依赖,就不需要每次重启配置类
<!--热部署配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
配置自动编译
最后Shift+Ctrl+Alt+/,选择Registry(选完之后再次查看一下是否勾选上)
再次重新运行一次配置类即可!
四、SpringBoot中的默认配置
可以从jar包中找到SpringBoot的默认配置文件位置
SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties或者application.yml(application.yaml)进行配置,SpringBoot默认会从Resources目录下加载application.properties或application.yml(application.yaml)文件。
其中,application.properties文件是键值对类型的文件,除此之外,SpringBoot还可以使用yml文件进行配置,YML文件格式是YAML (YAML Aint Markup Language)编写的文件格式,YAML是一种直观的能够被电脑识别的的数据数据序列化格式,并且容易被人类阅读,容易和脚本语言交互的,可以被支持YAML库的不同的编程语言程序导入,比如: C/C++, Ruby, Python, Java, Perl, C#, PHP等。YML文件是以数据为核心的,比传统的xml方式更加简洁,YML文件的扩展名可以使用.yml或者.yaml。
application.properties方式修改默认配置
application.yml方式修改默认配置(注意:yml文件中空格表示层级关系)
yml文件支持的配置
#普通数据的配置 name: jack #对象的配置 user: username: rose password: 123 #配置数组 array: beijing, tianjin, shanghai #配置集合 yangl: test: name: tom arr: 1,jack,2,tom list1: #这种对象形式的,只能单独写一个对象去接收,所以无法使用@value注解获取 - zhangsan - lisi list2: - driver: mysql port: 3306 - driver: oracle port: 1521 map: key1: value1 key2: value2
把yml文件中配置的内容注入到成员变量中,
第一种方式,创建UserController,使用@Value注解方式注入
package com.qf.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class UserController { @Value("${name}") private String name; @Value("${user.username}") private String username; @Value("${user.password}") private String password; @Value("${array}") private String [] array; @RequestMapping("/test") public String[] test(){ System.out.println(name); System.out.println(username); System.out.println(password); System.out.println(array[0]); return array; } }
第二种方式,使用@ConfigurationProperties注解方式,提供GET/SET方法
创建YmlController
package com.qf.controller; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; import java.util.List; import java.util.Map; @RestController @ConfigurationProperties(prefix = "yangl.test") public class YmlController { private String name; private String[] arr; private List<String> list1; private List<Map<String,String>> list2; private Map<String,String> map; @RequestMapping("/yml") public void yml(){ System.out.println(name); System.out.println(Arrays.toString(arr)); System.out.println(list1); System.out.println(list2); System.out.println(map); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String[] getArr() { return arr; } public void setArr(String[] arr) { this.arr = arr; } public List<String> getList1() { return list1; } public void setList1(List<String> list1) { this.list1 = list1; } public List<Map<String, String>> getList2() { return list2; } public void setList2(List<Map<String, String>> list2) { this.list2 = list2; } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } }
如果使用@ConfigurationProperties注解时提示以下信息
导入以下依赖即可(也可以不导入)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
五、SpringBoot整合MyBatis【重点】
5.1 导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
5.2 在application.properties中配置数据库信息
#配置数据库 spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/wyy_music?serverTimezone=Asia/Shanghai&characterEncoding=utf8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #连接池 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource #mybatis #指定mapper.xml文件的位置 mybatis.mapper-locations=classpath:mapper/*.xml #指定控制台输出日志 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
也可使用yml方式配置,需要创建application.yml
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/wyy_music?serverTimezone=Asia/Shanghai&characterEncoding=utf8 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource mybatis: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5.3 使用之前的Music表,创建实体类
Music
package com.qf.pojo; import lombok.Data; @Data public class Music { private Integer musicId; private String musicName; private String musicAlbumName; private String musicAlbumPicurl; private String musicMp3url; private String musicArtistName; private Integer sheetId; }
5.4 创建mapper
MusicMapper
package com.qf.mapper; import com.qf.pojo.Music; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface MusicMapper { List<Music> findAll(); }
5.5 在src\main\resources\mapper路径下创建对应的MusicMapper.xml文件
MusicMapper.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.qf.mapper.MusicMapper"> <resultMap id="musicMap" type="com.qf.pojo.Music"> <id property="musicId" column="music_id"></id> <result property="musicName" column="music_name"></result> <result property="musicAlbumName" column="music_album_name"></result> <result property="musicAlbumPicurl" column="music_album_picUrl"></result> <result property="musicMp3url" column="music_mp3Url"></result> <result property="musicArtistName" column="music_artist_name"></result> <result property="sheetId" column="sheet_id"></result> </resultMap> <sql id="BaseSql"> select music_id,music_name,music_album_name,music_album_picUrl,music_mp3Url,music_artist_name,sheet_id from tb_music </sql> <select id="findAll" resultMap="musicMap"> <include refid="BaseSql"></include> </select> </mapper>
5.6 创建service
MusicService
package com.qf.service; import com.qf.pojo.Music; import java.util.List; public interface MusicService { List<Music> findAll(); }
5.7 创建serviceImpl
MusicServiceImpl
package com.qf.service.impl; import com.qf.mapper.MusicMapper; import com.qf.pojo.Music; import com.qf.service.MusicService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class MusicServiceImpl implements MusicService { @Autowired private MusicMapper musicMapper; @Override public List<Music> findAll() { return musicMapper.findAll(); } }
5.8创建controller
MusicController
package com.qf.controller; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.qf.pojo.Music; import com.qf.service.MusicService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("music") public class MusicController { @Autowired private MusicService musicService; @RequestMapping("findAll") public List<Music> findAll(){ return musicService.findAll(); } }
5.9启动配置类(注意:需要在启动类上配置@MapperScan并扫描Mapper对应的包名)
package com.qf; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.qf.mapper")//扫描mapper public class Springboot03Application { public static void main(String[] args) { SpringApplication.run(Springboot03Application.class, args); } }
启动测试即可!
5.10分页插件的使用
5.10.1导入依赖
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.1</version> </dependency>
5.10.2添加方法
//分页 @RequestMapping("findByPage") public PageInfo findByPage( @RequestParam(value = "pageNum",required = false,defaultValue = "1")Integer pageNum, @RequestParam(value = "pageSize",required = false,defaultValue = "2")Integer pageSize){ PageHelper.startPage(pageNum,pageSize); List<Music> musicList = musicService.findAll(); PageInfo<Music> musicPageInfo = new PageInfo<>(musicList); return musicPageInfo; }
5.10.3在application.properties中添加配置
#配置分页 pagehelper.helper-dialect=mysql pagehelper.reasonable=true pagehelper.support-methods-arguments=true
启动测试即可!
5.11 SpringBoot工程打包部署
双击package执行打包命令,如果打包时报错,需要修改pom.xml文件中的build标签
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
改为:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>1.5.4.RELEASE</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.4</version> </plugin> </plugins> </build>
然后再次打包,在对应的磁盘目录找到对应的xxx.jar文件
部署:
修改项目中的数据库相关配置,然后启动Linux上的mysql,把xxx.jar文件拷贝到Linux中的任意目录
java -jar xxx.jar Linux上启动springboot工程 java -jar -Dserver.port=1234 xxx.jar Linux上指定端口号启动springboot工程
后台启动:
使用以上命令启动成功后,输入:ctrl + z,输入:bg,输入:exit;
六、SpringBoot中的异常处理
6.1 创建自定义异常类
MyException
package com.qf.exception; public class MyException extends Exception { public MyException(String message) { super(message); } }
6.2 在Controller测试异常
在Controller添加方法
//测试异常 @RequestMapping("add") public String add(){ musicService.add(); return "success"; } //测试自定义异常 @RequestMapping("delete") public String deleteUser() throws MyException { musicService.delete(); return "success"; }
6.3 创建Service接口以及实现类
在Service添加方法
void add(); void delete()throws MyException;
在ServiceImpl添加方法
@Override public void add() { System.out.println("add..."); int i = 1/0; } @Override public void delete() throws MyException { System.out.println("delete..."); throw new MyException("自定义异常"); }
6.4 创建全局异常处理类
MyExceptionHandler
package com.qf.exception; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @RestControllerAdvice public class MyExceptionHandler { @ExceptionHandler(Exception.class) public Map<String,Object> hander1(Exception e, HttpServletRequest request){ System.out.println("hander1..."); Map<String, Object> map = new HashMap<>(); map.put("code",-1); map.put("msg",e.getMessage()); map.put("url",request.getRequestURL()); return map; } @ExceptionHandler(MyException.class) public Map<String,Object> hander2(MyException e, HttpServletRequest request){ System.out.println("hander2..."); Map<String, Object> map = new HashMap<>(); map.put("code",-1); map.put("msg",e.getMessage()); map.put("url",request.getRequestURL()); return map; } }
访问测试即可!
七、SpringBoot中的过滤器(Listener操作同理)
7.1 创建过滤器
LoginFilter
package com.qf.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter("/*") public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("进入LoginFilter之前..."); //放行 filterChain.doFilter(servletRequest,servletResponse); System.out.println("进入LoginFilter之后..."); } }
7.2 创建Controller中添加方法测试
package com.qf.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("user") public class UserController { //测试过滤器 @RequestMapping("login") public String testFilter(){ System.out.println("登录"); return "success"; } }
7.3 在启动类添加@ServletComponentScan注解
package com.qf; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @SpringBootApplication @MapperScan("com.qf.mapper")//扫描mapper @ServletComponentScan//Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册 public class Springboot02Application { public static void main(String[] args) { SpringApplication.run(Springboot02Application.class, args); } }
访问测试即可!
八、SpringBoot中的拦截器
8.1 创建自定义拦截器
package com.qf.interceptor; import org.omg.PortableInterceptor.Interceptor; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { //进入controller方法之前调用 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); return true;//true表示放行,false表示不放行 } //调用完controller之后,视图渲染之前 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } //页面跳转之后,整个流程执行之后,一般用于资源清理操作 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); } }
8.2 创建拦截器配置类
package com.qf.interceptor; 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 MyInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //设置拦截器并指定拦截路径 //registry.addInterceptor(new MyInterceptor()).addPathPatterns("/user/login"); //registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");//拦截所有 registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login");//指定不拦截 //添加自定义拦截器 WebMvcConfigurer.super.addInterceptors(registry); } }
访问对应方法测试即可!
8.3 跨域拦截器配置(补充)
配置后不需要在每一个Controller上面写@CrossOrigin("*")注解了
8.3.1 编写拦截器
package com.qf.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @Component public class CorsInterceptor implements HandlerInterceptor{ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception { // 此处配置的是允许任意域名跨域请求,可根据需求指定 response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin")); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS"); response.setHeader("Access-Control-Max-Age", "86400"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Authorization"); return true; } }
8.3.2 编写拦截器配置类
package com.qf.config; import com.qf.interceptor.CorsInterceptor; import org.springframework.beans.factory.annotation.Autowired; 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 WebConfig implements WebMvcConfigurer { @Autowired private CorsInterceptor corsInterceptor; /** * 配置springboot拦截注册 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(corsInterceptor).addPathPatterns("/**"); } }
九、SpringBoot整合Thymeleaf
9.1导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
9.2在application.properties文件添加配置
########## 配置thymeleaf ########## spring.thymeleaf.cache=false spring.thymeleaf.encoding=utf-8 spring.thymeleaf.prefix=classpath:/templates spring.thymeleaf.suffix=.html spring.thymeleaf.mode=HTML5 spring.thymeleaf.servlet.content-type=text/html
9.3创建实体类
package com.qf.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String username; private Date birthday; }
9.4创建Controller
package com.qf.controller; import com.qf.pojo.User; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import java.util.ArrayList; import java.util.Date; @Controller @RequestMapping("user") public class UserController { @RequestMapping("findAll") public String findAll(Model model){ //模拟数据库查询 ArrayList<User> users = new ArrayList<>(); users.add(new User(1001,"张三",new Date())); users.add(new User(1002,"李四",new Date())); users.add(new User(1003,"王五",new Date())); users.add(new User(1004,"赵六",new Date())); model.addAttribute("users",users); return "/list"; } @RequestMapping("findById") public String findById(Integer uid,Model model){ System.out.println(uid); //模拟数据库查询 User db_user = new User(uid,"杰克",new Date()); model.addAttribute("user",db_user); return "/queryOne"; } }
9.5在templates目录下创建list.html以及queryOne.html
list.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div th:if="${users!=null}"> <table border="1" width="600"> <tr th:each="user,state : ${users}"> <td th:text="${state.count}"></td> <td th:text="${user.id}"></td> <td th:text="${user.username}"></td> <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td> </tr> </table> <hr> <table border="1" width="600"> <!-- 第二个变量,可以获取遍历的元素的状态--> <tr th:each="user,state : ${users}"> <td th:text="${state.index}"></td> <td th:text="${user.id}"></td> <td th:text="${user.username}"></td> <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td> </tr> </table> <hr> <table border="1" width="600"> <!--如果不设置表示状态的变量,默认遍历的元素的变量名+Stat 表示状态的变量--> <tr th:each="user : ${users}"> <td th:text="${userStat.count}"></td> <td th:text="${user.id}"></td> <td th:text="${user.username}"></td> <td th:text="${#dates.format(user.birthday, 'yyyy-MM- dd HH:mm:ss')}"></td> </tr> </table> </div> <hr color="red"> <table border="1" width="600"> <tr> <th>序号</th> <th>姓名</th> <th>生日</th> <th>详情</th> </tr> <tr th:each="user : ${users}"> <td th:text="${userStat.count}"></td> <td th:text="${user.username}"></td> <td th:text="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}"></td> <td><a th:href="@{/user/findById(uid=${user.id})}">查询</a></td> </tr> </table> </body> </html>
queryOne.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form th:object="${user}" action="http://www.baidu.com"> <!-- *{...}选择表达式一般跟在th:object后,直接选择object中的属性--> <input type="hidden" th:id="*{id}" name="id"> 用户名:<input type="text" th:value="*{username}" name="username" /><br /><br /> 生日:<input type="date" th:value="${#dates.format(user.birthday, 'yyyy-MM-dd HH:mm:ss')}" name="birthday"/><br /><br /> <input type="submit" value="提交"> <input type="reset" value="重置"> </form> </body> </html>
十、SpringBoot整合FreeMarker
10.1导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency>
10.2在application.properties文件添加配置
#配置freemarker spring.freemarker.charset=UTF-8 spring.freemarker.content-type=text/html #缓存 spring.freemarker.cache=false #加载路径(前缀) spring.freemarker.template-loader-path=classpath:/templates #后缀(不能省略) spring.freemarker.suffix=.ftl
10.3创建实体类
package com.qf.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Student { private Integer id; private String name; private Integer age; private String address; }
10.4在templates目录下创建student.ftl文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <table border="1" width=600> <tr> <th>index</th> <th>id</th> <th>name</th> <th>age</th> <th>address</th> </tr> <#list students as student> <#if student_index % 2 == 0> <tr bgcolor="red"> <#else> <tr bgcolor="yellow"> </#if> <td>${student_index}</td> <td>${student.id}</td> <td>${student.name}</td> <td>${student.age}</td> <td>${student.address}</td> </tr> </#list> </table> </body> </html>
10.5创建controller
package com.qf.controller; import com.qf.pojo.Student; import freemarker.template.Configuration; import freemarker.template.Template; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @Controller @RequestMapping("student") public class StudentController { @RequestMapping("findAll") public String findAll(Model model){ System.out.println("findAll..."); ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,"张三",20,"二七区")); students.add(new Student(2,"李四",21,"金水区")); students.add(new Student(3,"王五",22,"中原区")); students.add(new Student(4,"赵六",23,"经开区")); model.addAttribute("students",students); return "/student"; } //网页静态化,提高访问效率 //生成静态页面 @RequestMapping("createHtml") @ResponseBody public String createHtml() throws Exception { //准备数据 ArrayList<Student> students = new ArrayList<>(); students.add(new Student(1,"张三",20,"二七区")); students.add(new Student(2,"李四",21,"金水区")); students.add(new Student(3,"王五",22,"中原区")); students.add(new Student(4,"赵六",23,"经开区")); //创建Map HashMap<String, Object> map = new HashMap<>(); map.put("students",students); //创建配置类 Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); //设置字符集 configuration.setDefaultEncoding("utf-8"); //设置要加载的模版目录 configuration.setDirectoryForTemplateLoading(new File("D:\\java2111\\springboot-03\\src\\main\\resources\\templates")); //获取某一个模板 Template template = configuration.getTemplate("student.ftl"); //创建输出流 FileWriter fileWriter = new FileWriter("D:\\java2111\\springboot-03\\src\\main\\resources\\static\\student.html"); //创建页面 template.process(map,fileWriter); //关流 fileWriter.close(); return "success"; } }
启动工程,先访问 http://localhost:8080/student/createHtml,然后点击IDEA查看static是否生成student.html页面,如果生成后,则可以直接访问 http://localhost:8080/student.html 查看页面
十一、SpringBoot整合Redis
11.1导入依赖
<!-- 配置redis所需要的依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency>
11.2创建application.yml文件并添加配置
spring: redis: host: 127.0.0.1 port: 6379 database: 0 lettuce: pool: max-active: 100
11.3创建User类
package com.qf.pojo; import lombok.Data; import java.io.Serializable; @Data public class User implements Serializable { private Integer id; private String name; private String password; }
11.4导入Redis序列化配置类
package com.qf.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig{ /** * springboot 默认帮我们创建的RedisTemplate的key和value的序列化方式是jdk默认的方式, * 我们有时候手动向redis中添加的数据可能无法被查询解析出来,所以我们需要修改序列化方式 * @param connectionFactory * @return */ @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(connectionFactory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); redisTemplate.setKeySerializer(stringRedisSerializer); //设置key的序列化方式 redisTemplate.setHashKeySerializer(stringRedisSerializer);//设置hash类型的数据的key的序列化方式 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);//非final类型的数据才会被序列化 jackson2JsonRedisSerializer.setObjectMapper(objectMapper); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//设置value的序列化方式为json redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); return redisTemplate; } }
11.5导入Redis工具类
package com.qf.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @Component public final class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time, TimeUnit timeUnit) { try { if (time > 0) { redisTemplate.expire(key, time, timeUnit); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * * @param key 键 不能为null * @param item 项 不能为null */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key 键 * @param map 对应多个键值 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time, TimeUnit timeUnit) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time, timeUnit); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time, TimeUnit timeUnit) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time, timeUnit); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key 键 * @param item 项 * @param by 要增加几(大于0) */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key 键 * @param item 项 * @param by 要减少记(小于0) */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, TimeUnit timeUnit, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time, timeUnit); } return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key 键 */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key 键 */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) */ public boolean lSet(String key, Object value, long time,TimeUnit timeUnit) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) { expire(key, time,timeUnit); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time,TimeUnit timeUnit) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time,timeUnit); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key 键 * @param index 索引 * @param value 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
11.6在项目自带的测试类中测试
package com.qf; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.qf.pojo.User; import com.qf.utils.RedisUtil; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.concurrent.TimeUnit; @SpringBootTest class Springboot04RedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisUtil redisUtil; @Test void contextLoads() { //对应类型 redisTemplate.opsForValue();//String类型 redisTemplate.opsForList();//List类型 redisTemplate.opsForSet();//Set类型 redisTemplate.opsForZSet();//ZSet类型 redisTemplate.opsForHash();//Hash类型 //redisTemplate测试 // User user = new User(); // user.setId(1001); // user.setName("张三"); // user.setPassword("123"); // // redisTemplate.opsForValue().set("user",user); // // User db_user = (User)redisTemplate.opsForValue().get("user"); // System.out.println(db_user); //stringRedisTemplate测试 // User user = new User(); // user.setId(1001); // user.setName("张三"); // user.setPassword("123"); // // //转Json // String jsonUser = null; // try { // jsonUser = new ObjectMapper().writeValueAsString(user); // } catch (JsonProcessingException e) { // e.printStackTrace(); // } // //保存数据 // stringRedisTemplate.opsForValue().set("user",jsonUser,30, TimeUnit.SECONDS); // //获取数据 // String db_jsonUser = stringRedisTemplate.opsForValue().get("user"); // System.out.println(db_jsonUser); //redisUtil测试 User user = new User(); user.setId(1001); user.setName("张三"); user.setPassword("123"); redisUtil.set("user",user); User db_user = (User)redisUtil.get("user"); System.out.println(db_user); } }
十二、SpringBoot整合Quartz以及异步方法调用
12.1 异步方法调用
1.创建AsyncController
package com.qf.controller; import com.qf.service.AsyncService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("async") public class AsyncController { @Autowired private AsyncService asyncService; @RequestMapping("testAsync") public String testAsync(){ asyncService.testAsync(); return "AsyncController"; } }
2.创建AsyncService
package com.qf.service; public interface AsyncService { void testAsync(); }
3.创建AsyncServiceImpl
package com.qf.service.impl; import com.qf.service.AsyncService; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncServiceImpl implements AsyncService { @Async//设置当前方法为异步方法 @Override public void testAsync() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("AsyncServiceImpl"); } }
4.在启动上添加开启异步注解,然后测试
package com.qf; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync//使用异步方法 public class Springboot05QuartzApplication { public static void main(String[] args) { SpringApplication.run(Springboot05QuartzApplication.class, args); } }
12.2使用@Scheduled注解实现定时任务
1.创建任务类,多个任务执行时,默认使用单线程,可以通过异步方法使其为多线程
package com.qf.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import java.util.Date; //任务类 @Configuration public class Tasks { private Logger logger = LoggerFactory.getLogger(Tasks.class); @Async @Scheduled(cron = "*/2 * * * * ?") public void task1(){ //System.out.println("task1:"+new Date().toLocaleString()); logger.info("task1:"+new Date().toLocaleString()); } @Async @Scheduled(cron = "*/3 * * * * ?") public void task2(){ //System.out.println("task2:"+new Date().toLocaleString()); logger.info("task2:"+new Date().toLocaleString()); } }
2.在启动上添加开启任务注解,然后测试
package com.qf; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableAsync//使用异步方法 @EnableScheduling//开启任务 public class Springboot05QuartzApplication { public static void main(String[] args) { SpringApplication.run(Springboot05QuartzApplication.class, args); } }
12.3 定时发送邮件案例
1.导入依赖
<!-- 邮箱 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
2.使用MailUtils工具类进行测试
package com.qf.utils; import javax.mail.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import java.util.Properties; import java.util.Random; /** * 发邮件工具类 */ public final class MailUtils { private static final String USER = "729953102@qq.com"; // 发件人邮箱地址 private static final String PASSWORD = "kmrmcaazztfjbefj"; // qq邮箱的客户端授权码(需要发短信获取) /** * @param to 收件人邮箱地址 * @param text 邮件正文 * @param title 标题 */ /* 发送验证信息的邮件 */ public static boolean sendMail(String to, String text, String title) { try { final Properties props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.host", "smtp.qq.com"); // 发件人的账号 props.put("mail.user", USER); //发件人的密码 props.put("mail.password", PASSWORD); // 构建授权信息,用于进行SMTP进行身份验证 Authenticator authenticator = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { // 用户名、密码 String userName = props.getProperty("mail.user"); String password = props.getProperty("mail.password"); return new PasswordAuthentication(userName, password); } }; // 使用环境属性和授权信息,创建邮件会话 Session mailSession = Session.getInstance(props, authenticator); // 创建邮件消息 MimeMessage message = new MimeMessage(mailSession); // 设置发件人 String username = props.getProperty("mail.user"); InternetAddress form = new InternetAddress(username); message.setFrom(form); // 设置收件人 InternetAddress toAddress = new InternetAddress(to); message.setRecipient(Message.RecipientType.TO, toAddress); // 设置邮件标题 message.setSubject(title); // 设置邮件的内容体 message.setContent(text, "text/html;charset=UTF-8"); // 发送邮件 Transport.send(message); return true; } catch (Exception e) { e.printStackTrace(); } return false; } //随机生成num个数字验证码 public static String getValidateCode(int num) { Random random = new Random(); String validateCode = ""; for (int i = 0; i < num; i++) { //0 - 9 之间 随机生成 num 次 int result = random.nextInt(10); validateCode += result; } return validateCode; } //测试 public static void main(String[] args) throws Exception { //给指定邮箱发送邮件 MailUtils.sendMail("729953102@qq.com", "你好,这是一封测试邮件,无需回复。", "测试邮件随机生成的验证码是:" + getValidateCode(6)); System.out.println("发送成功"); } }
3.编写邮箱任务类
package com.qf.task; import com.qf.utils.MailUtils; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; @Configuration public class TaskMail { //指定时间进行发送邮件 @Scheduled(cron = "10 55 * * * ?") public void sendMail(){ MailUtils.sendMail("729953102@qq.com", "你好,这是一封测试邮件,无需回复。", "测试邮件随机生成的验证码是:" + MailUtils.getValidateCode(6)); } }
启动项目测试即可!
12.4整合quartz,首先导入依赖
1.导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
2.编写任务类
package com.qf.quartz.job; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.util.Date; public class MyJob implements Job { @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("MyJob:"+ new Date().toLocaleString()); } }
3.编写配置类
package com.qf.quartz; import com.qf.quartz.job.MyJob; import org.quartz.CronTrigger; import org.quartz.JobDetail; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.CronTriggerFactoryBean; import org.springframework.scheduling.quartz.JobDetailFactoryBean; import org.springframework.scheduling.quartz.SchedulerFactoryBean; @Configuration public class QuartzConfig { //任务 @Bean public JobDetailFactoryBean getJobDetailFactoryBean(){ JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean(); //指定任务 jobDetailFactoryBean.setJobClass(MyJob.class); return jobDetailFactoryBean; } //触发器 @Bean public CronTriggerFactoryBean getCronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean){ CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); JobDetail jobDetail = jobDetailFactoryBean.getObject(); //指定任务详情 cronTriggerFactoryBean.setJobDetail(jobDetail); //指定Cron表达式 cronTriggerFactoryBean.setCronExpression("*/3 * * * * ?"); return cronTriggerFactoryBean; } //调度器 @Bean public SchedulerFactoryBean getSchedulerFactoryBean(CronTriggerFactoryBean cronTriggerFactoryBean){ SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); CronTrigger cronTrigger = cronTriggerFactoryBean.getObject(); //指定触发器 schedulerFactoryBean.setTriggers(cronTrigger); return schedulerFactoryBean; } }
4.启动测试类测试即可
十三、SpringBoot日志
SpringBoot默认使用的日志是Logback,官方建议日志文件命名为:logback-spring.xml
13.1在resources目录下创建logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。 默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <configuration scan="true" scanPeriod="60 seconds" debug="true"> <!-- 定义变量,可通过 ${log.path}和${CONSOLE_LOG_PATTERN} 得到变量值 --> <property name="log.path" value="D:/log" /> <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} |-[%-5p] in %logger.%M[line-%L] -%m%n"/> <!-- 输出到控制台 --> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!-- Threshold=即最低日志级别,此appender输出大于等于对应级别的日志 (当然还要满足root中定义的最低级别) --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>debug</level> </filter> <encoder> <!-- 日志格式(引用变量) --> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!-- 追加到文件中 --> <appender name="file1" class="ch.qos.logback.core.FileAppender"> <file>${log.path}/mylog1.log</file> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- 滚动追加到文件中 --> <appender name="file2" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/mylog2.log</file> <!--日志文件输出格式--> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> <charset>UTF-8</charset> <!-- 设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 文件超过最大尺寸后,会新建文件,然后新的日志文件中继续写入 如果日期变更,也会新建文件,然后在新的日志文件中写入当天日志 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 新建文件后,原日志改名为如下 %i=文件序号,从0开始 --> <fileNamePattern>${log.path}/newlog-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- 每个日志文件的最大体量 --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>1kb</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!-- 日志文件保留天数,1=则只保留昨天的归档日志文件 ,不设置则保留所有日志--> <maxHistory>1</maxHistory> </rollingPolicy> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="file1"/> <appender-ref ref="file2"/> </root> </configuration>
启动工程,进行测试
十四、SpringBoot整合Swagger
14.1 Swagger介绍
现在开发,很多采用前后端分离的模式,前端只负责调用接口,进行渲染,前端和后端的唯一联系,变成了API接口。因此,API文档变得越来越重要。swagger是一个方便我们更好的编写API文档的框架,而且swagger可以模拟http请求调用。
大部分采取的方式:Vue + SpringBoot,Vue通过js渲染页面,后端把数据传递给js,早期前端只负责写页面,然后把写好的HTML页面给后端,后端使用模板引擎(Jsp,Thymeleaf、 freemarker)进行开发。
前后端分离的好处:各自开发,相对独立,松耦合,前后端通过API进行交互,后端提供接口给前端,前端去调用该接口,但可能会导致前后端团队人员不能做到及时协商,出现一些问题。解决方式:早期使用实时更新文档,但非常繁琐,后来又使用postman来进行一些测试。
swagger是目前流行的Api框架,官网:API Documentation & Design Tools for Teams | Swagger
14.2导入依赖
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency>
14.3创建配置类
@Configuration @EnableSwagger2//开启Swagger2 public class SwaggerConfig { }
然后启动测试运行,访问:http://localhost:8080/swagger-ui.html,看到如下页面:
14.4手动配置实例,修改SwaggerConfig配置类
package com.qf.swagger.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2//开启Swagger2 public class SwaggerConfig { //配置Swagger的Bean实例 @Bean public Docket createDocket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()); } //配置API的基本信息(会在http://项目实际地址/swagger-ui.html页面显示) private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("测试API文档标题") .description("测试api接口文档描述") .termsOfServiceUrl("http://www.baidu.com") .version("1.0") .build(); } }
再次启动测试运行,访问:http://localhost:8080/swagger-ui.html,看到如下页面:
14.5创建实体类
package com.qf.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @ApiModel("用户") public class User { @ApiModelProperty("编号") private String uid; @ApiModelProperty("用户名") private String username; @ApiModelProperty("密码") private String password; public String getUid() { return uid; } public void setUid(String uid) { this.uid = uid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
14.6创建controller
package com.qf.controller; import com.qf.entity.User; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; //@Api(description = "用户接口") @Api(tags = "用户接口") @RestController @RequestMapping("/user") public class UserController { @ApiOperation("查询单个用户") @RequestMapping("/findById") public User findById(@RequestParam @ApiParam("用户ID") String uid){ User user = new User(); user.setUid(uid); user.setUsername("张三"); user.setPassword("123"); return user; } @ApiOperation("删除单个用户") @PostMapping("/delete") public User delete(String uid){ User user = new User(); user.setUid(uid); user.setUsername("李四"); user.setPassword("456"); return user; } @ApiOperation("查询所有用户") @GetMapping("/findAll") public List<User> findAll(){ User user1 = new User(); user1.setUid("1001"); user1.setUsername("张三"); user1.setPassword("123"); User user2 = new User(); user2.setUid("1002"); user2.setUsername("李四"); user2.setPassword("456"); ArrayList<User> users = new ArrayList<>(); users.add(user1); users.add(user2); return users; } }
14.7修改SwaggerConfig配置类
package com.qf.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2//开启Swagger2 public class SwaggerConfig { //配置Swagger的Bean实例 @Bean public Docket createDocket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(createApiInfo()) .groupName("yangl")分组名称(可以创建多个Docket就有多个组名) .enable(true)//enable表示是否开启Swagger .select() //RequestHandlerSelectors指定扫描的包 .apis(RequestHandlerSelectors.basePackage("com.qf.controller")) .build(); } //配置API的基本信息(会在http://项目实际地址/swagger-ui.html页面显示) public ApiInfo createApiInfo(){ return new ApiInfoBuilder() .title("测试标题") .description("测试描述") .termsOfServiceUrl("http://www.baidu.com") .build(); //return ApiInfo.DEFAULT; } }
Swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiModel:用对象来接收参数 ,修饰类
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiResponse:HTTP响应其中1个描述
@ApiResponses:HTTP响应整体描述,一般描述错误的响应
@ApiIgnore:使用该注解忽略这个API
@ApiError :发生错误返回的信息
@ApiParam:单个参数描述
@ApiImplicitParam:一个请求参数,用在方法上
@ApiImplicitParams:多个请求参数
十五、SpringBoot整合knife4j
15.1 knife4j介绍
官方文档:knife4j
knife4j可以理解swagger的升级版,采用的是后端Java代码和Ui都混合在一个Jar包里面的方式提供给开发者使用,Knife4j不仅仅将前身的Ui皮肤通过Vue技术栈进行了重写,也增加了更多个性化的特性增强功能,基于springfox项目以及OpenAPI的规范,目前主要支持以Java开发为主,并且是依赖于大环境下使用的
Spring MVC
、Spring Boot
、Spring Cloud
框架.
15.2 导入依赖
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>
15.3创建配置类
package com.qf.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; @Configuration @EnableSwagger2WebMvc public class Knife4jConfiguration { @Bean(value = "defaultApi2") public Docket defaultApi2() { Docket docket=new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() //.title("swagger-bootstrap-ui-demo RESTful APIs") .description("# swagger-bootstrap-ui-demo RESTful APIs") .termsOfServiceUrl("http://www.baidu.com/") .contact(new Contact("张三","www.zhangs.com","zhangs@qq.com")) .version("1.0") .build()) //分组名称 .groupName("2.X版本") .select() //这里指定Controller扫描包路径 .apis(RequestHandlerSelectors.basePackage("com.qf.controller")) //.paths(PathSelectors.any()) .build(); return docket; } }
15.4 创建controller
package com.qf.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.util.UUID; @Api(tags = "首页模块") @RestController public class IndexController { @ApiImplicitParam(name = "name",value = "姓名",required = true)//参数说明 @ApiOperation(value = "向客人问好") @GetMapping("/sayHi") public ResponseEntity<String> sayHi(@RequestParam(value = "name")String name){ return ResponseEntity.ok("Hi:"+name);//ResponseEntity返回对应数据 } }