Spring 诞生时是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB),Spring 为企业级Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。
(1)第一阶段:xml配置
在Spring 1.x时代,使用Spring开发满眼都是xml配置的Bean,随着项目的扩大,我们需要把xml配置文件放到不同的配置文件里,那时需要频繁的在开发的类和配置文件之间进行切换
(2)第二阶段:注解配置
在Spring 2.x 时代,随着JDK1.5带来的注解支持,Spring提供了声明Bean的注解(例如@Component、@Service),大大减少了配置量。主要使用的方式是应用的基本配置(如数据库配置)用xml,业务配置用注解。
(3)第三阶段:java配置
Spring 3.0 引入了基于 Java 的配置能力,这是一种类型安全的可重构配置方式,可以代替 XML。我们目前刚好处于这个时代,Spring4.x和Spring Boot都推荐使用Java配置。
(4)第四阶段:SpringBoot
Spring Boot 简化了基于Spring的应用开发,只需要“run”就能创建一个独立的、生产级别的Spring应用。Spring Boot为Spring平台及第三方库提供开箱即用的设置(提供默认设置),这样我们就可以简单的开始。多数Spring Boot应用只需要很少的Spring配置。
注意事项:可以使用SpringBoot创建java应用,并使用java –jar 启动它,或者采用传统的war部署方式。
Spring Boot 主要目标是:
为所有 Spring 的开发提供一个从根本上更快的入门体验
开箱即用,但通过自己设置参数,即可快速摆脱这种方式。
提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等
绝对没有代码生成,也无需 XML 配置。
// 1、定义章节实体类
public class Chapter;
// 2、定义用户实体类
public class User;
// 3、定义视频实体类
public class Video implements Serializable;
// 1、模拟静态用户数据
public class UserMapper(){};
// 2、模拟静态视频数据
public class VideoMapper(){};
public class VideoMapper {
private static Map<Integer, Video> videoMap = new HashMap<>();
static {
videoMap.put(5,new Video(5,"小滴课堂面试专题第一季,300道大厂连环问"));
}
// 定义数据接口
public List<Video> listVideo(){
List<Video> list = new ArrayList<>();
list.addAll(videoMap.values());
return list;
}
}
public interface VideoService {
List<Video> listVideo();
}
@Service
public class VideoServiceImpl implements VideoService {
@Autowired
private VideoMapper videoMapper;
@Override
public List<Video> listVideo() {
return videoMapper.listVideo();
}
}
@RestController
@RequestMapping("/api/v1/pub/video")
public class VideoController {
@Autowired
private VideoService videoService;
//@RequestMapping(value = "list",method = RequestMethod.GET)
@GetMapping("list")
public JsonData list() throws JsonProcessingException {
List<Video> list = videoService.listVideo();
return JsonData.buildSuccess(list);
}
}
public class JsonData
http://localhost:8080/api/v1/pub/video/list
@RestController
@RequestMapping("api/v1/pub/user")
public class UserController {
@Autowired
public UserService userService;
/**
* 登录接口
* @param user
* @return
*/
@PostMapping("login")
public JsonData login(@RequestBody User user){
System.out.println("user=" + user.toString());
String token = userService.login(user.getUsername(), user.getPwd());
return token !=null ? JsonData.buildSuccess(token) : JsonData.buildError("账号密码错误");
}
}
public interface UserService {
String login(String username, String pwd);
List<User> listUser();
}
// 定义用户注册登录逻辑
@Service
public class UserServiceImpl implements UserService {
private static Map<String, User> sessionMap = new HashMap<>();
@Autowired
private UserMapper userMapper;
@Override
public String login(String username, String pwd) {
User user = userMapper.login(username,pwd);
if(user==null){
return null;
}else {
String token = UUID.randomUUID().toString();
System.out.println(token);
sessionMap.put(token,user);
return token;
}
}
@Override
public List<User> listUser() {
return userMapper.listUser();
}
}
@Repository
public class UserMapper {
private static Map<String , User> userMap = new HashMap<>();
static {
userMap.put("jack",new User(1,"jack","123"));
userMap.put("xdclass-lw",new User(2,"xdclass-lw","123456"));
userMap.put("tom",new User(3,"tom","123456789"));
}
// 比对用户名是否存在 & 用户密码是否正确
public User login(String username, String pwd){
User user = userMap.get(username);
if(user == null){
return null;
}
if(user.getPwd().equals(pwd)){
return user;
}
return null;
}
public List<User> listUser(){
List<User> list = new ArrayList<>();
list.addAll(userMap.values());
return list;
}
}
(1)POST注册用户
http://localhost:8080/api/v1/pub/user/login
(2)SET查询用户
http://localhost:8080/api/v1/pub/user/list
@RestController
@RequestMapping("/api/v1/pub/video")
public class VideoController {
......
@PostMapping("save_video_chapter")
public JsonData saveVideoChapter(@RequestBody Video video){
System.out.println(video.toString());
return JsonData.buildSuccess(video);
}
}
http://localhost:8080/api/v1/pub/video/save_video_chapter
(1)性能:Jackson > FastJson > Gson > Json-lib 同个结构
(2)特点:空间换时间,时间换空间
(1)指定字段不返回:@JsonIgnore
(2)指定⽇期格式:@JsonFormat(pattern=“yyyy-MM-dd hh:mm:ss”,locale=“zh”,timezone=“GMT+8”)
(3)空字段不返回:@JsonInclude(Include.NON_NULL)
(4)指定别名:@JsonPropert
(1)业务实现:过滤⽤户敏感信息。
@RestController
@RequestMapping("api/v1/pub/user")
public class UserController {
......
/**
* 列出全部用户
* @return
*/
@GetMapping("list")
public JsonData listUser(){
return JsonData.buildSuccess(userService.listUser());
}
}
// 过滤密码字段,即不返回密码
public class User {
......
@JsonIgnore
private String pwd;
}
(2)业务需求:视频创建时间返回⾃定义格式;
// 指定返回时间格式
public class Video implements Serializable {
......
@JsonProperty("create_time")
@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
private Date createTime;
}
(3)业务需求:空字段不返回
// 指定返回时间格式
public class Video implements Serializable {
......
@JsonInclude(JsonInclude.Include.NON_NULL)
private List<Chapter> chapterList;
}
(1)常⻅的配置⽂件格式
xml、properties、json、yaml
(2)Springboot⾥⾯常⽤xx.yml
YAML(Yet Another Markup Language)
写 YAML 要⽐写 XML 快得多(⽆需关注标签或引号) 使⽤空格 Space 缩进表示分层,不同层次 之间的缩进可以使⽤不同的空格数⽬
注意:key后⾯的冒号,后⾯⼀定要跟⼀个空格,树状结构 xml、properties、json、yaml
server:
port: 8080 //设置启动端⼝号为8080
house:
family:
name: Doe
parents:
- John
- Jane
children:
- Paul
- Mark
- Simone
address:
number: 34
street: Main Street
city: Nowheretown
zipcode: 12345
(3)Springboot⾥⾯常⽤ xx.properties(推荐)
server.port=8082
#session失效时间,30m表示30分钟
server.servlet.session.timeout=30m
# Maximum number of connections that the server accepts and processes at
any given time.
server.tomcat.max-connections=10000
# Maximum size of the HTTP post content.
server.tomcat.max-http-post-size=2MB
server.tomcat.max-http-form-post-size=2MB
# Maximum amount of worker threads
server.tomcat.max-threads=200
配置⽂件加载
(1)⽅式⼀
(2)⽅式⼆:实体类配置⽂件
#微信支付的appid
wxpaay.appid=w18434343
#支付密钥
wxpay.sercret=dasjdkj1k
#微信支付商户号
wx.mechid=128138
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
@Value("${wxpaay.appid}")
private String payAppid;
@Value("${wxpaay.appid}")
private String paySecret;
@GetMapping("get_conf")
public JsonData testConfig(){
Map<String,String> map = new HashMap<>();
map.put("payappid",payAppid);
map.put("paysecret",paySecret);
return JsonData.buildSuccess(map);
}
}
http://localhost:8080/api/v1/test/get_conf
(1)单元测试依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
(2)单元测试相关注解
序号 | 注解 | 概述 |
---|---|---|
1 | @Runwith(SpringRunner.class) | 底层用junit |
2 | @SpringBootTest(Class=入口类) | 启动整个SpringBoot工程 |
3 | before | |
4 | test | |
5 | after | |
6 | TestCase.assertXXX | 断言,判断程序结果是否符合预期 |
@RunWith(SpringRunner.class)
@SpringBootTest(classes={HeimaApplication.class})
class HeimaApplicationTests {
@Before
void testOne() {
System.out.println("测试Before");
}
@Test
void testTwo() {
System.out.println("测试Two");
}
@Test
void testThree() {
System.out.println("测试Three");
}
@After
void testFour() {
System.out.println("测试After");
}
}
(1)Controller层接口单元测试
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoProjectApplication.class})//启动整个springboot工程
public class UserTest {
@Autowired
private UserController userController;
@Test
public void loginTest(){
User user = new User();
user.setUsername("jack");
user.setPwd("1234");
JsonData jsonData = userController.login(user);
System.out.println(jsonData.toString());
// 断言:判断Code状态码是否为1
TestCase.assertEquals(0,jsonData.getCode());
}
}
(2)Service层接口测试
@RunWith(SpringRunner.class) //底层用junit SpringJUnit4ClassRunner
@SpringBootTest(classes={DemoProjectApplication.class})//启动整个springboot工程
public class VideoTest {
@Autowired
private VideoService videoService;
@Before
public void testOne(){
System.out.println("这个是测试 before");
}
@Test
public void testVideoList(){
List<Video> videoList = videoService.listVideo();
TestCase.assertTrue(videoList.size()>0);
}
}
(1)引入案例
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
@GetMapping("list")
public JsonData testExt(){
int i = 1/0;
return JsonData.buildSuccess("");
}
}
(2)全局异常介绍
(3)配置方式
①添加自定义异常处理器
/**
* 标记这个是一个异常处理类
*/
@RestControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class)
JsonData handlerException(Exception e, HttpServletRequest request){
return JsonData.buildError("服务端出问题了", -2);
}
}
②Contoller异常逻辑
@RestController
@RequestMapping("api/v1/test")
@PropertySource({"classpath:pay.properties"})
public class TestController {
@GetMapping("list")
public JsonData testExt(){
int i = 1/0;
return JsonData.buildSuccess("");
}
}
③测试结果
(1)概述
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
(2)实战案例
①自定义异常处理类
/**
* 标记这个是一个异常处理类
*/
//@RestControllerAdvice
@ControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value = Exception.class)
Object handlerException(Exception e, HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("error.html");
System.out.println(e.getMessage());
modelAndView.addObject("msg",e.getMessage());
return modelAndView;
}
}
②自定义html.xml文件
DOCTYPE html>
<html xmlns:th="http://www/thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
欢迎学习 这个是自定义异常界面
<p th:text="${msg}"> p>
body>
html>
③测试结果
context-param (由外到内)
-->listener(监听器)
-->filter(过滤器)
-->Servlet
-->interceptor(拦截器)
-->Controller(控制器)
过滤器是在**web应用启动的时候初始化一次**, 在web应用停止的时候销毁,可以**对请求的URL进行过滤,** **对敏感词过滤**,挡在拦截器的外层
第一步:启动类⾥⾯增加 @ServletComponentScan,进⾏扫描
第二步:新建⼀个Filter类,继承Filter并实现相关方法,并实现对应的接⼝
第三步:@WebFilter 标记⼀个类为filter,被spring进⾏扫描
urlPatterns:拦截规则,⽀持正则
实战案例
①启动类⾥⾯增加 @ServletComponentScan
@SpringBootApplication
@ServletComponentScan
public class DemoProjectApplication {
public static void main(String[] args) {
SpringApplication.run(DemoProjectApplication.class, args);
}
}
②新建所需要拦截URL,即对应Controller类
@RestController
@RequestMapping("api/v1/pri/order")
public class VideoOrderController {
@RequestMapping("save")
public JsonData saveOrder(){
return JsonData.buildSuccess("下单成功");
}
}
③新建Filter类,拦截对应的URL
@WebFilter(urlPatterns = "/api/v1/pri/*", filterName = "loginFilter")
public class LoginFilter implements Filter {
/**
* 容器加载的时候
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init LoginFilter======");
}
// 过滤处理逻辑;
/**
* 容器销毁的时候
*/
@Override
public void destroy() {
System.out.println("destroy LoginFilter======");
}
}
Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。
(1)第一步:定义拦截器配置类实现WebMvcConfigurer类
(2)第二步:自定义拦截器:HandlerInterceptor
①preHandle:调用Controller某个方法之前
②postHandle:Controller之后调用,视图渲染之前,如果控制器Controller出现了异常,则不会执行此方法
③afterCompletion:不管有没有异常,这个afterCompletion都会被调用,用于资源清理。
(3)第三步:将自定义拦截器配置到拦截器配置类中。
①拦截器配置类:用于注册拦截器
/**
* 拦截器配置类
*/
@Configuration
class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
②自定义拦截器
class LoginIntercepter implements HandlerInterceptor {
private static final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginIntercepter preHandle =====");
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
token = request.getParameter("token");
}
if(!StringUtils.isEmpty(token)){
//判断token是否合法
User user = UserServiceImpl.sessionMap.get(token);
if(user!=null){
return true;
}else {
JsonData jsonData = JsonData.buildError("登录失败,token无效",-2);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response,jsonStr);
return false;
}
}else {
JsonData jsonData = JsonData.buildError("未登录",-3);
String jsonStr = objectMapper.writeValueAsString(jsonData);
renderJson(response,jsonStr);
return false;
}
//return HandlerInterceptor.super.preHandle(request,response,handler);
}
private void renderJson(HttpServletResponse response,String json){
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
try(PrintWriter writer = response.getWriter()){
writer.print(json);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("LoginIntercepter postHandle =====");
HandlerInterceptor.super.postHandle(request,response,handler,modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("LoginIntercepter afterCompletion =====");
HandlerInterceptor.super.afterCompletion(request,response,handler,ex);
}
}
③在拦截器配置类中添加拦截器
/**
* 拦截器配置类
*/
@Configuration
class CustomWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/pri/**");
registry.addInterceptor(new TwoIntercepter()).addPathPatterns("/api/v1/pri/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
@Bean
public LoginIntercepter getLoginInterceptor(){
return new LoginIntercepter();
}
}
过滤器 | 拦截器 | |
---|---|---|
关注点 | web请求 | Action(部分web请求) |
实现方式 | 函数回调 | 动态代理 |
级别 | 系统级 | 非系统级 |
深度 | Servlet前后 | 方法前后 |
(1)触发时机
过滤器:只能在请求的前后使用
拦截器:可以详细到每个方法。
(2)实现原理
(3)生命周期
(4)应用场景
(1)Starter介绍
starter主要简化依赖⽤的,spring-boot-starter-web->⾥⾯包含多种依赖 查看 pom⽂件 spring-boot-starter-parent -> spring-boot-dependencies ⾥⾯综合的 很多依赖包
(2)常见模板引擎
①JSP(后端渲染,消耗性能)
Java Server Pages 动态⽹⻚技术,由应⽤服务器中的JSP引擎来编译和执⾏,再将⽣成的整个页面返回给客户端,不再使用。
②Freemarker
FreeMarker Template Language(FTL) ⽂件⼀般保存为 xxx.ftl,严格依赖MVC模式,不依赖Servlet容器(不占⽤JVM内存)。
③Thymeleaf (主推)
轻量级的模板引擎(复杂逻辑业务的不推荐,解析DOM或者XML会占⽤多的内存) ,可以直接在浏览器中打开且正确显示模板⻚⾯ 直接是html结尾,直接编辑xdlcass.net/user/userinfo.html。
(1)pom文件添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
(2)application文件中配置Freemarker
# 是否开启thymeleaf缓存,本地为false,⽣产建议为true
spring.freemarker.cache=false
spring.freemarker.charset=UTF-8
spring.freemarker.allow-request-override=false
spring.freemarker.check-template-location=true
#类型
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
#⽂件后缀
spring.freemarker.suffix=.ftl
#路径
spring.freemarker.template-loader-path=classpath:/templates/
(3)建立文件夹
1)src/main/resources/templates/fm/user/
2)建⽴⼀个index.ftl
3)user⽂件夹下⾯建⽴⼀个user.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
这是freemaker整合index.html页面
<h1>payAppid ${setting.payAppid}h1>
<h1>paySecret ${setting.paySecret}h1>
body>
html>
(4)创建调用测试类
@Controller
@RequestMapping("freemaker")
class FreemakerController {
@Autowired
private WXConfig wxConfig;
@GetMapping("test")
public String index(ModelMap modelMap){
modelMap.addAttribute("setting",wxConfig);
//不用加后缀,因为配置文件里面已经指定了后缀
return "user/fm/index";
}
}
(1)官网地址
https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html
(2)pom文件添加thymeleaf依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
(3)application文件添加thymeleaf配置
#开发时关闭缓存,不然没法看到实时⻚⾯
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML5
#前缀
spring.thymeleaf.prefix=classpath:/templates/
#编码
spring.thymeleaf.encoding=UTF-8
#类型
spring.thymeleaf.content-type=text/html
#名称的后缀
spring.thymeleaf.suffix=.html
(4)建立文件夹
1)src/main/resources/templates/tl/
2)建⽴⼀个index.html
(5)创建调用测试类
@Controller
@RequestMapping("tpl")
class TemplateController {
@Autowired
private WXConfig wxConfig;
@GetMapping("thymeleaf")
public String index2(ModelMap modelMap){
modelMap.addAttribute("setting",wxConfig);
//不用加后缀,因为配置文件里面已经指定了后缀
return "tl/index";
}
}
只需要按统一的方式写记录日志的代码,而无需关心日志是通过哪个日志系统,以什么风格输出的。因为它们取决于部署项目时绑定的日志系统。例如,在项目中使用了 slf4j 记录日志,并且绑定了 log4j(即导入相应的依赖),则日志会以 log4j 的风格输出;后期需要改为以 logback 的风格输出日志,只需要将 log4j 替换成 logback 即可,不用修改项目中的代码。这对于第三方组件的引入的不同日志系统来说几乎零学习成本,况且它的优点不仅仅这一个而已,还有简洁的占位符的使用和日志级别的判断
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
// ……
}
logging:
config: logback.xml
level:
com.itcodai.course03.dao: trace
(1)logging.config 是用来指定项目启动的时候,读取哪个配置文件,这里指定的是日志配置文件是根路径下的 logback.xml 文件,关于日志的相关配置信息,都放在 logback.xml 文件中了。
(2)logging.level 是用来指定具体的 mapper 中日志的输出级别,上面的配置表示 com.itcodai.course03.dao 包下的所有 mapper 日志输出级别为 trace,会将操作数据库的 sql 打印出来,开发时设置成 trace 方便定位问题,在生产环境上,将这个日志级别再设置成 error 级别即可。
常用的日志级别按照从高到低依次为:ERROR、WARN、INFO、DEBUG。
(1)定义日志输出格式和存储路径
<configuration>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
<property name="FILE_PATH" value="D:/logs/course03/demo.%d{yyyy-MM-dd}.%i.log" />
configuration>
首先定义一个格式,**命名为 “LOG_PATTERN”,**该格式中 %date 表示日期,%thread 表示线程名,%-5level 表示级别从左显示5个字符宽度,%logger{36} 表示 logger 名字最长36个字符,%msg 表示日志消息,%n 是换行符。
然后再定义一下名为 “FILE_PATH” 文件路径,日志都会存储在该路径下。%i 表示第 i 个文件,当日志文件达到指定大小时,会将日志生成到新的文件里,这里的 i 就是文件索引,日志文件允许的大小可以设置,下面会讲解。这里需要注意的是,不管是 windows 系统还是 Linux 系统,日志存储的路径必须要是绝对路径。
(2)定义控制台输出
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
configuration>
class="ch.qos.logback.core.ConsoleAppender"
)的配置,定义为 “CONSOLE”。使用上面定义好的输出格式(LOG_PATTERN)来输出,使用 ${}
引用进来即可。(3)定义日志文件相关参数
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${FILE_PATH}fileNamePattern>
<maxHistory>15maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MBmaxFileSize>
timeBasedFileNamingAndTriggeringPolicy>
rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}pattern>
encoder>
appender>
configuration>
(4)定义日志输出级别
<configuration>
<logger name="com.itcodai.course03" level="INFO" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
root>
configuration>
(1)相关注解
序号 | 注解 | 备注 |
---|---|---|
1 | @EnableScheduling | 作用在启动类上,开启基于注解的定时任务 |
2 | @Component | 表示该类被容器扫描 |
3 | @Scheduled | 作用在方法上,表示该方法为定时方法 |
(2)入门案例
@Component
public class ScheduledServiceImpl {
@Scheduled(fixedRate = 2000) // 每 4 秒执行一次
public void hello(){
System.out.println("hello ... ");
}
}
@SpringBootApplication(scanBasePackages = "com.springboot.heima")
@EnableScheduling // 启动定时任务
public class HeimaApplication {
public static void main(String[] args) {
SpringApplication.run(HeimaApplication.class, args);
}
}
(3)多种定时表达式
序号 | 任务表达式 | 备注 |
---|---|---|
1 | cron=“*/1 * * * * *” | 定时任务表达式 |
2 | fixedRate | 定时多久执⾏⼀次(上⼀次开始执⾏时间点后xx秒再次执⾏) |
3 | fixedDelay | 上⼀次执⾏结束时间点后xx秒再次执⾏ |
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
(1)相关注解
序号 | 注解 | 备注 |
---|---|---|
1 | @EnableAsync | 作用在启动类上,开启基于注解的异步任务 |
2 | @Component | 表示该类被容器扫描 |
3 | @Async | 作用在方法上,说明该方法是异步方法;作用在类上,表示该类的所有方法都是异步方法 |
(2)入门案例
@Component
public class AsyncServiceImpl {
@Async
public Future<String> execTaskA() throws InterruptedException {
System.out.println("TaskA开始");
long star = new Date().getTime();
Thread.sleep(5000);
long end = new Date().getTime();
System.out.println("TaskA结束,耗时毫秒数:" + (end - star));
return new AsyncResult<>("TaskA结束");
}
@Async
public Future<String> execTaskB() throws InterruptedException {
System.out.println("TaskB开始");
long star = new Date().getTime();
Thread.sleep(3000);
long end = new Date().getTime();
System.out.println("TaskB结束,耗时毫秒数:" + (end - star));
return new AsyncResult<>("TaskB结束");
}
@Async
public Future<String> execTaskC() throws InterruptedException {
System.out.println("TaskC开始");
long star = new Date().getTime();
Thread.sleep(4000);
long end = new Date().getTime();
System.out.println("TaskC结束,耗时毫秒数:" + (end - star));
return new AsyncResult<>("TaskC结束");
}
}
@SpringBootApplication(scanBasePackages = "com.springboot.heima")
@EnableScheduling // 启动定时任务
@EnableAsync // 启动异步任务
public class HeimaApplication {
public static void main(String[] args) {
SpringApplication.run(HeimaApplication.class, args);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes={HeimaApplication.class})
class AsyncTaskApplicationTests {
// 重点:异步任务封装到类⾥⾯,不能直接写到Controller
@Autowired
AsyncServiceImpl asyncTask;
@Test
public void testAsyncTask() throws InterruptedException {
long star = new Date().getTime();
System.out.println("任务开始,当前时间" +star );
Future<String> taskA = asyncTask.execTaskA();
Future<String> taskB = asyncTask.execTaskB();
Future<String> taskC = asyncTask.execTaskC();
//间隔一秒轮询 直到 A B C 全部完成
while (true) {
if (taskA.isDone() && taskB.isDone() && taskC.isDone()) {
break;
}
Thread.sleep(1000);
}
long end = new Date().getTime();
System.out.println("任务结束,当前时间" + end);
System.out.println("总耗时:"+(end-star));
}
}
(3)注意事项
(1)默认线程池
在springboot配置文件中加入上面的配置,即可实现ThreadPoolTaskExecutor 线程池,如果没有配置线程池的话,springboot会自动配置一个ThreadPoolTaskExecutor 线程池到bean当中。
# 核心线程数
spring.task.execution.pool.core-size=8
# 最大线程数
spring.task.execution.pool.max-size=16
# 空闲线程存活时间
spring.task.execution.pool.keep-alive=60s
# 是否允许核心线程超时
spring.task.execution.pool.allow-core-thread-timeout=true
# 线程队列数量
spring.task.execution.pool.queue-capacity=100
# 线程关闭等待
spring.task.execution.shutdown.await-termination=false
spring.task.execution.shutdown.await-termination-period=
# 线程名称前缀
spring.task.execution.thread-name-prefix=task-
(2)自定义线程池
// 创建一个线程池配置类TaskConfiguration,并配置一个任务线程池对象taskExecutor
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
return executor;
}
}
(3)实战案例 - 使用自定义线程池改造上个案例
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new CallerRunsPolicy());
return executor;
}
}
@Component
public class AsyncServiceImpl {
@Async("taskExecutor")
public Future<String> execTaskA() throws InterruptedException {
System.out.println("TaskA开始");
long star = new Date().getTime();
Thread.sleep(5000);
long end = new Date().getTime();
System.out.println("TaskA结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
return new AsyncResult<>("TaskA结束");
}
@Async("taskExecutor")
public Future<String> execTaskB() throws InterruptedException {
System.out.println("TaskB开始");
long star = new Date().getTime();
Thread.sleep(3000);
long end = new Date().getTime();
System.out.println("TaskB结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
return new AsyncResult<>("TaskB结束");
}
@Async("taskExecutor")
public Future<String> execTaskC() throws InterruptedException {
System.out.println("TaskC开始");
long star = new Date().getTime();
Thread.sleep(4000);
long end = new Date().getTime();
System.out.println("TaskC结束,耗时毫秒数:" + (end - star) + Thread.currentThread().getName());
return new AsyncResult<>("TaskC结束");
}
}