目录
04.Spring Boot的自动配置原理以及启动分析(难点)
点进去spring-boot-starter-parent以后这依赖管理里面写和很从版本
简介:
Spring SpringMVC和SpringBoot 区别
1、Spring
Spring是一个开源容器框架,可以接管web层,业务层,dao层,持久层的组件,并且可以配置各种bean,和维护bean与bean之间的关系。其核心就是控制反转(IOC),和面向切面(AOP),简单的说就是一个分层的轻量级开源框架。
2、SpringMVC
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。SpringMVC是一种web层mvc框架,用于替代servlet(处理|响应请求,获取表单参数,表单校验等。SpringMVC是一个MVC的开源框架,SpringMVC=struts2+spring,springMVC就相当于是Struts2加上Spring的整合。
3、SpringBoot
Springboot是一个微服务框架,延续了spring框架的核心思想IOC和AOP,简化了应用的开发和部署。Spring Boot是为了简化Spring应用的创建、运行、调试、部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置。提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题—>习惯大于约定
区别与总结
1.简单理解为:Spring包含了SpringMVC,而SpringBoot又包含了Spring或者说是在Spring的基础上做得一个扩展。spring mvc < spring < springboot
2、Spring Boot 对比Spring的一些优点包括:
提供嵌入式容器支持
使用命令java -jar独立运行jar
在外部容器中部署时,可以选择排除依赖关系以避免潜在的jar冲突
部署时灵活指定配置文件的选项
用于集成测试的随机端口生成
3、结论
Spring Boot只是Spring本身的扩展,使开发,测试和部署更加方便。
01.Spring Boot 入门程序
创建第一个springboot项目
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
这个spring-boot-starter-web 是web的启动器,封装了springmv所需要的jar包
创建一个controller
启动测试
启动项目访问http://localhost:8080/index/hello
jar包启动测试,先打包
测试访问
项目结构
彩蛋
spring Boot启动的时候会有一个默认的启动图案。
在src/main/resources路径下新建一个banner.txt文件,并输入想要的内容即可
02.常用注解【spring的java配置】
类上面的注解:
@Controller --控制器
@RestController --返回json的控制器 =Controller +ResponseBody
@Service --标记服务接口
@Respority --标记Dao接口
@Component --标记组件
@RequestMapping --请求映射 (也可在方法上)
方法上的注解:
|--GetMapping
|--PostMapping
|--DeleteMapping
|--PutMapping
|--PatchMapping
@ResponseBody --返回JSON对象
参数上的注解:
@RequestBody --入参是JSON对象
@PathVariable(value=””) --将路径上面的参数映射到入参里面
http://127.0.0.1:8080/car/user/1
@RequestParam 如果Controller里面使用map接收参数
|--如果controller不知道页面要传多少参数,那么可以使用Map<String,String>去接收。 必须加@RequestParam的注解
属性上面的注解
@Autowried --自动注入(首选按照类型) byName byType
@Resource --自动注入(首选按照名字)
传参方式
http://127.0.0.1:8080/car/user?id=1
http://127.0.0.1:8080/car/user/1 加@pathvarible注解
@Configuration
作用于类上,相当于一个xml配置文件;|--application-dao.xml
@Bean 作用于方法上,相当于xml配置中的<bean>
@Qualifier注解
qualifier的意思是合格者,通过这个标示,表明了哪个实现类才是我们所需要的,我们修改调用代码,添加@Qualifier注解,需要注意的是@Qualifier的参数名称必须为我们之前定义@Bean注解的名称之一
@Primary 主候选的
当IOC容器里面有多个同类型的对象时,就会发生冲突,标注了该注解的就作为主候选对象
03. Spring Boot热部署
什么是热部署
spring为开发者提供了一个名为spring-boot-devtools的模块来使springboot应用支持热部署,提高开发的效率,修改代码后无需重启应用
<!--添加热部署的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!--运行时生效-->
<scope>runtime</scope>
<!--依赖是否会传递,为true则不会传递-->
<optional>true</optional>
</dependency>
配置idea的启动面板
如果不配置面板,那么可直接使用ctrl+F9去刷新
04.Spring Boot的自动配置原理以及启动分析(难点)
4.1.pom的依赖分析
<!-- 继承springboot的父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
点进去spring-boot-starter-parent以后这依赖管理里面写和很从版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.4</version>
</parent>
这里才是真正管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;
4.2.启动器 spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.4</version>
</dependency>
springboot-boot-starter-xxx:就是spring-boot的场景启动器
<!-- 引入了spring-boot-starter-web以后,springboot就帮我们导入了web模块运行的所有组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SpringBoot将所有的功能场景都抽取出来,做成一个个的starter (启动器),只需要在项目中引入这些starter即可,所有相关的依赖都会导入进来 , 我们要用什么功能就导入什么样的场景启动器即可 ,也可以自己自定义 starter;
4.3原理分析
- 为什么启动类所在的所及其子包能被默认扫描
- 为什么引入spring-boot-starter-web之后能直接集成springmvc
注解功能划分
如何自动扫描(默认扫描启动类所在的包以下面的及子包)
@Import(AutoConfigurationPackages.Registrar.class)
在AutoConfigurationPackages中registerBeanDefinitions
如图:
疑问:这是springmvc中的写法,我们要这么用,必须配置相关xml
为什么引入spring-boot-starter-web之后能直接集成springmvc
回顾SSM的配置:
- 包扫描
- 注解
- 配置适配器
- Contoller的实方式
- 实现Controller接口
- 实现HttpRequestHandler
- 使用注解
- Controller
- RestController
- 总结,就是为了适配Controller的不同实现方式
- 配置映射器
- 处理用户请求地址和Controller里方法的映射关系的
- 配置视图解释器
- org.springframework.web.servlet.view.InternalResourceViewResolver
- 配置拦截器
- 拦截请求 Inteceptor
- 配置文件上传管理器
- <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
配置前端控制器
springmvc.xml
web.xml
是因为上面配置是springboot帮我们做了
如何加载自动配置类
找到@Import(AutoConfigurationImportSelector.class)
在这个类里面的getAutoConfigurationEntry()方法
springboot在启动的时候默认加载了所有的配置
进入getCandidateConfigurations()方法查看究竟
这个方法是spring的加载工厂去筛选所有引入(link)EnableAutoConfiguration的配置类
EnableAutoConfiguration是Key 返回的List<String>是vlaue
接着进入loadFactoryNames()方法
最后回到getAutoConfigurationEntry()方法里面往下执行
剔除不需要的(pom里面没有引入starter的自动配置类)
至此自动加载配置类的初探就结束了
(从加载到的配置类中选讲)如何加载前端控制器,在ssm里面,我们需要手动去创建DispatcherServlet对象,然后注入到Servlet容器中,在springboot里面,已经帮我们自动配置了
查看DispatcherServletAutoConfiguration这个自动配置类
创建DispatcherServlet对象
设置servlet的名称,loadonstart 启动时加载 路径
Springboot的run方法到底执行了什么
Springboot默认提供了哪些候选的starter
https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter
05.Spring Boot的两种配置文件语法【重中之重】
引入依赖
<!--添加配置文件前缀的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
创建Hero 类
properties方式:
hero.id=2
hero.name=LJH
hero.age=18
hero.birth=2012/12/31
hero.hobby=LOL,DNF,CF
hero.list=C#,Python,PS
hero.set=football,basketball,swim
hero.map.k1=v1
hero.map.k2=v2
yml方式:
hero:
id: 1
age: 18
name: 超人
birth: 2012/12/31
hobby:
- LOL
- DNF
- CF
list:
- JAVA
- JS
- C++
set:
- 足球
- 篮球
- 排球
map:
k1: v1
k2: v2
测试
配置文件取值
hero.name=${hero.age}
两种配置文件的用法说明
- 如果properties里面配置了就不会去yml里面去取值,如果没有配置就会去yml里面去取
- 两种配置文件是互补的存在
注入对象
06.@Value读取配置文件以及验证
总结说明
1,@Value只能注入普通的属性[也就是基本数据类型和String] 其它的复杂类型是不能取到值的[如果yaml配置是array:LOL,DNF]这样的配置是可以取 或者在perpeties里面配置
2,如果属性是使用驼峰命名法则不能使用属性名注入,要使用@Value("${hero.class-name}")来取值
不能使用@Value("${hero.className}")来取值
@Value和@ConfigurationProperties取值比较
注解验证
引入依赖
<!--引入注解验证的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
常用的验证注解
@NotNull --不能为null,但可以空着不写 (name:)
@NotEmpty --不能为空,也不能为null,但可以是空格 “ ”
@NotBlank --不能为空格,也不能为null,也不能为空格
@Min 最大值
@Max 最小值
@Size(min = 1, max = 6) 长度限制
@Range(min = 1,max = 2) 范围限制
@Pattern(regexp"[0,1]{1}") 正则限制
全局异常
package com.bjsxt.exception;
import com.bjsxt.vo.ResultObj;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {BindException.class})
public ResultObj bindException(BindException e){
BindingResult result = e.getBindingResult();
return getResultObj(result);
}
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public ResultObj methodArgumentNotValidException(MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
return getResultObj(result);
}
private ResultObj getResultObj(BindingResult result) {
List<Map<String,Object>> resList=new ArrayList<>();
//取出errors
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError allError : allErrors) {
//取出对象名
String objectName = allError.getObjectName();
String defaultMessage = allError.getDefaultMessage();
FieldError fieldError= (FieldError) allError;
String field = fieldError.getField();
Map<String,Object> map=new HashMap<>();
map.put("objectName",objectName);
map.put("field",field);
map.put("defaultMessage",defaultMessage);
resList.add(map);
}
return new ResultObj(400,"数据验证不通过",resList);
}
}
07.Profile配置文件详解
7.1为什么要使用profiles
在开发中,一般有两种环境
1,生产环境 [项目上线,客户在使用中,就是生产环境]
2,开发环境 [就是开发环境,自己开发测试的环境]
有时候开发环境和生产环境的配置方法是不一样的,那么如何快速的切换呢,这里就要使用profiles文件
7.2使用方法
创建application.yml
创建application-dev.yml
创建application-pro.yml
运行测试
总结
在application.yml的主配置文件中,激活哪个配置文件,就会使用该配置文件进行运行
08.配置文件加载优先级和外部配置文件
8.1项目内部配置文件
spring boot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件
其中同一目录下的properties文件的优先级大于yml文件
配置文件可以放的位置和优先级
classpath:/ --优先级4 默认放的位置
classpath:/config/ --优先级3
file:./ --优先级2 跟jar包同级别的目录
file:./config/ --优先级1
测试前两个
测试前三个
先打包
启动jar包运行
在jar包同级路面建config包,里面application.yml端口为8004
启动测试:
Win10 下关闭已运行jar包的方法,本文直接查找端口杀之
查找端口命令 netstat -aon|findstr “8081”
使用命令杀掉:taskkill /pid 8080 -f 杀掉端口
查看ConfigFileApplicationListener
8.2外部的配置文件
在D盘放一个application.yml文件 端口指定为8009
java -jar 01springboot-hello-0.0.1-SNAPSHOT.jar --spring.config.location=D:/application.yml
09.自动配置原理以及 @Conditional派生注解
9.1自动配置
我们就了解到了Spring Boot在启动的时候:
- 去扫描加载了所有引入依赖的jar包下面的MATE-INF/spring.factories文件,
- 然后通过EnableAutoConfiguration为key,下面所有自动配置类的全限定类名作为value,(List<String>)
- 经过筛选排除掉不符合要求的(pom中没有添加依赖的配置类)
- 最后把(List<String>:符合条件的自配配置类的全限定类名)添加到IOC容器去管理,从而实现了自动配置原理
@Conditional派生注解
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置类里面的所有内容才生效;(条件之间是并且的关系)
10. 整合logback
10.1概述
在搭建新的系统时候必不可少的是需要日志的,日志的作用就不用多说了吧,可以用来调试程序,记录程序运行的状态,最重要的是可以用来排查线上的问题。
那我们该如何在项目中使用日志呢?
SpringBoot内部集成了LogBack日志依赖,SpringBoot默认使用LogBack记录日志信息,默认根据base.xml配置内容来输出到控制台和文件之中,这是默认的配置不能达到企业级项目的要求
创建logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="log.path" value="D:/mylogs/" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--<logger name="org.springframework.web" level="info"/>-->
<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-->
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!--开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.nmys.view" level="debug"/>
</springProfile>
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!--生产环境:输出到文件-->
<!--<springProfile name="pro">-->
<!--<root level="info">-->
<!--<appender-ref ref="CONSOLE" />-->
<!--<appender-ref ref="DEBUG_FILE" />-->
<!--<appender-ref ref="INFO_FILE" />-->
<!--<appender-ref ref="ERROR_FILE" />-->
<!--<appender-ref ref="WARN_FILE" />-->
<!--</root>-->
<!--</springProfile>-->
</configuration>
创建启动类测试
查看本地日志
11.AOP开发
11.1概述
aop是spring的两大功能模块之一,功能非常强大,为解耦提供了非常优秀的解决方案。SpringBoot集成aop是非常方便的,下面使用aop来拦截业务组件的方法
Aop的作用:在不修改源代码的情况下,对类里面的方法进行增强
(前置,后置,环绕,异常)
11.2使用方法
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建Man测试
@Component
public class TestAop {
public void eat(String food){
// int a = 10/0;
System.out.println("吃"+food);
}
}
创建切面类
package com.wxz.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* TODO
*
* @author wxz
* @date 2022/3/2014:16
*/
@Component
@Aspect
public class TestAspect {
public static final String pc ="execution(* com.wxz.domain.*.*(..))";
// @Before(pc)
public void before(){
System.out.println("饭前来点水果");
}
// @After(pc)
public void after(){
System.out.println("饭后来一把");
}
@Around(pc)
public Object around(ProceedingJoinPoint point){
before();
Object[] args = point.getArgs();
String game = args[0].toString();
System.out.println(game);
//执行目标方法,获得返回值
Object proceed = null;
try {
proceed = point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
after();
//返回出去
return proceed;
}
@AfterThrowing(value = pc, throwing = "tw")
public void afterThrowing(Throwable tw) {
System.out.println("出现异常" + tw.getMessage());
}
}
12.WEB静态资源访问规则
- classpath:META-INF/resources 优先级最高
- classpath:resources 其次
- classpath:static
- classpath:public
查看WebMvcAutoConfiguration里面的静态类WebMvcAutoConfigurationAdapter
关于资源管的方法addResourceHandlers
如果这四个目录下面都有相同的文件,那么访问的优先级为:
META-INF/resources>resources>static>public
13.Thymeleaf模板的使用
Thymeleaf概述
Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸引人的特点:
1、Thymeleaf 在有网络和无网络的环境下皆可
运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
2、Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
3、Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Spring Boot项目Thymeleaf模板页面存放位置
查看ThymeleafAutoConfiguration自动配置类,找到模板存放的位置
禁用缓存
templates目录不能直接访问,需要controller进行页面跳转
测试访问:
Thymeleaf的相关语法:
1,简单表达式
1、变量的表达式:${...} 取作用域里面的值(request,session,applicationContext)
2、选择变量表达式:*{...}
3、信息表达式:#{...} 取IOC容器里面的值
4、链接URL表达式:@{...} <a href="user/query.action"> <a th:href="@{user/query.action}"
2,字面值 th:text
1、文本文字:'one text', 'Another one!',…
2、文字数量:0, 34, 3.0, 12.3,…
3、布尔型常量:true, false
4、空的文字:null
5、文字标记:one, sometext, main,…
3,文本处理
1、字符串并置:+
2、文字替换:|The name is ${name}|
4,表达式基本对象
1、#ctx:上下文对象
2、#vars:上下文变量
3、#locale:上下文语言环境
4、#httpServletRequest:(只有在Web上下文)HttpServletRequest对象
5、#httpSession:(只有在Web上下文)HttpSession对象。
用法:<span th:text="${#locale.country}">US</span>.
5,实用工具对象
#dates: java.util的实用方法。对象:日期格式、组件提取等.
#calendars:类似于#日期,但对于java.util。日历对象
#numbers:格式化数字对象的实用方法。
#strings:字符串对象的实用方法:包含startsWith,将/附加等。
Thymeleaf读取Model里面的对象
创建类Hero:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hero {
private Integer id;
private String name;
private String sex;
private Integer age;
private String country;
private String phone;
private Date birth;
private Double salary;
}
@RequestMapping("helloHero")
public String helloHero(Model model){
Hero hero=new Hero(1,"后羿","男",18,"中国","110",new Date());
model.addAttribute("hero",hero);
return "showHero";
}
修改页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
<link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
<legend>英雄面板</legend>
</fieldset>
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md3">
<div class="layui-card">
<span >英雄id</span> <span th:text="${hero.id}"></span> <br>
<span>英雄名字</span> <span th:text="${hero.name}"></span> <br>
<span>英雄性别</span> <span th:text="${hero.sex}"></span> <br>
<span>英雄年龄</span> <span th:text="${hero.age}"></span> <br>
<span>英雄年龄</span> <span th:text="${#numbers.formatDecimal(hero.age,0,2)}"></span> <br>
<span>英雄国家</span> <span th:text="${hero.country}"></span> <br>
<span>英雄电话</span> <span th:text="${hero.phone}"></span> <br>
<span>英雄生日</span> <span th:text="${hero.birth}"></span> <br>
<span>英雄生日</span> <span th:text="${#dates.format(hero.birth,'yyyy-MM-dd')}"></span> <br>
</div>
</div>
</div>
</div>
</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>
测试访问
Thymeleaf读取Model里面的集合
@RequestMapping("helloHeroList")
public String helloHeroList(Model model){
List<Hero> list=new ArrayList<>();
for (int i = 1; i <=5 ; i++) {
list.add(new Hero(i,"后羿"+i,"男",18,"中国","110",new Date()))
}
model.addAttribute("heros",list);
return "showHeroList";
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
<link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 20px;">
<legend>英雄面板</legend>
</fieldset>
<div style="padding: 20px; background-color: #F2F2F2;">
<div class="layui-row layui-col-space15">
<div class="layui-col-md3" th:each="hero:${heros}">
<div class="layui-card">
<span>英雄id</span> <span th:text="${hero.id}"></span> <br>
<span>英雄名字</span> <span th:text="${hero.name}"></span> <br>
<span>英雄性别</span> <span th:text="${hero.sex}"></span> <br>
<span>英雄年龄</span> <span th:text="${hero.age}"></span> <br>
<span>英雄年龄</span> <span th:text="${#numbers.formatDecimal(hero.age,0,2)}"></span> <br>
<span>英雄国家</span> <span th:text="${hero.country}"></span> <br>
<span>英雄电话</span> <span th:text="${hero.phone}"></span> <br>
<span>英雄生日</span> <span th:text="${hero.birth}"></span> <br>
<span>英雄生日</span> <span th:text="${#dates.format(hero.birth,'yyyy-MM-dd')}"></span> <br>
</div>
</div>
</div>
</div>
</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>
测试访问
ThymeleafObjects的使用
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--<link rel="stylesheet" href="/resources/layui/css/layui.css">-->
<link rel="stylesheet" th:href="@{/resources/layui/css/layui.css}">
</head>
<body>
model:<div th:text="${name}"></div><hr>
request:<div th:text="${#httpServletRequest.getAttribute('name')}"></div><hr>
session:<div th:text="${#httpSession.getAttribute('name')}"></div><hr>
session:<div th:text="${session.name}"></div><hr>
servletContext:<div th:text="${#servletContext.getAttribute('name')}"></div><hr>
<div th:text="${#locale.getCountry()}"></div>
<div th:text="${#locale.getLanguage()}"></div>
</body>
<script th:src="@{/resources/layui/layui.js}" ></script>
</html>
14. 内嵌tomcat加载原理分析
我们在使用springboot项目的时候并没有使用外部的tomcat,那么springboot是如何帮我们管理内置的服务器的呢?
查看启动原理
查看ServletWebServerFactoryAutoConfiguration
发现包含了3个内嵌的服务器,默认是使用了内嵌的tomcat
点击EmbeddedTomcat 进入
继续往下走查看TomcatServletWebServerFactory
点击getTomcatWebServer
点击TomcatWebServer
15.启动内嵌jetty服务器【了解】
在springboot里面默认使用内嵌的tomcat 可以改成jetty
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 在web的starter里面排除tomcat的启动器-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-start-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入jetty的启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
@Configuration
public class JettyConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory jettyServletWebServerFactory = new JettyServletWebServerFactory();
return jettyServletWebServerFactory;
}
}
启动查看
16.注册Web三大组件【重点】
web的三大件有哪些
Servlet ---web.xml @WebServlet
Filter --web.xml @WebFilter
Listener --web.xml @WebListener
16.1首先查看注册组件的结构
16.2注册自己的Servlet
我们可以模仿DispatcherServlet的注册方式
创建UserServlet
package com.wxz.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.println("hello-userServlet");
writer.flush();
writer.close();
System.out.println("hello-UserServlet");
}
}
在配置类注册自己的servlet
@Configuration
public class WebConfig {
@Bean
public UserServlet userServlet() {
return new UserServlet();
}
@Bean
public ServletRegistrationBean<UserServlet> userServletServletRegistrationBean(UserServlet userServlet) {
ServletRegistrationBean<UserServlet> servletRegistrationBean = new ServletRegistrationBean<>();
servletRegistrationBean.setServlet(userServlet);
servletRegistrationBean.setUrlMappings(Arrays.asList("/user1.action", "/user2.action"));
return servletRegistrationBean;
}
}
16.3注册自己的Filter
创建MyFilter
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("前置过滤");
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("后置过滤");
}
@Override
public void destroy() {
}
}
在配置类注册自己的Filter
@Bean
public MyFilter myFilter() {
return new MyFilter();
}
/**
* 注册自己的filter
*
* @param myFilter
* @return
*/
@Bean
public FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean(MyFilter myFilter) {
FilterRegistrationBean<MyFilter> myFilterFilterRegistrationBean = new FilterRegistrationBean<>();
myFilterFilterRegistrationBean.setFilter(myFilter);
//设置所有的请求都走过滤器
myFilterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
return myFilterFilterRegistrationBean;
}
16.4注册自己的Listener
创建MyListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContextEvent被创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContextEvent被销毁");
}
}
在配置类注册自己的Listener
@Bean
public MyListener myListener() {
return new MyListener();
}
@Bean
public ServletListenerRegistrationBean listenerRegistrationBean(MyListener myListener) {
ServletListenerRegistrationBean<MyListener> listenerRegistrationBean = new ServletListenerRegistrationBean<>();
listenerRegistrationBean.setListener(myListener);
return listenerRegistrationBean;
}
17.数据源配置和自动管理【重中之中】
选择依赖
17.1使用DriverManagerDataSource
修改配置文件
spring:
datasource: #数据源的配置
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
type: org.springframework.jdbc.datasource.DriverManagerDataSource #spring自带的数据源
测试连接
17,2使用Druid数据源【自己配置】
数据源
数据源的监控页面
添加durid的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
添加MyDruidProperties配置文件类
@Data
@ConfigurationProperties(prefix = "my.druid")
public class MyDruidProperties {
private String url;
private String username;
private String password;
private String driverClassName;
//初始化数量
private Integer initialSize;
//最大活跃数
private Integer maxActive;
//最小连接数
private Integer minIdle;
//检查连接的
private String validationQuery;
private StatView statView;
/**
* 监控配置
*/
@Data
static class StatView {
//监控登录名
private String loginUsername;
//监控登录密码
private String loginPassword;
//白名单
private String allow;
//黑名单
private String deny;
//映射路径
private String[] urlMapping;
}
}
添加MyDruidAutoConfiguration自动配置类
@Data
@Configuration
@Log4j2
@EnableConfigurationProperties(MyDruidProperties.class)
@ConditionalOnClass(value = {DruidDataSource.class}) //必须有DruidDataSource的这个类
public class MyDruidAutoConfiguration {
// @Autowired 如果不想使用Autowired就必须提交一个构造方法,如下
private MyDruidProperties myDruidProperties;
public MyDruidAutoConfiguration(MyDruidProperties myDruidProperties) {
this.myDruidProperties = myDruidProperties;
}
/**
* 创建数据源对象
*
*/
@Bean(initMethod = "init",destroyMethod = "close")
public DruidDataSource druidDataSource(){
if (myDruidProperties.getUrl() == null) {
log.error("URL can not be null");
throw new RuntimeException("URL can not be null");
}
DruidDataSource dataSource=new DruidDataSource();
dataSource.setDriverClassName(myDruidProperties.getDriverClassName());
dataSource.setUrl(myDruidProperties.getUrl());
dataSource.setUsername(myDruidProperties.getUsername());
dataSource.setPassword(myDruidProperties.getPassword());
dataSource.setMaxActive(myDruidProperties.getMaxActive());
dataSource.setMinIdle(myDruidProperties.getMinIdle());
dataSource.setValidationQuery(myDruidProperties.getValidationQuery());
return dataSource;
}
/**
* 注册StatViewServlet
*/
@Bean
@ConditionalOnClass(StatViewServlet.class)
public ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean(){
StatViewServlet statViewServlet=new StatViewServlet();
ServletRegistrationBean<StatViewServlet> registrationBean=new ServletRegistrationBean<>();
registrationBean.setServlet(statViewServlet);
registrationBean.addInitParameter("loginUsername",myDruidProperties.getStatView().getLoginUsername());
registrationBean.addInitParameter("loginPassword",myDruidProperties.getStatView().getLoginPassword());
registrationBean.addInitParameter("allow",myDruidProperties.getStatView().getAllow());
registrationBean.addInitParameter("deny",myDruidProperties.getStatView().getDeny());
registrationBean.addUrlMappings(myDruidProperties.getStatView().getUrlMapping());
return registrationBean;
}
}
修改yml配置文件
my:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
initial-size: 5
max-active: 10
min-idle: 2
validation-query: select 'x'
stat-view:
login-username: admin
login-password: admin
allow:
deny:
url-mapping:
- /druid/*
- /druid2/*
测试访问
17.3使用Druid数据源【官方starter】
添加durid starter的依赖
<!-- 引入druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
修改配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
# type: com.alibaba.druid.pool.DruidDataSource
druid:
max-active: 10
min-idle: 2
validation-query: select 'x'
stat-view-servlet:
login-username: admin
enabled: true #启用监控页
login-password: admin
allow:
deny:
url-pattern: /druid/*
18.集成JdbcTemplate【熟悉】
我们之前在学spring的时候接触过JdbcTemplate,这个东西是官方的,那么在springboot里面怎么用,需要配置什么呢,答案:只要有数据源就行了
测试
因为数据源已经搞定了,所以直接测试即可
原理
找到JdbcTemplateAutoConfiguration
找到JdbcTemplateConfiguration
从上面的代码可以看出,只要有数据源,就直接创建了,所以我们可以直接使用
19.集成mybatis【重点】
19.1注解方式的整合
创建项目
<!-- 引入druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
创建数据库
创建User对象
package com.wxz.domain;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
/**
* 用户编号
*/
private Integer id;
/**
* 用户姓名
*/
private String name;
/**
* 用户地址
*/
private String address;
/**
* 出生时间
*/
private Date birth;
/**
* 是否删除1删除0未删除
*/
private Integer flag;
}
创建UserMapper
package com.wxz.mapper;
import com.wxz.domain.User;
import org.apache.ibatis.annotations.*;
public interface UserMapper {
@Delete("delete from sys_user where id= #{id}")
int deleteByPrimaryKey(Integer id);
@Insert("insert into sys_user(id,name,address,birth,flag) values(#{user.id},#{user.name},#{user.address} ,#{user.birth},#{user.flag})")
int insert(@Param("user") User record);
@Select("select * from sys_user where id=#{id} ")
User selectByPrimaryKey(Integer id);
@Update("update sys_user set name=#{user.name},address=#{user.address} ,birth=#{user.birth} ,flag=#{user.flag} where id=#{user.id} ")
int updateByPrimaryKey(@Param("user") User user);
}
修改yml配置文件
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
# type: com.alibaba.druid.pool.DruidDataSource
druid:
max-active: 10
min-idle: 2
validation-query: select 'x'
stat-view-servlet:
login-username: admin
enabled: true #启用监控页
login-password: admin
allow:
deny:
url-pattern: /druid/*
#配置mybatis
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #输出sql
启动了上扫描mapper所在的包
测试查询
19.2配置文件方式整合(推荐)
生成UserMapper接口
public interface UserMapper {
int deleteByPrimaryKey(Integer id);
int insert(User record);
User selectByPrimaryKey(Integer id);
int updateByPrimaryKey(User record);
List<User> selectAll();
}
在resources/mapper生成UserMapper.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.wxz.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.wxz.domain.User">
<!--@mbg.generated-->
<!--@Table sys_user-->
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="address" jdbcType="VARCHAR" property="address" />
<result column="birth" jdbcType="TIMESTAMP" property="birth" />
<result column="flag" jdbcType="INTEGER" property="flag" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, `name`, address, birth, flag
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
<!--@mbg.generated-->
select
<include refid="Base_Column_List" />
from sys_user
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
<!--@mbg.generated-->
delete from sys_user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.bjsxt.domain.User" useGeneratedKeys="true">
<!--@mbg.generated-->
insert into sys_user (`name`, address, birth,
flag)
values (#{name,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{birth,jdbcType=TIMESTAMP},
#{flag,jdbcType=INTEGER})
</insert>
<update id="updateByPrimaryKey" parameterType="com.wxz.domain.User">
<!--@mbg.generated-->
update sys_user
set `name` = #{name,jdbcType=VARCHAR},
address = #{address,jdbcType=VARCHAR},
birth = #{birth,jdbcType=TIMESTAMP},
flag = #{flag,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectAll" resultMap="BaseResultMap">
<!--@mbg.generated-->
select
<include refid="Base_Column_List" />
from sys_user
</select>
</mapper>
修改yml配置文件
#配置mybatis
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #输出sql
mapper-locations:
- classpath:mapper/*Mapper.xml
启动类添加mapper扫描
19.3配置PageHelper插件分页
依赖pageHelper的starter
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
测试:
19.4事务管理
和spring里面一样的,但是在boot里面只要在XXXXServiceImpl上Transactionl
20.集成mybatisplus【重点】
创建项目引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
配置yml
server:
port: 8080
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
initial-size: 5
max-active: 10
min-idle: 2
validation-query: select 'x'
stat-view-servlet:
login-username: admin
login-password: 123456
enabled: true #启用监控页面
url-pattern: /druid/*
#配置mybatis-plus
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations:
- classpath:mapper/*.xml
生成User UserMapper UserMapper.xml
修改启动类
测试
配置分页
package com.wxz.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
@SpringBootTest
class ApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void queryForPage() {
Integer pageNum=1;
Integer pageSize=10;
Page<User> page=new Page<>(pageNum,pageSize);
this.userMapper.selectPage(page,null);
List<User> records = page.getRecords();
long total = page.getTotal();
System.out.println("总条数:"+total);
for (User record : records) {
System.out.println(record);
}
}
}
21.集成swagger3.0【熟悉】
使用步骤
创建项目加入依赖
<!--swagger starter-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
创建SwaggerProperties信息配置类
@Data
@ConfigurationProperties(prefix = "swagger2")
public class SwaggerProperties {
//作者姓名
private String name;
//作者主页链接
private String url;
//作者邮箱
private String email;
//版本号
private String version;
//分组名称
private String groupName;
//文档标题
private String title;
//文档描述
private String description;
//组织地址
private String termsOfServiceUrl;
}
创建SwaggerAutoConfiguration自动配置类
package com.wxz.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.ArrayList;
@Configuration
@EnableConfigurationProperties(value = {SwaggerProperties.class})
@EnableOpenApi //启用swagger
public class SwaggerAutoConfiguration {
private SwaggerProperties swaggerProperties;
public SwaggerAutoConfiguration(SwaggerProperties swaggerProperties) {
this.swaggerProperties = swaggerProperties;
}
/**
* 创建文档
*
* @return
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.groupName(swaggerProperties.getGroupName())
.select()
//要扫描的包
.apis(RequestHandlerSelectors.basePackage("com.wxz"))
// .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
// .apis(RequestHandlerSelectors.withMethodAnnotation(GetMapping.class))
.build();
}
/**
* 自定义文档信息
*
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfo(swaggerProperties.getTitle(), //文档标题
swaggerProperties.getDescription(), //文档描述
swaggerProperties.getVersion(), //版本号
swaggerProperties.getTermsOfServiceUrl(), //组织信息
new Contact(swaggerProperties.getName(), //个人信息
swaggerProperties.getUrl(),
swaggerProperties.getEmail()
),
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
}
修改yml文件
swagger2:
name: "小白"
description: "测试的接口"
email: 78414842@qq.com
title: "后台"
version: 1.0
group-name: leige
terms-of-service-url: https://gitee.com/smiledouble
url: https://gitee.com/smiledouble
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mvc:
format:
date-time: yyyy-MM-dd HH:mm:ss
创建Hero类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "Hero", description = "英雄对象")
public class Hero {
@ApiModelProperty(value = "英雄的id")
private Integer id;
@ApiModelProperty(value = "英雄的名称")
private String name;
@ApiModelProperty(value = "英雄的地址")
private String address;
@ApiModelProperty(value = "英雄的生日")
private Date birth;
@ApiModelProperty(value = "英雄的爱好")
private List<String> hobby;
@ApiModelProperty(value = "英雄的map")
private Map<String, String> map;
}
创建Controller
@Api(tags = "英雄的管理接口") //标记一个controller信息
@RestController
public class TestController {
/**
* 获取一个英雄
*
* @param id 使用restful风格,讲参数拼接在url上
* @return
*/
@GetMapping("getOneHero/{id}")
@ApiOperation(value = "获取一个英雄", notes = "根据id获取一个英雄对象")
@ApiImplicitParam(name = "id", value = "英雄编号(必填)", required = true, dataType = "Integer", paramType = "path")
public Hero getOne(@PathVariable(value = "id") Integer id) {
Map<String, String> map = new HashMap<>(4);
map.put("q", "哈萨克");
return new Hero(id, "亚索", "峡谷", new Date(), Arrays.asList("吹风,笛子"), map);
}
/**
* 添加英雄
*
* @param hero
* @return
*/
@PostMapping("addHero")
@ApiOperation(value = "创建英雄", notes = "根据Hero创建英雄对象")
@ApiImplicitParam(name = "hero", value = "英雄对象", required = true, dataTypeClass = Hero.class, paramType = "body")
public Map<String,Object> addHero(@RequestBody Hero hero) {
System.out.println(hero);
Map<String,Object> res=new HashMap<>();
res.put("code",200);
res.put("msg","添加成功");
return res;
}
/**
* 删除一个英雄
*
* @param id
* @return
*/
@DeleteMapping("deleteHero")
@ApiOperation(value = "删除一个英雄", notes = "根据删除一个英雄对象")
@ApiImplicitParam(name = "id", value = "英雄编号(必填)", required = true, dataType = "Integer", paramType = "query")
public Map<String,Object> deleteHero(@RequestParam("id") Integer id) {
System.out.println(id);
Map<String,Object> res=new HashMap<>();
res.put("code",200);
res.put("msg","删除成功");
return res;
}
}
测试访问文档页面
启动项目访问 http://localhost:8080/swagger-ui/index.html
测试接口
补充注解说明
https://gumutianqi1.gitbooks.io/specification-doc/content/tools-doc/spring-boot-swagger2-guide.html
更换页面ui
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
注解总结
model(实体类) 的注解
|--@ApiModel(value = "Hero", description = "英雄对象") 标记这个类的描述
|--@ApiModelProperty(value = "英雄的id") 标记类的属性的作用
controller上的注解
|--@Api(tags = "英雄测试类接口",description = "描述")
|--作用在类上,标记这个contorller的作用
|--@ApiOperation(value = "根据ID获取英雄对象",notes = "根据id获取一个英雄对象") //当前接口的描述
|--作用在方法上,标记这个方法的作用
|--@ApiImplicitParam(name = "hero", value = "英雄对象", required = true, dataTypeClass = Hero.class, paramType = "body")
|--作用在方法上,标记这个方法需要哪些参数 及需要的数据格式
配置类
|--@EnableOpenApi //启用swagger
22.集成shiro非前后端分离【重点】
之前SSM的配置
- UserRealm
- shiro.xml
- springmvc.xml
- web.xml
导入sql建表
/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50724
Source Host : localhost:3306
Source Schema : shiro
Target Server Type : MySQL
Target Server Version : 50724
File Encoding : 65001
Date: 15/12/2020 14:53:39
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission` (
`perid` int(11) NOT NULL AUTO_INCREMENT,
`pername` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`percode` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`perid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, '用户查询', 'user:query');
INSERT INTO `permission` VALUES (2, '用户添加', 'user:add');
INSERT INTO `permission` VALUES (3, '用户修改', 'user:update');
INSERT INTO `permission` VALUES (4, '用户删除', 'user:delete');
INSERT INTO `permission` VALUES (5, '导出用户', 'user:export');
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`roleid` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`roleid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '超级管理员');
INSERT INTO `role` VALUES (2, 'CEO');
INSERT INTO `role` VALUES (3, '保安');
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
`perid` int(255) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`perid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (1, 3);
INSERT INTO `role_permission` VALUES (2, 1);
INSERT INTO `role_permission` VALUES (2, 2);
INSERT INTO `role_permission` VALUES (3, 1);
INSERT INTO `role_permission` VALUES (3, 2);
INSERT INTO `role_permission` VALUES (4, 1);
INSERT INTO `role_permission` VALUES (5, 3);
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`userpwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', '639ffb0cbcca39d4fff8348844b1974e', '男', '武汉');
INSERT INTO `user` VALUES (2, 'lisi', '0d303fa8e2e2ca98555f23a731a58dd9', '女', '北京');
INSERT INTO `user` VALUES (3, 'wangwu', '473c41db9af5cc0d90e7adfd2b6d9180', '女', '成都');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`userid` int(11) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`userid`, `roleid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (2, 2);
INSERT INTO `user_role` VALUES (3, 3);
SET FOREIGN_KEY_CHECKS = 1;
分析表的数据
zhangsan/123456 超级管理员 1234号权限user:query user:add user:update user:delete
lisi/123456 CEO 123号权限user:query user:add user:update
wangwu/123456 保安 15号权限 user:query user:export
创建项目选择依赖
<!-- 引入druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<!-- shiro的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<!-- shiro和thymeleraf的依赖 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
修改主启动类
创建yml
#配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/0817-shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
druid:
# filters: log4j,stat
max-active: 20
min-idle: 5
validation-query: select x
initial-size: 3
max-wait: 5000
stat-view-servlet:
login-username: root
login-password: 123456
allow:
deny:
url-pattern: /druid/*
enabled: true #启用数据源监控
#mybatis的配置
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.wxz.domain #配置字别名
#shiro的配置
shiro:
hash-iterations: 2
hash-algorithm-name: md5
login-url: /index.html
unauthorized-url: /unauthorized.html
log-out-url: /login/logout
anon-url: #这些地址不用认证
- /index.html*
- /login.html*
- /login/toLogin*
- /login/doLogin*
authc-url: #下面的需要认证
- /**
生成User
package com.wxz.domain;
public class User {
/*
*用户id
*/
private Integer userid;
/*
*用户名
*/
private String username;
/*
*密码
*/
private String userpwd;
/*
*性别
*/
private String sex;
/*
*地址
*/
private String address;
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpwd() {
return userpwd;
}
public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
UserMapper
package com.wxz.mapper;
import com.wxz.domain.User;
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
User queryUserByUserName(@Param("username") String username);
}
UserMapper.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.wxz.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.wxz.domain.User">
<!--@mbg.generated-->
<!--@Table `user`-->
<id column="userid" jdbcType="INTEGER" property="userid" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="userpwd" jdbcType="VARCHAR" property="userpwd" />
<result column="sex" jdbcType="VARCHAR" property="sex" />
<result column="address" jdbcType="VARCHAR" property="address" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
userid, username, userpwd, sex, address
</sql>
<select id="queryUserByUserName" resultMap="BaseResultMap">
<!--@mbg.generated-->
select
<include refid="Base_Column_List" />
from `user`
where username = #{username}
</select>
</mapper>
UserService
package com.wxz.service;
import com.wxz.domain.User;
public interface UserService{
/**
* 据用户名去数据库查询用户
* @param username
* @return
*/
User queryUserByUserName(String username);
}
UserServiceImpl
package com.wxz.service.impl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.domain.User;
import com.wxz.mapper.UserMapper;
import com.wxz.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Resource
private UserMapper userMapper;
@Override
public User queryUserByUserName(String username) {
return this.userMapper.queryUserByUserName(username);
}
}
生成Role
package com.wxz.domain;
public class Role {
/*
*角色id
*/
private Integer roleid;
/*
*角色名
*/
private String rolename;
public Integer getRoleid() {
return roleid;
}
public void setRoleid(Integer roleid) {
this.roleid = roleid;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
}
RoleMapper
package com.wxz.mapper;
import com.wxz.domain.Role;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RoleMapper {
List<Role> queryRolesByUserId(@Param("userid") Integer userid);
}
RoleMapper.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.wxz.mapper.RoleMapper">
<resultMap id="BaseResultMap" type="com.wxz.domain.Role">
<!--@mbg.generated-->
<!--@Table `role`-->
<id column="roleid" jdbcType="INTEGER" property="roleid" />
<result column="rolename" jdbcType="VARCHAR" property="rolename" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
roleid, rolename
</sql>
<select id="queryRolesByUserId" resultMap="BaseResultMap">
select t1.* from role t1 inner join user_role t2
on(t1.roleid=t2.roleid) where t2.userid=#{userid}
</select>
</mapper>
RoleService
package com.wxz.service;
import com.wxz.domain.Role;
import java.util.List;
public interface RoleService{
/**
* 根据用户ID查询用户拥有的角色
* @param userid
* @return
*/
List<String> queryRolesByUserId(Integer userid);
}
RoleServiceImpl
package com.wxz.service.impl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.mapper.RoleMapper;
import com.wxz.domain.Role;
import com.wxz.service.RoleService;
import java.util.ArrayList;
import java.util.List;
@Service
public class RoleServiceImpl implements RoleService{
@Resource
private RoleMapper roleMapper;
@Override
public List<String> queryRolesByUserId(Integer userid) {
List<Role> roleList=this.roleMapper.queryRolesByUserId(userid);
List<String> roles=new ArrayList<>();
for (Role role : roleList) {
roles.add(role.getRolename());
}
return roles;
}
}
生成Permission
package com.wxz.domain;
public class Permission {
/*
*权限id
*/
private Integer perid;
/*
*权限名
*/
private String pername;
private String percode;
public Integer getPerid() {
return perid;
}
public void setPerid(Integer perid) {
this.perid = perid;
}
public String getPername() {
return pername;
}
public void setPername(String pername) {
this.pername = pername;
}
public String getPercode() {
return percode;
}
public void setPercode(String percode) {
this.percode = percode;
}
}
PermissionMapper
package com.wxz.mapper;
import com.wxz.domain.Permission;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface PermissionMapper {
List<Permission> queryPermissionsByUserId(@Param("userid") Integer userid);
}
PermissionMapper.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.wxz.mapper.PermissionMapper">
<resultMap id="BaseResultMap" type="com.wxz.domain.Permission">
<!--@mbg.generated-->
<!--@Table permission-->
<id column="perid" jdbcType="INTEGER" property="perid" />
<result column="pername" jdbcType="VARCHAR" property="pername" />
<result column="percode" jdbcType="VARCHAR" property="percode" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
perid, pername, percode
</sql>
<select id="queryPermissionsByUserId" resultMap="BaseResultMap">
select distinct t1.* from permission t1 inner join role_permission t2 inner join user_role t3
on(t1.perid=t2.perid and t2.roleid=t3.roleid)
where t3.userid=#{userid}
</select>
</mapper>
PermissionService
package com.wxz.service;
import java.util.List;
public interface PermissionService{
/**
* 根据用户ID查询用户拥有的权限
* @param userid
* @return
*/
List<String> queryPermissionsByUserId(Integer userid);
}
PermssionServiceImpl
package com.wxz.service.impl;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import com.wxz.mapper.PermissionMapper;
import com.wxz.domain.Permission;
import com.wxz.service.PermissionService;
import java.util.ArrayList;
import java.util.List;
@Service
public class PermissionServiceImpl implements PermissionService{
@Resource
private PermissionMapper permissionMapper;
@Override
public List<String> queryPermissionsByUserId(Integer userid) {
List<Permission> permissionList=this.permissionMapper.queryPermissionsByUserId(userid);
List<String> permissions=new ArrayList<>();
for (Permission permission : permissionList) {
permissions.add(permission.getPercode());
}
return permissions;
}
}
创建ActiveUser
package com.wxz.common;
import com.wxz.domain.User;
import com.wxz.domain.User;
import java.util.List;
public class ActiveUser {
private User user;
private List<String> roles;
private List<String> permissions;
public ActiveUser() {
this.user = user;
this.roles = roles;
this.permissions = permissions;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
}
创建UserRealm
package com.wxz.realm;
import com.wxz.common.ActiveUser;
import com.wxz.domain.User;
import com.wxz.service.PermissionService;
import com.wxz.service.RoleService;
import com.wxz.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* 做认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//得到用户名
String username = token.getPrincipal().toString();
//根据用户名去数据库查询用户
User user=this.userService.queryUserByUserName(username);
if(null!=user){
ActiveUser activeUser = new ActiveUser();
activeUser.setUser(user);
//根据用户ID查询用户拥有的角色
List<String> roles=this.roleService.queryRolesByUserId(user.getUserid());
//根据用户ID查询用户拥有的权限
List<String> permissions=this.permissionService.queryPermissionsByUserId(user.getUserid());
activeUser.setRoles(roles);
activeUser.setPermissions(permissions);
//进行密码匹配
ByteSource salt=ByteSource.Util.bytes(user.getUsername()+user.getAddress());
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(activeUser,user.getUserpwd(),salt,this.getName());
return info;
}else{
//用户名不存在
return null;
}
}
/**
* 作授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
//取出activeUser
ActiveUser activeUser= (ActiveUser) principals.getPrimaryPrincipal();
List<String> roles = activeUser.getRoles();
List<String> permissions = activeUser.getPermissions();
if(null!=roles&&!roles.isEmpty()){ //一定要判断,如果roles为空shiro会出异常
info.addRoles(roles);
}
if(permissions!=null&&!permissions.isEmpty()){
info.addStringPermissions(permissions);
}
return info;
}
}
创建ShiroProperties配置信息类
package com.wxz.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties("shiro")
public class ShiroProperties {
//散列次数
private Integer hashIterations=2;
//加密方式
private String hashAlgorithmName="MD5";
//登录
private String loginUrl;
//未授权证
private String unauthorizedUrl;
//登出
private String logOutUrl;
//放行的请求
private String[] anonUrl;
//需要认证的请求
private String[] authcUrl;
}
创建ShiroAutoConfiguration配置类
package com.wxz.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.wxz.realm.UserRealm;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableConfigurationProperties(value = ShiroProperties.class)
public class ShiroAutoConfiguration {
private static final String SHIRO_FILTER = "shiroFilter";
private ShiroProperties properties;
public ShiroAutoConfiguration(ShiroProperties properties) {
this.properties = properties;
}
/**
* 创建凭证匹配器
* @return
*/
@Bean
public CredentialsMatcher credentialsMatcher(){
HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
//设置加密算法
credentialsMatcher.setHashAlgorithmName(properties.getHashAlgorithmName());
//设置散列次数
credentialsMatcher.setHashIterations(properties.getHashIterations());
return credentialsMatcher;
}
/**
* 配置自定义的realm
* @param credentialsMatcher
* @return
*/
@Bean
public UserRealm userRealm(CredentialsMatcher credentialsMatcher){
UserRealm userRealm=new UserRealm();
//注入一个凭证匹配器
userRealm.setCredentialsMatcher(credentialsMatcher);
return userRealm;
}
/**
* 配置shiro的核心管理器
*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 配置过滤器链
*/
@Bean(SHIRO_FILTER)
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
//注入安全管理器
filterFactoryBean.setSecurityManager(securityManager);
//设置登陆页面
filterFactoryBean.setLoginUrl(properties.getLoginUrl());
//设置未授权的页面
filterFactoryBean.setUnauthorizedUrl(properties.getUnauthorizedUrl());
//注入过滤器链
Map<String,String> map=new HashMap<>();
String[] anonUrl = properties.getAnonUrl();//不用认证
String[] authcUrl = properties.getAuthcUrl();//需要认证之后才能访问的
//注入不用认证的路径
if(null!=anonUrl&&anonUrl.length>0){
for (String anon : anonUrl) {
map.put(anon,"anon");
}
}
//注入要认证的路径
if(null!=authcUrl&&authcUrl.length>0){
for (String authc : authcUrl) {
map.put(authc,"authc");
}
}
filterFactoryBean.setFilterChainDefinitionMap(map);
return filterFactoryBean;
}
/**
* 替换web.xml的配置 注册过滤器
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyFilterRegistrationBean(){
DelegatingFilterProxy filterProxy=new DelegatingFilterProxy();
FilterRegistrationBean<DelegatingFilterProxy> filterProxyFilterRegistrationBean=new FilterRegistrationBean<>();
filterProxyFilterRegistrationBean.setFilter(filterProxy);
filterProxyFilterRegistrationBean.addInitParameter("targetBeanName",SHIRO_FILTER);
filterProxyFilterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
//设置拦击的servlet 就是前端控制器
filterProxyFilterRegistrationBean.setServletNames(Arrays.asList(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
return filterProxyFilterRegistrationBean;
}
/**
* 这里是为了能在html页面引用shiro标签,不然不会走授权方法
*/
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
创建LoginController
package com.wxz.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("login")
public class LoginController {
/**
* 跳转到登陆页面
*/
@GetMapping("toLogin")
public String toLogin(){
return "login";
}
/**
* 做登陆
*/
@PostMapping("doLogin")
public String doLogin(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try{
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return "list";
}catch (UnknownAccountException e){
model.addAttribute("error","用户名不存在");
}catch (IncorrectCredentialsException e){
model.addAttribute("error","密码不正确");
}catch (Exception e){
model.addAttribute("error","登陆失败,原因:"+ e.getMessage());
}
return "login";
}
}
创建static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script>
window.location.href="/login/toLogin"
</script>
</html>
创建template/login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户登陆</title>
</head>
<body>
<h1>用户登陆</h1>
<h2 style="color: red" th:text="${error}"></h2>
<form action="/login/doLogin" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登陆" />
</form>
</body>
</html>
创建template/list.html
<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>用户管理</title>
</head>
<body>
<h1><a href="" shiro:hasPermission="user:query">用户查询</a></h1>
<h1><a href="" shiro:hasPermission="user:add">用户添加</a></h1>
<h1><a href="" shiro:hasPermission="user:update">用户修改</a></h1>
<h1><a href="" shiro:hasPermission="user:delete">用户删除</a></h1>
<h1><a href="" shiro:hasPermission="user:export">用户导出</a></h1>
</body>
</html>
启动项目测试:
zhangsan/123456 超级管理员 1234号权限user:query user:add user:update user:delete
lisi/123456 CEO 123号权限user:query user:add user:update
wangwu/123456 保安 15号权限 user:query user:export
zhangsan登录:
lisi登录
wangwu登录:
23.集成shiro前后端分离
复制上一个项目
创建ResultObj
情况说明:
- 用户没有登陆,直接访问资源 返回一个json提示您没有登陆
- 用户已经登陆,访问有权限的资源,返回controller返回的数据
- 用户已登陆,没有资源访问权限,返回一个您没有权限的json串
创建全局异常监控
package com.wxz.exception;
import com.wxz.commons.ResultObj;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice //如果出在异常,直接以json串的形式返回
public class GlobalExceptionHandler {
/**
* 捕获未授权的全局异常
*/
@ExceptionHandler(value = {UnauthorizedException.class})
public ResultObj unauthorizedExp(){
return new ResultObj(302,"您没有访问权限","");
}
}
创建LoginFormAuthenticationFilter 处理未登陆重定向的问题
package com.wxz.config;
import com.alibaba.fastjson.JSON;
import com.wxz.commons.ResultObj;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class LoginFormAuthenticationFilter extends FormAuthenticationFilter {
/**
* @param servletRequest
* @param servletResponse
* @return 返回值 如果为false就代表拦截
* 没有登陆才会走这个方法
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request= (HttpServletRequest) servletRequest;
ResultObj resultObj=new ResultObj(302,"您没有登陆","");
String json= JSON.toJSONString(resultObj);
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("application/json");
servletResponse.getWriter().write(json);
return false;
}
}
加入fastjson依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
修改ShiroAutoConfiguration
/**
* shiro的filter
*/
@Bean(SHIRO_FILTER)
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean filterFactoryBean=new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
filterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl());
filterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl());
//覆盖之前的authc过滤器
Map<String, Filter> filters=new HashMap<>();
filters.put("authc",new LoginFormAuthenticationFilter());
filterFactoryBean.setFilters(filters);
String[] anonUrl = shiroProperties.getAnonUrl();//不用认证
String[] authcUrl = shiroProperties.getAuthcUrl();//需要认证之后才能访问的
Map<String,String> map=new HashMap<>();
if(anonUrl!=null&&anonUrl.length>0){
for (String url : anonUrl) {
map.put(url,"anon");
}
}
if(authcUrl!=null&&authcUrl.length>0){
for (String url : authcUrl) {
map.put(url,"authc");
}
}
map.put(shiroProperties.getLogOutUrl(), "logout");
filterFactoryBean.setFilterChainDefinitionMap(map);
return filterFactoryBean;
}
/**
* 替换web.xml的配置 注册过滤器
*/
@Bean
public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxyFilterRegistrationBean(){
DelegatingFilterProxy filterProxy=new DelegatingFilterProxy();
FilterRegistrationBean<DelegatingFilterProxy> filterProxyFilterRegistrationBean=new FilterRegistrationBean<>();
filterProxyFilterRegistrationBean.setFilter(filterProxy);
filterProxyFilterRegistrationBean.addInitParameter("targetBeanName",SHIRO_FILTER);
filterProxyFilterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
//设置拦击的servlet 就是前端控制器
filterProxyFilterRegistrationBean.setServletNames(Arrays.asList(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME));
return filterProxyFilterRegistrationBean;
}
/*加入注解的使用,不加入这个注解不生效--开始*/
/**
* @param securityManager
*
* <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
*
* <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
* depends-on="lifecycleBeanPostProcessor"/>
* <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
* <property name="securityManager" ref="securityManager"/>
* </bean>
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/*加入注解的使用,不加入这个注解不生效--结束*/
修改LoginController
package com.wxz.controller;
import com.wxz.commons.ActiverUser;
import com.wxz.commons.ResultObj;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("login")
public class LoginController {
/**
* 登陆
*/
@GetMapping("doLogin")
public ResultObj doLogin(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
String msg="";
try{
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return new ResultObj(200,"登陆成功",username);
}catch (UnknownAccountException e){
model.addAttribute("error","用户名不存在");
msg="用户名不存在";
}catch (IncorrectCredentialsException e){
model.addAttribute("error","密码不正确");
msg="密码不正确";
}catch (Exception e){
model.addAttribute("error","登陆失败,原因:"+ e.getMessage());
msg="登陆失败,原因:"+ e.getMessage();
}
return new ResultObj(401,msg,"");
}
}
修改UserController
package com.wxz.controller;
import com.wxz.commons.ResultObj;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("query")
@RequiresPermissions(value = {"user:query"})
public Object query(){
return new ResultObj(200,"查询成功","");
}
@GetMapping("add")
@RequiresPermissions(value = {"user:add"})
public Object add(){
return new ResultObj(200,"添加成功","");
}
@GetMapping("update")
@RequiresPermissions(value = {"user:update"})
public Object update(){
return new ResultObj(200,"更新成功","");
}
@GetMapping("delete")
@RequiresPermissions(value = {"user:delete"})
public Object delete(){
return new ResultObj(200,"删除成功","");
}
@GetMapping("export")
@RequiresPermissions(value = {"user:export"})
public Object export(){
return new ResultObj(200,"导出成功","");
}
}
测试
http://127.0.0.1:8080/login/doLogin?username=lisi&password=123456
http://127.0.0.1:8080/user/query
http://127.0.0.1:8080/user/export
24.普通缓存(非redis)
在SpringBoot中可以使用注解式开发缓存,默认没有开启缓存中间件,那么使用的就是存储在Map中的原理,但是我们还可以配置自己的缓存中间件,比如redis
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
创建yaml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
druid:
max-active: 10
min-idle: 2
validation-query: select 'x'
stat-view-servlet:
login-username: admin
enabled: true #启用监控页
login-password: admin
allow:
deny:
url-pattern: /druid/*
#配置mybatis
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #输出sql
mapper-locations:
- classpath:mapper/*Mapper.xml
开启缓存
创建UserController
package com.wxz.controller;
import com.wxz.domain.User;
import com.wxz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 查询ID查询一个用户
*/
@GetMapping("queryUserById")
public User queryUserById(Integer id){
return this.userService.queryUserById(id);
}
/**
* 添加一个用户
*/
@GetMapping("addUser")
public User addUser(User user){
return this.userService.addUser(user);
}
/**
* 修改一个用户
*/
@GetMapping("updateUser")
public User updateUser(User user){
return this.userService.updateUser(user);
}
/**
* 删除一个用户
*/
@GetMapping("deleteUserById")
public String deleteUserById(Integer id){
int i=this.userService.deleteUserById(id);
return i>0?"删除成功":"删除失败";
}
}
创建UserService
package com.wxz.service;
import com.wxz.domain.User;
public interface UserService {
User queryUserById(Integer id);
User addUser(User user);
User updateUser(User user);
Integer deleteUserById(Integer id);
}
创建UserServiceImpl
package com.wxz.service.impl;
import com.wxz.domain.User;
import com.wxz.mapper.UserMapper;
import com.wxz.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Cacheable(value = "user",key = "#id",unless = "#result==null",sync = true)
@Override
public User queryUserById(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
@Override
@CachePut(value = "user",key = "#user.id")
public User addUser(User user) {
userMapper.insertSelective(user);
return user;
}
@Override
@CachePut(value = "user",key = "#user.id")
public User updateUser(User user) {
userMapper.updateByPrimaryKeySelective(user);
return this.userMapper.selectByPrimaryKey(user.getId());
}
@Override
@CacheEvict(value = "user",key = "#id")
public Integer deleteUserById(Integer id) {
return this.userMapper.deleteByPrimaryKey(id);
}
}
修改启动类
25.Spring Boot定时任务
相关注解
@Scheduled()
@PostConstruct 这个注解不是定时任务的
当IOC容器里面的所有对象全部初始化完成之后会调用的方法
- sprignBoot定时任务是与quartz整合,不需要添加任何的依赖
- 在springBoot的启动类上添加`@EnableScheduling`注解开启定时调度
- 在需要定时调度的方法上添加`@Scheduled`这个注解即可,其中可以指定**cron表达式和其他的定时方式
开启定时任务
执行定时任务
package com.wxz.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyTask {
// @Scheduled(fixedDelay = 2000) //固定延时2秒执行一次
// @Scheduled(fixedRate=2000) //fixedRate 每过多少秒执行一次
// @Scheduled(initialDelay = 2000, fixedRate = 5000) //第一次延迟两秒执行,后面按照fixedRate的规则执行
@Scheduled(cron = "0 30 0 1/1 * ? *") //每天晚上12点半执行一次https://www.matools.com/cron/
public void task1(){
System.out.println("定时任务执行了。。。。。。");
}
}
https://cron.qqe2.com/
26.Spring Boot邮件发送
SpringBoot实现邮件功能是非常的方便快捷的,因为SpringBoot默认有starter实现了Mail。
发送邮件应该是网站的必备功能之一,什么注册验证,忘记密码或者是给用户发送营销信息。
最早期的时候我们会使用JavaMail相关api来写发送邮件的相关代码,后来spring退出了JavaMailSender更加简化了邮件发送的过程,在之后springboot对此进行了封装就有了现在的spring-boot-starter-mail。
准备工作
先去qq邮箱设置smtp开启,并获得授权码
邮箱->设置->账户->POP3/SMTP服务:开启服务后会获得权码、
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
编写测试类:
package com.wxz;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
@SpringBootTest
class SpringbootHelloApplicationTests {
@Autowired
private JavaMailSender javaMailSender;
@Test
void contextLoads() {
System.out.println(javaMailSender);
}
@Test
void sendSimpleMail(){
SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
//设置发送人
simpleMailMessage.setFrom("111@qq.com");
//设置接收人
simpleMailMessage.setTo("222@qq.com");
//设置主题
simpleMailMessage.setSubject("这是一个测试开发的邮件发送");
//设置内容
simpleMailMessage.setText("收到后请认真查看 http://www.leige.plus:8088?id=123456");
javaMailSender.send(simpleMailMessage);
}
/**
* 发送一个带图片,带附件的邮件
*/
@Test
void sendComplexMail() throws MessagingException {
//创建mimeMessage
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
//创建一个MimeMessageHelper,来组装参数 true表示开启附件,utf-8表示编码
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
//设置发送人
mimeMessageHelper.setFrom("111@qq.com");
//设置接收人
String[] to ={"2222@qq.com"};
mimeMessageHelper.setTo(to);
//设置主题
mimeMessageHelper.setSubject("这是一个复杂的邮件,带html解析和附件的哦");
//拼接内容参数
StringBuilder sb = new StringBuilder();
sb.append("<html> <body> <h1 style='color:red'>springboot 测试邮件发送复杂格式o</h1>");
sb.append("<p style='color:blue,font-size:16px'>哈哈哈</p>");
sb.append("<p style='text-align:center'>居中</p>");
sb.append("<img src='cid:picture'/> </body></html>"); //如果要插入图片src='cid:picture'
//设置内容,可以被html解析
mimeMessageHelper.setText(sb.toString(), true);
//添加内容图片
mimeMessageHelper.addInline("picture", new File("D:\\img\\bg_car.jpg"));
//添加附件
mimeMessageHelper.addAttachment("美女大图.zip", new File("D:\\img\\dog.jpg"));
javaMailSender.send(mimeMessage);
}
}