介绍
本文将对Springboot框架开发过程中常用的技术要点进行汇总,包括过滤器与监听器引入、文件上传下载、集成Thymeleaf模板、Thymeleaf模板语法、整合Hibernate-validate校验框架、集成Spring Data Redis缓存、集成Quartz定时任务框架、Cron表达式常见用法、全局异常实现和集成Mybatis框架等方面。
目录
一、项目初始化
1.1、下载项目
Springboot项目初始化很简单,直接去官网下载即可,我这是选的是Maven项目,版本是2.1.13.RELEASE。
1.2、方法验证
1.2.1、引入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
1.2.2、新建TestController类
编写一个getMsg()
测试方法,启动DemoApplication。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
public class TestController {
@RequestMapping("/test")
@ResponseBody
public Map<String, Object> getMsg(){
Map<String, Object> map = new HashMap<>();
map.put("result", "response msg");
return map;
}
}
1.2.3、效果查看
在浏览器中访问http://localhost:8080/test
地址,请求响应成功
二、过滤器与监听器
这两者项目中很常见,我们这里使用注解模式实现
2.1、过滤器Filter
1)在config目录下,新建MyFilter
过滤器,这里需要使用到@WebFilter
注解。
filterName为过滤器名称,urlPatterns匹配过滤请求类型(这里只配置了上文的/test请求,实际可根据需求配置多个,如{"*.do","*.jsp"}等),然后在doFilter()中编写处理逻辑。
package com.example.demo.config;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* 自定义过滤器(注解方式)
*/
@WebFilter(filterName = "MyFilter", urlPatterns = {"/test"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("********** filter start **********");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("********** filter end **********");
}
@Override
public void destroy() {
}
}
2)在启动类DemoApplication中增加组件扫描注解@ServletComponentScan
3)启动应用,访问http://localhost:8080/test
,看到控制台输出如下日志,说明过滤器已经生效了。
2020-06-09 07:53:05.307 INFO 21184 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-06-09 07:53:05.316 INFO 21184 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 9 ms
********** filter start **********
********** filter end **********
2.2、监听器Listenser
1)在config目录下,新建MyListener
监听器,这里需要使用到@WebListener
注解,在contextInitialized()
中编写处理逻辑。
package com.example.demo.config;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 自定义监听器(注解方式)
*/
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("***** init MyListener *****");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
2)启动应用时看到控制台输出如下日志,说明监听器已经生效了。
2020-06-09 07:55:09.607 INFO 16296 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1182 ms
***** init MyListener *****
2020-06-09 07:55:09.821 INFO 16296 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
三、集成Thymeleaf
常用的模板比较多,比如freemaker,我们这里使用官网推荐的Thymeleaf模板
3.1、集成步骤
3.1.1、引入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3.1.2、编写html
在templates
目录下,创建index.html页面,使用th:text
进行文本取值
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<!-- th:text取值 -->
<span th:text="${result}"></span>
</body>
</html>
3.1.3、编写Controller方法
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
/**
* 跳转至index界面
* @param model
* @return
*/
@RequestMapping("/index")
public String index(Model model){
model.addAttribute("result","success");
return "index";
}
}
3.1.4、效果查看
启动应用,访问http://localhost:8080/index
3.2、Thymeleaf常用语法
Thymeleaf模板有一套自己的语法格式,包括字符串取值、日期格式化、条件判断、list和map遍历、域对象和Url处理等,下面我们列举一些实例
3.2.1、字符串取值
<!-- th:text取值 -->
1.<span th:text="${result}"></span><hr/>
<!-- input标签取值 -->
2.<input type="text" th:value="${result}" /><hr/>
<!-- 字符串判空 -->
3.<span th:text="${#strings.isEmpty(result)}"></span><hr/>
<!-- 获取字符串长度 -->
4.<span th:text="${#strings.length(result)}"></span><hr/>
<!-- 字符串截取 -->
5.<span th:text="${#strings.substring(result,4)}"></span><hr/>
3.2.2、日期格式化
<!-- 日期格式化 -->
6.<span th:text="${#dates.format(date)}"></span><hr/>
<!-- 日期格式化(指定格式) -->
7.<span th:text="${#dates.format(date,'yyyy-MM-dd')}"></span><hr/>
3.2.3、条件判断
<!-- if判断 -->
8.<span th:if="${language}==php">php是世界上最好的语言</span><hr/>
<!-- switch判断(等价于if else效果) -->
9.<span th:switch="${language}">
<span th:case="php">php是世界上最好的语言</span>
<span th:case="java">java表示不服</span>
<span th:case="python">python表示不服</span>
</span><hr/>
3.2.4、list和map遍历
<!-- 遍历list -->
10.<table>
<tr>
<th>序号</th>
<th>名称</th>
<th>排名</th>
</tr>
<!-- obj表示数据对象,v表示状态变量(包括index、count、size等参数) -->
<tr th:each="obj,v : ${list}">
<td th:text="${v.index}"></td>
<td th:text="${obj.name}"></td>
<td th:text="${obj.sort}"></td>
</tr>
</table><hr/>
<!-- 遍历map -->
11.<table>
<tr>
<th>名称</th>
<th>排名</th>
</tr>
<!-- 需要迭代两次 -->
<tr th:each="maps : ${map}">
<td th:each="entry:${maps}" th:text="${entry.value.name}"></td>
<td th:each="entry:${maps}" th:text="${entry.value.sort}"></td>
</tr>
</table><hr/>
3.2.5、域对象
<!-- 域对象取值方式 -->
12.Request:<span th:text="${#httpServletRequest.getAttribute('req')}"></span>|
Session:<span th:text="${session.se}"></span>|
Application:<span th:text="${application.app}"></span><hr/>
3.2.6、Url处理
<!-- url处理 -->
13.<a th:href="@{https://blog.csdn.net}" target="_blank" >绝对路径</a>|
<a th:href="@{/welcome}" target="_blank" />相对路径</a>|
<a th:href="@{/welcome(name=python, sort=第二)}" target="_blank" />带参数url</a><hr/>
3.2.7、后台方法
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Controller
public class IndexController {
/**
* 跳转至index界面
* @param model
* @return
*/
@RequestMapping("/index")
public String index(Model model, HttpServletRequest req){
model.addAttribute("result","success");
model.addAttribute("date",new Date());
model.addAttribute("language","php");
//构造list
List<Language> list = new ArrayList<>();
list.add(new Language("java","第一"));
list.add(new Language("python","第二"));
list.add(new Language("php","第三"));
model.addAttribute("list",list);
//构造map
Map<String, Language> map = new HashMap<>();
map.put("key1",new Language("java","第一"));
map.put("key2",new Language("python","第二"));
map.put("key3",new Language("php","第三"));
model.addAttribute("map",map);
//域对象
req.setAttribute("req","HttpServletRequest对象");
req.getSession().setAttribute("se","HttpSession对象");
req.getSession().getServletContext().setAttribute("app","Application对象");
return "index";
}
/**
* java、python、php
*/
class Language{
private String name;//语言名称
private String sort;//排名
public Language(String name, String sort) {
this.name = name;
this.sort = sort;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
}
/**
* 跳转至welcome界面
* @param name
* @param sort
* @return
*/
@RequestMapping("/welcome")
public String index(String name, String sort){
System.out.println("name="+name+",sort="+sort);
return "welcome";
}
}
3.2.8、效果查看
启动应用,访问http://localhost:8080/index
四、文件上传
文件上传功能是每个系统中必备的功能,这里会演示上传流程和阈值设置方法
4.1、编写上传页面
在templates
目录下,创建testUpload.html
,并编写上传form表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TestUpload</title>
</head>
<body>
<!-- 文件上传form表单 -->
<form action="myUpload" method="post" enctype="multipart/form-data" >
<input type="file" name="fileName" />
<input type="submit" value="上传" />
</form>
</body>
</html>
4.2、Controller方法
创建TestUploadController
,编写upload()
上传方法
package com.example.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Controller
public class TestUploadController {
/**
* 跳转至上传页面
* @return
*/
@RequestMapping("/toUpload")
public String toUpload(){
return "/testUpload";
}
/**
* 测试文件上传
* @param fileName
* @return
* @throws IOException
*/
@RequestMapping("/myUpload")
@ResponseBody
public String upload(MultipartFile fileName) throws IOException{
//1.获取上传文件名
String originalFilename = fileName.getOriginalFilename();
//2.将文件保存到对应目录
File newFileName = new File("d:/" + originalFilename);
fileName.transferTo(newFileName);
//3.返回处理状态
return "success";
}
}
4.3、效果查看
启动应用,访问http://localhost:8080/toUpload
,选择一张图片,点击上传,可以看到图片上传到了D盘中
4.4、文件大小阈值设置
通常我们会限制上传文件的大小,这只要在application.properties中配置两个属性即可,第一个表示单个文件上传限制(默认1MB)
,第二个表示单次请求文件上传限制(默认10MB)
# 单个文件上传限制(默认1MB)
spring.servlet.multipart.max-file-size=50MB
# 单次请求文件上传限制(默认10MB)
spring.servlet.multipart.max-request-size=100MB
五、整合Hibernate-validate
5.1、在请求对象中添加校验注解
后端参数校验也是项目中必不可少的一部分,我们这里使用Hibernate-validate与Thymeleaf整合,编写一个实例。
在model包中新建请求对象UserReq
,再给字段添加判断注解即可,比如判空校验、长度校验、格式校验等
package com.example.demo.model;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
@Data
public class UserReq {
@NotBlank(message = "用户名必填")
@Length(min = 6,max = 15,message = "用户名长度为6-15位")
private String username;
@NotBlank(message = "密码必填")
private String password;
@Min(value = 18,message = "年龄不能小于18岁")
private int age;
@Email(message = "邮箱格式不合法")
private String email;
}
备注:这里使用了lombok注解,需要先引入pom依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
5.2、在控制器中方法中开启校验模式
这里需注意两点,第一,在UserReq对象前面加上@Valid
注解即开启校验模式。第二,使用@ModelAttribute("validTip")
注解指定校验信息取值方式,validTip
是自定义的,这个在html中获取校验信息时会用到。
package com.example.demo.controller;
import com.example.demo.model.UserReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.Valid;
@Controller
@Slf4j
public class UserController {
/**
* 使用@ModelAttribute("validTip")指定校验失败提示信息key为validTip
* @param req
* @return
*/
@RequestMapping("/addUser")
public String showPage(@ModelAttribute("validTip") UserReq req){
return "/user/add";
}
/**
* Valid 注解示开启数据校验
* BindingResult 返回校验的结果
**/
@RequestMapping("/save")
public String saveUser(@ModelAttribute("validTip") @Valid UserReq req, BindingResult result){
//如果校验不通过,返回原界面
if(result.hasErrors()){
return "/user/add";
}
log.info(" valid success");
return "success";
}
}
5.3、视图模板回显提示信息
在templates目录下创建user文件夹,再新建add.html
模板。可以看到,我们通过th:errors="${validTip.username}
方式获取校验提示信息,validTip
是我们之前在控制方法中指定的,username
为对应字段名。
<!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:action="@{/save}" method="post">
姓名:<input type="text" name="username"/><font color="red" th:errors="${validTip.username}"></font><br/>
密码:<input type="password" name="password" /><font color="red" th:errors="${validTip.password}"></font><br/>
年龄:<input type="text" name="age" /><font color="red" th:errors="${validTip.age}"></font><br/>
邮箱:<input type="text" name="email" /><font color="red" th:errors="${validTip.email}"></font><br/>
<input type="submit" value="提交"/> </form>
</body>
</html>
5.4、效果查看
六、全局异常实现
6.1、编写异常测试类
这里定义了两个方法,分别为sysExceptionTest()
和bizExceptionTest()
package com.example.demo.controller;
import com.example.demo.exception.biz.BizException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestExceptionController {
/**
* 系统异常测试方法
* @return
*/
@RequestMapping("/sysException")
public String sysExceptionTest(){
// 模拟一个空指针异常
String str = null;
System.out.println(str.hashCode());
return "index";
}
/**
* 业务异常测试方法
* @return
*/
@RequestMapping("/bizException")
public String bizExceptionTest(){
// 自定义异常提示
if(true){
throw new BizException("-1","这是一个自定义异常提示");
}
return "index";
}
}
6.2、自定义业务异常类BizException
package com.example.demo.exception;
public class BizException extends RuntimeException {
private static final long serialVersionUID = 1L;
protected String errorCode; //错误码
protected String errorMsg; //错误信息
public BizException() {
super();
}
public BizException(String errorMsg) {
super(errorMsg);
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg) {
super(errorCode);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg, Throwable cause) {
super(errorCode, cause);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public String getMessage() {
return errorMsg;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
6.3、定义全局异常捕获类GlobalException
这里需要注意一点,如果是想将所有异常抛到统一的error界面,则instanceof Exception
即可。如果是想将不同异常分别跳转至不同错误页,则需根据具体异常类型判断。
package com.example.demo.exception;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常类
*/
@Configuration
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mv = new ModelAndView();
//1、所有异常统一抛到error.html界面
if(e instanceof Exception){
mv.setViewName("error");
}
//2、不同异常类型,跳转至自定义的不同界面
// if(e instanceof NullPointerException){
// mv.setViewName("error1");
// }
// if(e instanceof ArithmeticException){
// mv.setViewName("error2");
// }
// if(e instanceof ClassCastException){
// mv.setViewName("error3");
// }
// if(e instanceof ArrayIndexOutOfBoundsException){
// mv.setViewName("error4");
// }
//3、将异常信息放入ModelAndView对象返回
mv.addObject("errorMsg", e.toString());
return mv;
}
}
6.4、编写统一异常页面error.html
在templates目录下创建error.html模板,errorMsg
为GlobalException中捕获返回的异常信息
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>异常信息界面</title>
</head>
<body>
哎呀,服务器出错了!!!<br/>
异常信息:<span th:text="${errorMsg}"></span>
</body>
</html>
6.5、效果查看
1)访问http://localhost:8080/sysException
2)访问http://localhost:8080/bizException
七、集成Spring Data Redis
7.1、引入依赖
先引入redis的pom依赖
<!-- 引入spring data redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 引入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
7.2、创建RedisConfig配置类
初始化redis连接池和连接工厂,并创建redisTemplate操作模板。
说明
:@ConfigurationProperties(prefix = "spring.redis.pool")
与@ConfigurationProperties(prefix = "spring.redis")
分别对应application.properties中的配置信息,它们会将指定的前缀相同各个配置实例化为一个配置对象。
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class RedisConfig {
/**
* 连接池配置
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.redis.pool")
public JedisPoolConfig jedisPoolConfig(){
return new JedisPoolConfig();
}
/**
* 连接工厂配置
* @param cfg
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.redis")
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig cfg){
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setPoolConfig(cfg);
return factory;
}
/**
* RedisTemplate操作模板
* @param factory
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(JedisConnectionFactory factory){
RedisTemplate<String, Object> rt = new RedisTemplate<>();
rt.setConnectionFactory(factory);
rt.setKeySerializer(new StringRedisSerializer());//key序列化
rt.setValueSerializer(new StringRedisSerializer());//value序列化
return rt;
}
}
7.3、添加配置信息
在application.properties中添加如下配置
# redis配置信息
# 分别为最小空闲连接、最大空闲连接、最大连接数、ip、端口、指定库(默认0)
spring.redis.pool.min-idle=5
spring.redis.pool.max-idle=10
spring.redis.pool.max-total=20
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=1
7.4、新建一个MovieInfo对象
package com.example.demo.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 电影实体类
*/
@Data
public class MovieInfo implements Serializable {
private Long id; //主键
private String name; //名称
private String role; //主演
private String area; //地区(大陆、香港、美国)
}
7.5、效果查看
在Junit测试类中,使用3种方法进行存/取值
1、字符串存/取
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* redis存值(字符串)
*/
@Test
public void redisSetStr(){
this.redisTemplate.opsForValue().set("movieName","无间道");
}
/**
* redis取值(字符串)
*/
@Test
public void redisGetStr(){
System.out.println("get str: "+this.redisTemplate.opsForValue().get("movieName"));
}
2、对象存/取
/**
* redis存值(对象)
*/
@Test
public void redisSetObj(){
MovieInfo movie = new MovieInfo();
movie.setId(1L);
movie.setName("当幸福来敲门");
movie.setRole("威尔史密斯");
movie.setArea("美国");
this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
this.redisTemplate.opsForValue().set("movie",movie);
}
/**
* redis取值(对象)
*/
@Test
public void redisGetObj(){
this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
MovieInfo movie = (MovieInfo) this.redisTemplate.opsForValue().get("movie");
System.out.println("get obj: "+movie);
}
3、Json对象存/取
/**
* redis存值(json格式对象)
*/
@Test
public void redisSetJsonObj(){
MovieInfo movie = new MovieInfo();
movie.setId(1L);
movie.setName("当幸福来敲门");
movie.setRole("威尔史密斯");
movie.setArea("美国");
this.redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(MovieInfo.class));
this.redisTemplate.opsForValue().set("movie_json",movie);
}
/**
* redis取值(json格式对象)
*/
@Test
public void redisGetJsonObj(){
this.redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(MovieInfo.class));
MovieInfo movie = (MovieInfo) this.redisTemplate.opsForValue().get("movie_json");
System.out.println("get json obj: "+movie);
}
效果如下
八、集成Quartz定时任务框架
8.1、集成步骤
8.1.1、引入pom依赖
<!-- 引入Scheduled -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- 引入Quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入Sprng tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
8.1.2、自定义定时任务
自定义TestJob
定时任务
package com.example.demo.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.util.Date;
/**
* 测试job
*/
public class TestJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
System.out.println("定时Job执行 :"+ new Date());
}
}
8.1.3、扩展AdaptableJobFactory类
package com.example.demo.job;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
/**
* 自定义扩展AdaptableJobFactory
*/
@Component("AdaptableJobFactoryExtend")
public class AdaptableJobFactoryExtend extends AdaptableJobFactory {
//AutowireCapableBeanFactory 可以将一个对象添加到 SpringIOC 容器中, 并且完成该对象注入
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
/*** 该方法需要将实例化的任务对象手动的添加到 springIOC 容器中并且完成对 象的注入 */
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object obj = super.createJobInstance(bundle);
//将 obj 对象添加 Spring IOC 容器中,并完成注入,否则定时任务调用业务对象会报错
this.autowireCapableBeanFactory.autowireBean(obj);
return obj;
}
}
8.1.4、定义QuartzConfig配置类
package com.example.demo.job;
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 {
/**
* 创建Job任务
* @return
*/
@Bean
public JobDetailFactoryBean jobDetailFactoryBean(){
JobDetailFactoryBean job = new JobDetailFactoryBean();
job.setJobClass(TestJob.class);//引入自定义 Job 类
return job;
}
/**
* 创建Cron表达式Trigger触发器
* @param job
* @return
*/
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean job){
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(job.getObject());
trigger.setCronExpression("0/5 * * * * ?"); //每5秒执行一次
return trigger;
}
/**
* 创建Scheduler任务调度器
* @param trigger
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean trigger,AdaptableJobFactoryExtend jobExtend){
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setTriggers(trigger.getObject());//放入trigger触发器
scheduler.setJobFactory(jobExtend);
return scheduler;
}
}
8.1.5、效果查看
启动可以看到,每隔5秒会执行一次TestJob
任务
2020-06-09 21:56:39.974 INFO 11444 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-09 21:56:39.977 INFO 11444 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 3.166 seconds (JVM running for 4.692)
定时Job执行 :Tue Jun 09 21:56:40 CST 2020
定时Job执行 :Tue Jun 09 21:56:45 CST 2020
定时Job执行 :Tue Jun 09 21:56:50 CST 2020
定时Job执行 :Tue Jun 09 21:56:55 CST 2020
8.2、Cron表达式常见用法
定时任务规则通常会使用Cron表达式配置,上小结中我们也使用到了,这里对它再进行详细说明
8.2.1、简介
Cron 表达式是一个字符串,分为 6 或 7 个域,每一个域代表一个含义
-
两种语法格式
- Seconds Minutes Hours Day Month Week (即 秒 分 时 日 月 周)
- Seconds Minutes Hours Day Month Week Year (即 秒 分 时 日 月 周 年)
8.2.2、各域含义
位置 | 时间 | 取值范围 | 支持特殊字符 |
---|---|---|---|
1 | 秒 | 0-59 | , - * / |
2 | 分 | 0-59 | , - * / |
3 | 时 | 0-23 | , - * / |
4 | 日 | 1-31 | , - * / L W C |
5 | 月 | 1-12 | , - * / |
6 | 周 | 1-7 | , - * ? / L W C |
7 | 年(可选) | 1970-2099 | , - * / |
说明
:这里的周(1-7天)是按西方习惯来的,表示从周日开始到下周周六,不是我们习惯的周一到周日,使用时需要注意。
8.2.3、常用特殊字符介绍
1、星号:可用在所有域中,表示对应时间域的每一个时刻,例如,星号在小时域时,表示“每小时”;
2、问号(?):该字符只在日期和周域中使用,它通常指定为“无意义的值”,相当于占位符;(通常日期和周只选择其一设置,因为同时使用会有可能冲突)
3、减号(-):表示范围,如在小时域中使用“8-10”,则表示从 8 到 10 点,即 8,9,10;
4、逗号(,):表示列表值,如在周域中使用“MON,WED,FRI”,则表示星期一,星期三和星期 五;
5、斜杠(/):x/y 表示等步长序列,x 为起始值,y 为增量步长值。如在分钟域中使用 0/15,则 表示为 0,15,30 和 45 秒,而 5/15 在分钟域中表示 5,20,35,50,你也可以使用*/y,它等同于 0/y;
8.2.4、举例
1、@Scheduled(cron="0 0 2 * * *")
表示每天凌晨 2 点执行
2、@Scheduled(cron = "0 0 2 10 * ?")
每月10号凌晨 2 点执行
3、@Scheduled(cron = "0 0 2 10 1,4,7,10 ?")
每个季度的第一个月的10号凌晨 2 点执行
4、@Scheduled(cron = "0 0 2 10 6 ?")
每年6月的10号凌晨 2 点执行
九、集成Mybatis
Mybatis框架不用多说了,平时用的很多持久层框架,我们这里结合一个电影demo,实现简单的增删改查操作即可
9.1、引入pom依赖
我们数据库使用Mysql,连接池使用Druid
<!-- 引入mybatis依赖 -->
<dependency> <groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 引入mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 引入druid连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
9.2、配置文件编写
需要配置mysql连接、指定druid数据源和指定sql映射文件位置
# 配置数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# 指定数据源类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 开启sql日志输出(两种方式都可以)
logging.level.com.example.demo.mapper=debug
#mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 指定sql映射文件位置
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
9.3、创建电影表
CREATE TABLE `t_movie_info` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(32) DEFAULT NULL COMMENT '名称',
`role` varchar(32) DEFAULT NULL COMMENT '主演',
`area` varchar(16) DEFAULT NULL COMMENT '地区(大陆、香港、美国)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='电影信息表';
9.4、entity实体
与数据层对应,集成了lombok插件,无需编写构造方法和get、set方法
package com.example.demo.entity;
import lombok.Data;
/**
* 电影实体类
*/
@Data
public class MovieInfo {
private Long id; //主键
private String name; //名称
private String role; //主演
private String area; //地区(大陆、香港、美国)
}
9.5、model实体
与view层对应
1)新增功能对应请求实体类;
package com.example.demo.model;
import lombok.Data;
/**
* 新增请求实体
*/
@Data
public class AddMovieReq {
private String name; //名称
private String role; //主演
private String area; //地区(大陆、香港、美国)
}
2)编辑功能对应请求实体类;
package com.example.demo.model;
import lombok.Data;
/**
* 编辑请求实体
*/
@Data
public class EditMovieReq {
private Long id;
private String name; //名称
private String role; //主演
private String area; //地区(大陆、香港、美国)
}
9.6、controller层
package com.example.demo.controller;
import com.example.demo.entity.MovieInfo;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;
import com.example.demo.service.IMovieInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/movie")
public class MovieInfoController {
@Autowired
private IMovieInfoService movieInfoService;
/**
* 页面跳转
* @param page
* @return
*/
@RequestMapping("/{page}")
public String showPage(@PathVariable String page){
return "/movie/"+page;
}
/**
* 新增
* @param req
* @return
*/
@RequestMapping("/addSubmit")
public String addMovie(AddMovieReq req){
this.movieInfoService.addMovie(req);
return "redirect:/movie/findMovieInfoList";
}
/**
* 列表查询
* @param model
* @return
*/
@RequestMapping("/findMovieInfoList")
public String findMovieInfoList(Model model){
List<MovieInfo> list = this.movieInfoService.findMovieInfoList();
model.addAttribute("list", list);
return "/movie/list";
}
/**
* 根据ID查询
* @param id
* @param model
* @return
*/
@RequestMapping("/findMovieInfoById")
public String findMovieInfoById(Long id,Model model){
MovieInfo movieInfo = this.movieInfoService.selectMovieInfoById(id);
model.addAttribute("movieInfo", movieInfo);
return "/movie/edit";
}
/**
* 编辑
* @param req
* @return
*/
@RequestMapping("/editSubmit")
public String editMovieInfo(EditMovieReq req){
this.movieInfoService.updateMovieInfo(req);
return "redirect:/movie/findMovieInfoList";
}
/**
* 删除
* @param id
* @return
*/
@RequestMapping("/delMovieInfoById")
public String delMovieInfoById(Long id){
this.movieInfoService.deleteMovieInfoById(id);
return "redirect:/movie/findMovieInfoList";
}
}
9.7、service层
1)接口层
package com.example.demo.service;
import com.example.demo.entity.MovieInfo;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;
import java.util.List;
public interface IMovieInfoService {
/**
* 新增电影
* @param req
*/
void addMovie(AddMovieReq req);
/**
* 列表查询
* @return
*/
List<MovieInfo> findMovieInfoList();
/**
* 根据ID查询
* @param id
* @return
*/
MovieInfo selectMovieInfoById(Long id);
/**
* 修改
*/
void updateMovieInfo(EditMovieReq req);
/**
* 删除
* @param id
*/
void deleteMovieInfoById(Long id);
}
2)实现层
package com.example.demo.service.impl;
import com.example.demo.entity.MovieInfo;
import com.example.demo.mapper.MovieInfoMapper;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;
import com.example.demo.service.IMovieInfoService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class MovieInfoServiceImpl implements IMovieInfoService {
@Autowired
private MovieInfoMapper movieInfoMapper;
@Override
public void addMovie(AddMovieReq req) {
MovieInfo info = new MovieInfo();
BeanUtils.copyProperties(req,info);
this.movieInfoMapper.insertMovieInfo(info);
}
@Override
public List<MovieInfo> findMovieInfoList() {
return this.movieInfoMapper.selectMovieInfoList();
}
@Override
public MovieInfo selectMovieInfoById(Long id) {
return this.movieInfoMapper.selectMovieInfoById(id);
}
@Override
public void updateMovieInfo(EditMovieReq req) {
MovieInfo info = new MovieInfo();
BeanUtils.copyProperties(req,info);
this.movieInfoMapper.updateMovieInfo(info);
}
@Override
public void deleteMovieInfoById(Long id) {
this.movieInfoMapper.deleteMovieInfoById(id);
}
}
9.8、mapper层
package com.example.demo.mapper;
import com.example.demo.entity.MovieInfo;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MovieInfoMapper {
void insertMovieInfo(MovieInfo info);
List<MovieInfo> selectMovieInfoList();
MovieInfo selectMovieInfoById(Long id);
void updateMovieInfo(MovieInfo info);
void deleteMovieInfoById(Long id);
}
9.9、sql编写
在resources
目录下创建mapper文件夹,再新建MovieInfoMapper.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.MovieInfoMapper">
<insert id="insertMovieInfo" parameterType="com.example.demo.entity.MovieInfo">
insert into t_movie_info(name, role, area) values(#{name}, #{role}, #{area})
</insert>
<select id="selectMovieInfoList" resultType="com.example.demo.entity.MovieInfo">
select id , name, role, area from t_movie_info order by id
</select>
<select id="selectMovieInfoById" resultType="com.example.demo.entity.MovieInfo">
select id , name, role, area from t_movie_info where id = #{value}
</select>
<update id="updateMovieInfo" parameterType="com.example.demo.entity.MovieInfo">
update t_movie_info set name=#{name}, role=#{role}, area=#{area} where id=#{id}
</update>
<delete id="deleteMovieInfoById">
delete from t_movie_info where id = #{value}
</delete>
</mapper>
9.10、html模板编写
在templates目录下创建movie文件夹,然后编写对应thymeleaf模板
1)列表查询:list.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>列表查询</title>
</head>
<body>
<a th:href="@{/movie/add}">新增</a>
<table border="1" style="width:360px;">
<tr>
<th>ID</th>
<th>电影名称</th>
<th>主演</th>
<th>地区</th>
<th>操作</th>
</tr>
<tr th:each="movie : ${list}">
<td th:text="${movie.id}"></td>
<td th:text="${movie.name}"></td>
<td th:text="${movie.role}"></td>
<td th:text="${movie.area}"></td>
<td>
<a th:href="@{/movie/findMovieInfoById(id=${movie.id})}">编辑</a>
<a th:href="@{/movie/delMovieInfoById(id=${movie.id})}">删除</a>
</td>
</tr>
</table>
</body>
</html>
2)新增:add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>新增电影</title>
</head>
<body>
<form th:action="@{/movie/addSubmit}" method="post">
名称:<input type="text" name="name"/><br/>
主演:<input type="text" name="role"/><br/>
地区:<input type="text" name="area"/><br/>
<input type="submit" value="新增"/><br/>
</form>
</body>
</html>
3)编辑:edit.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>编辑电影</title>
</head>
<body>
<form th:action="@{/movie/editSubmit}" method="post">
<input type="hidden" name="id" th:field="${movieInfo.id}"/>
名称:<input type="text" name="name" th:field="${movieInfo.name}"/><br/>
主演:<input type="text" name="role" th:field="${movieInfo.role}"/><br/>
地区:<input type="text" name="area" th:field="${movieInfo.area}"/><br/>
<input type="submit" value="修改"/><br/>
</form>
</body>
</html>
9.11、启动类注解
在启动类DemoApplication上添加mapper扫描注解
@MapperScan("com.example.demo.mapper") //扫描mapper接口
9.12、效果查看
启动应用,访问http://localhost:8080/movie/findMovieInfoList
地址,即可进行列表的增删改成操作
结语
至此,我们已经详细介绍了很多Springboot中的常用技术点,当然这只是其中的一部分,在项目开发过程中,我们还需要根据实际业务需求,集成其它各种的插件或框架。