org.springframework.boot spring-boot-devtools true true org.springframework.boot spring-boot-maven-plugin true
applicaion.yml
spring:
devtools:
restart:
enabled: true #设置开启热部署
additional-paths: src/main/java #重启目录
exclude: WEB-INF/**
freemarker:
cache: false #页面不加载缓存,修改即时生效
文件上传
创建工具类
//接收上传文件的工具类
public class ReceiveUploadFileUtil {
public static Map receiveFile(MultipartFile file) {
Map map = new HashMap();
if (file.isEmpty()) {
map.put("code", 202);
map.put("msg", "文件为空");
return map;
}
//获取文件后缀名示例 只接收以下文件
String filename = file.getOriginalFilename(); //获取文件名
String suffixName = filename.substring(filename.lastIndexOf(".")).toLowerCase();//获取后缀名并且转换为小写
if (!(".exe".equals(suffixName) || ".bat".equals(suffixName))) {
map.put("code", 202);
map.put("msg", "文件格式错误");
return map;
}
//设置文件夹 (获取当前年月)
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
String strFolderPath = format.format(new Date());
//判断文件夹是否存在 不存在则创建
if (isLinux()) { strFolderPath = "/home/uploadFile/" + strFolderPath; } //Linux
else { strFolderPath = "D:\uploadFile\" + strFolderPath; } //Windows
File dir = new File(strFolderPath);
if (dir.exists()) {
dir.mkdirs();
}
//修改文件名,防止重名 生成文件示例:1628154571624-1.jpg
String fileName=System.currentTimeMillis() + "-" + String.valueOf(new Random().nextInt(100))+suffixName;
//保存文件
try {
File dest = new File(strFolderPath,fileName);
file.transferTo(dest);
map.put("code", 200);
map.put("msg", "上传成功!");
} catch (IOException e) {
e.printStackTrace();
map.put("code", 400);
map.put("msg", "上传失败!");
}
return map;
}
public static boolean isLinux() {
Properties properties = System.getProperties();
String os = properties.getProperty("os.name");
if (os != null && os.toLowerCase().indexOf("linux") > -1) {
return true;
} else {
return false;
}
}
}
Thymeleaf模板引入
依赖
org.thymeleaf thymeleaf-spring5 org.thymeleaf.extras thymeleaf-extras-java8time org.springframework.boot spring-boot-starter-thymeleaf
头文件
xmlns:th=“http://www.thymeleaf.org”
相关配置
spring:
thymeleaf:
#cache:是否缓存,开发模式下设置为false,避免改了模板还要重启服务罂、线上设置为true、可以提高性能。
cache: false
#prefix:指定模板所在的目录
prefix: classpath:/templates/
#check-template-Location:检查模板路径是否存在
check-template: true
suffix: .html
encoding: UTF-8
mode: HTML5
解决模板引擎标红问题—将值放入map中即可
@RequestMapping("
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Map exceptionHandler( Exception e ) {
Map map = new HashMap<>();
map.put("code" , 500);
map.put( "msg“,"系统异常,请重试!");
return map;
}
//处理特定的异常 两种写法 2
@ExceptionHandler(value = ParamsException.class)
@ResponseBody
public Map exceptionHandler( ParamsException p ) {
Map map = new HashMap<>();
map.put("code" , p.getCode());
map.put( "msg“,p.getMsg());
return map;
}
// 其他类型的异常......
}
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Object exceptionHandler(Exception e) {
Map map = new HashMap<>();
map.put(e.getClass().toString(), e.getMessage());
log.error("异常类型:{}--异常信息:{}", e.getClass(), e.getMessage());
return map;
}
}
跳转至页面
// 自定义异常处理器
@ControllerAdvice
public class CustomExtHandler {
@ExceptionHandler(value=Exception.class)//处理哪一类异常
Object handlerException(Exception e, HttpServletRequest request){
ModelAndView modelAndView = new ModelAndView();
//跳转异常页面路径
modelAndView.setViewName("404.html");
//页面显示错误信息 页面只需要使用对应的取值方式取值就可以取到msg了
modelAndView.addObject("msg",e.getMessage());
return modelAndView;
}
}
日志配置
logback
需先导入lombok依赖
org.projectlombok lombok true ch.qos.logback logback-classic
logback配置—整合了mybatis配置 [配置文件详解](logback配置文件—logback.xml详解 - 马非白即黑 - 博客园 (cnblogs.com))
INFO ${CONSOLE_LOG_PATTERN} ${LOG_HOME}/promotion.%d{yyyy-MM-dd}.log 30 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 10MB ${LOG_HOME}/error/error.log ${LOG_HOME}/error/error-%d{yyyy-MM-dd}-%i.gz 50MB 365 20GB %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n ERROR ACCEPT DENY WARN ACCEPT DENY
读取配置文件
logging: config: classpath:log/logback.xml使用aop统一打印日志
导入aop依赖
org.springframework.boot spring-boot-starter-aop
新建aop.WebAopAspect切面类
package com.xxxy.core.aop;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.io.PrintWriter;
import java.io.StringWriter;
// web 切面类
@Component
@Aspect
public class WebAopAspect {
private static Logger logger = LoggerFactory.getLogger(WebAopAspect.class);
//切入点 所有方法
@Pointcut("execution(* com.xxxy.qd.service..*.*(..))") //-------注意包----------
public void pointCut(){
}
//前置通知
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
logger.info("---------------------------------->before");
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
StringBuilder log = new StringBuilder();
log.append("Service接入参数:")
.append("--->Service类名:")
.append(className)
.append("--->Service接口名:")
.append(methodName)
.append("--->Params: ");
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
log.append(JSONObject.toJSONString(arg)).append(",");
}
logger.info(log.toString());
}
//目标方法请求之后,打印信息
@AfterReturning(value = "pointCut()",returning = "returnObj")
public void afterReturn(Object returnObj){
String result = JSONObject.toJSONString(returnObj);
if(result.length() > 2000){
result = result.substring(0, 2000);
}
logger.info("------>Service传出参数:"+result.toString());
}
//处理程序中未处理的异常 注意包
@AfterThrowing(value = "execution(* com.qd.service..*.*(..))",throwing = "throwable")
public void afterThrowing(Throwable throwable){
logger.error("---ervice执行异常:"+throwable.getMessage(),throwable);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
@Around(value = "pointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint){
Long beginTime = System.currentTimeMillis();
StringBuilder log = new StringBuilder("---around:");
Object result = proceedingJoinPoint.getArgs();
try {
result = proceedingJoinPoint.proceed();
} catch (Throwable throwable) {
logger.error(log+throwable.getMessage(),throwable);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return getExceptionContent(throwable);
}
Long endTime = System.currentTimeMillis();
log.append("执行时间:").append(endTime-beginTime).append("ms");
return result;
}
public String getExceptionContent(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
}
整合Druid数据源
druid官方github地址: https://github.com/alibaba/druid
- 导入依赖
com.alibaba druid-spring-boot-starter 1.1.17
- 配置示例
spring:
datasource:
url: jdbc:mysql://localhost:3306/mytest
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true
login-username: admin
login-password: admin
resetEnable: false
web-stat-filter: # 监控web
enabled: true
urlPattern: /*
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
filter:
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000
logSlowSql: true
enabled: true
wall:
enabled: true
config:
drop-table-allow: false
注意 启动类需要添加注解 用来扫描Mapper包
整合Swagger了解更多请参考第三方文档:https://www.yuque.com/atguigu/springboot/aob431#pQCsf
- 号称世界上最流行的Api框架;
- RestFul Api文档在线自动生成工具=>Api文档与API定义同步更新
- 直接运行
- 支持多种语言
-
新建项目
-
导入依赖
io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 com.github.xiaoymin swagger-bootstrap-ui 2.9.2
- 编写工程
- 配置Swagger==》Config
package com.qd.swagger.config;
@Configuration
@EnableSwagger2 //开启Swagger2
@EnableKnife4j
public class SwaggerConfig {
}
- 测试运行 : http://localhost:8080/swagger-ui.html
- 配置类增加为
@Configuration
@EnableSwagger2 //开启Swagger2
@EnableKnife4j
public class SwaggerConfig {
//配置Swagger的Docket的bean的实例
@Bean
public Docket docket() {
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo()); //没有set与get方法,只能通过构造实例
}
//配置Swagger的信息==apiInfo
private ApiInfo apiInfo() {
//Contact contact=new Contact("前度","https://qd666.rthe.xyz/","2013498006@qq.com");
return new ApiInfo("前度的Swagger的API文档",
"这是一个描述",
"v1.0",
"https://qd666.rthe.xyz/",
"前度",
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0"
);
}
}
Swagger 配置扫描接口
Docket.select方法
//配置Swagger的Docket的bean的实例
@Bean
public Docket docket() {
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
//RequestHandlerSelectors 配置要扫描接口的方式
//basePackage 要扫描的包 any();none()
//withClassAnnotation 扫描类上的注解 参数是注解反射对象
//withMethodAnnotation 扫描类上的注解 RestController.class
.apis(RequestHandlerSelectors.basePackage("com.qd.swagger.controller"))
//过滤 只扫描qd下的类
// .paths(PathSelectors.ant("/qd/**"))
.build(); //没有set与get方法,只能通过构造实例
}
配置是否启动Swagger
//配置Swagger的Docket的bean的实例
@Bean
public Docket docket() {
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo())
//enable : 是否启用Swagger 默认为true
.enable(false)
.select()
.apis(RequestHandlerSelectors.basePackage("com.qd.swagger.controller"))
.build(); //没有set与get方法,只能通过构造实例
}
我只希望我的Swagger在生产环境中使用,在发布的时候不使用?
- 判断是不是生产环境
- 注入enable()
//配置Swagger的Docket的bean的实例
@Bean
public Docket docket(Environment environment) {
//设置要显示的Swagger环境
Profiles profiles = Profiles.of("dev");
//获取项目的环境 通过environment.acceptsProfiles判断是否处在自己设定的环境中
boolean falg = environment.acceptsProfiles(profiles);
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo())
//enable : 是否启用Swagger 默认为true
.enable(falg)
.select()
.apis(RequestHandlerSelectors.basePackage("com.qd.swagger.controller"))
.build(); //没有set与get方法,只能通过构造实例
}
配置分组
.groupName("前度")
如何配置多个组
//分组
@Bean
public Docket docket1(){
return new Docket(documentationType.SWAGGER_2).groupName("分组1");
}
@Bean
public Docket docket2(){
return new Docket(documentationType.SWAGGER_2).groupName("分组2");
}
@Bean
public Docket docket3(){
return new Docket(documentationType.SWAGGER_2).groupName("分组3");
}
实体类配置
//@api(注释)
@ApiModel("用户实体类")
public class User {
@ApiModelProperty("用户名")
public String userName;
@ApiModelProperty("密码")
public String password;
}
//====================================
@Api(tags = "接口注释")
@RestController
public class HelloController {
//只要接口中,返回值存在实体类,他就会被扫描到Swagger
@PostMapping(value = "/user")
public User user() {
return new User();
}
@PostMapping(value = "/hello")
@ApiOperation("HelloController") //Operation 方法注释
public String hello(@ApiParam("用户名") String userName) {
return "hello" + userName;
}
}
在线调试
视频第16分钟开始:https://www.bilibili.com/video/BV1PE411i7CV?p=50&spm_id_from=pageDriver
总结:
- 我们可以通过Swagger给一些比较难理解的属性或者接口,增加注释信息
- 接口文档实时更新
- 可以在线测试
Swagger是一个优秀的工具,几乎所有大公司都有使用它。
整合Ehcache缓存-
EhCache是一个比较成熟的Java 缓存框架,最早从hibernate 发展而来,是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,快速简单。
-
SpringBoot 缓存实现内部使用SpringCache实现缓存控制,这里集成Ehcache实际上是对SpringCache 抽象的其中一种实现,这里在使用Ehcache 实现缓存控制时相关注解说明如下:
@CacheConfig
用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字。
@cacheConfig(cacheNames = "users")
public interface Userservice {...}
配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable
应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中。
该注解主要有下面几个参数︰
- value、cacheNames : 两个等同的参数( cacheNames 为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4T新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了。
- key ∶缓存对象存储在Map集合中的 key值,非必需,缺省按照函数的所有参数组合作为 key值,若自己配置需使用SpEL表达式,比如: @Cacheable(key = “**#**pO”)∶使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档。
- condition : 缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如Cacheable(key = “#p0”, condition = “#p0.length()❤️”),表示只有当第一个参数的长度小于3的时候才会被缓存。
- unless : 另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的郸断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
- keyGenerator ∶用于指定key生成器,非必需。若需要指定一个自定义的 key生成器,我们需要去实现org.springframework.cache.interceptorKeyGenerator接口,并使用该参数来指定。需要注意的是︰该参数与key是互斥的。
- cacheManager︰用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
- cacheResolver∶用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接来实现自己的缓存解析器,并用该参数指定。
@cacheable(value = "user" , key = "#id") User selectUserById(final Integer id);
@CachePut
应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存,@CachePut的参数与 @Cacheable类似,示例如下∶
@CachePut(value = "user" , key = "#user.id")
public user save(user user) {
users.add(user) ;
return user;
}
@CacheEvict
应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据,示例如下︰
@CacheEvict(value = "user" , key = "#id") void delete(final Integer id);
除了同@Cacheable 一样的参数之外,@CacheEvict还有下面两个参数︰
- allEntries : 非必需,默认为false。当为true 时,会移除所有数据
- beforeInvocation : 非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
@Caching
组合多个Cache注解使用。示例︰
@caching(
put = {
@cachePut(value = "user" , key = "#user.id" ),
@cachePut( value = "user", key = "#user.username " ),
@CachePut(value = "user" , key = "#user.age")
}
}
将id —> user ; username —> user ; age —> user进行缓存。
环境配置- 添加依赖
org.springframework.boot spring-boot-starter-cache net.sf.ehcache ehcache
-
chcache.xml文件添加
在resource文件夹下创建文件ehcache.xml,并进行配置:
ehcache.xml 文件配置详解
diskStore:为缓存路径, ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。 defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。 仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。 最大时间介于创建时间和失效时间之间。 仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。 每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。 默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy: 可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO: first in first out,先进先出。 LFU : Less Frequently Used,一直以来最少被使用的。 如上面所讲,缓存的元素有一个hit属性,hit值最小 的将会被清出缓存。 LRU: Least Frequently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出 地方 来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
- application.yml缓存配置
##Ehcache缓存配置cache :
spring
ehcache:
config: classpath:ehcache.xml
- 在主类加上启动注解
在 Spring Boot 主类加上开启缓存的注解==@EnableCaching==。
- JavaBean 对象实现序列化
@ApiModel(description = "用户实体对象")
public class User implements Serializab1e{
@ApiModelProperty(value ="用户id")
private Integer id;
@ApiModelProperty(value = "用户名")
private string userName;
@ApiModelProperty(value ="用户密码")
private string userPwd ;
//省略get|set
}
- 用户详情页查询缓存
@cacheable(value = "users" ,key = "#userId")
public User queryUserByUserId( Integer userId) {
return userMapper.queryById (userId);
}
- 多个条件查询缓存
//用 - 分割 @Cacheable(value = "users" , key="#userQuery.userName + ' - ' +#userQuery.pageNum + ' -'+#userQuery.pagesize")
- 添加或修改方法
@CachePut(value = "users", key = "#user.id") public MapupdateUser(@RequestBody User user){ } //若出现异常 对应的注解类型返回对象应与数据库保持一致
- 删除方法
@CacheEvict(value = "users", key ="#userid") public MapdeleteUser(@PathVariable Integer userId){ }
ps:
整合Quartz这些注解建议添加在service层 并声明在方法上
在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量等。
在Spring Boot 中有Java自带的java.util.Timer类,也有强大的调度器Quartz,还有SpringBoot 自带的Scheduled 来实现。Scheduled在Spring5.引入,默认SpringBoot 自带该功能,使用起来也很简单,在启动类级别添加@EnableScheduling注解即可引入定时任务环境。但遗憾的是Scheduled 默认不支持分布式环境,这里主要讲解Quartz时钟调度框架与Spring Boot集成。
导入依赖org.springframework.boot spring-boot-starter-quartz 2.4.3
- 在Springboot启动类上打上注解
@EnableScheduling源代码添加
- 定义job
com.xxxx.springboot 下添加jobs包,定义待执行 job任务。实现Job接口,并且在 execute方法中实现自己的业务逻辑。
public class MyFirstob implements Job {
private Logger log = LoggerFactory.getLogger(MyFirstJob.class);
@override
public void execute(JobExecutionContext context) throws JobExecutionException{
simpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH : mm:ss");
log.info(sdf.format(new Date()) + "-->" +"Hello Spring Boot Quartz...");
}
}
- 构建调度配置类
创建JobDetail实例并定义Trigger注册到scheduler,启动scheduler开启调度
@configuration
public class Quartzconfig {
//准备JobDetail
@Bean
public JobDetail jobDetail1(){
return JobBuilder.newJob(myFirstob.class).storeDurably( ).build();
}
//准备Trigger
@Bean
public Trigger trigger1(){
simplescheduleBuilder scheduleBuilder = simplescheduleBuilder.simpleSchedule()
//每一秒执行一次
.withIntervalInSeconds(1)
//永久重复,一直执行下去.
.repeatForever();
return TriggerBuilder.newTrigger()
.withIdentity( "trigger1" ,"group1")
.withschedule(scheduleBuilder)
.forob( jobDetail1() ) //要改
.build( );
}
//每5秒触发一次任务
@Bean
public Trigger trigger2(){
return TriggerBuilder.newTrigger()
.withIdentity( "trigger2", "group1" )
.withschedule(cronscheduleBuilder.cronschedule("0/5 **** ?*")) //表达式
.forob( jobDetail1() ) //要改
.build();
}
}
表达式在线生成 : 在线Cron表达式生成器 - 码工具 (matools.com)
整合Redis- 导包或者从idea中选择
org.springframework.boot spring-boot-starter-data-redis
- 自定义模板
@Configuration
public class ReidsConfig {
//编写配置类
@Bean
@SuppressWarnings("all")
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//json序列化配置
Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer
- 封装工具类:百度~



