springboot的一些操作,比如异步编程,定时任务,发邮件,文件上传等等
6.java程序读取配置文件的内容
在配置文件添加
user.username=张三
user.password=123456
在java种使用
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("test1") @Slf4j public class TestController { @Value("${user.username}") private String username; @Value("${user.password}") private String password; @GetMapping("value") public String value(){ log.info("读取到配置文件的用户名为:{}",username); return password; } }
测试即可,如果中文乱码
#设置spring-boot 编码格式
spring.banner.charset=UTF-8
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8
注意事项:
静态变量无法直接赋值,需要通过下面的方式
private static String username; @Value("${user.username}") public void setUsername(String name){ username = name; }
# 随机数 # 随机int test.randomInt=${random.int} # 随机10以内 test.randomIntMax=${random.int(10)} # 随机20-50 test.randomIntMiddle=${random.int(20,50)} # 随机Long test.randomLong=${random.long} # 字符串 test.randomValue=${random.value} # uuid test.randomUuid=${random.uuid} # key不能random开头,使用时会有问题 #random.num=${random.int}
3.@ConfigrationProperties
student.name=李四
student.age=10
student.score=100.3
student配置文件
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "student") public class StudentProperties { private String name; private int age; private Double score; }
获取值
@Autowired private StudentProperties studentProperties; @GetMapping("student") public String student(){ log.info("通过configrationProperties获得配置文件种的学生姓名:{}",studentProperties.getName()); return "成绩为:"+studentProperties.getAge()+",分数为:"+studentProperties.getScore(); }
自定义配置文件,新建student.properties将上面学生的配置剪切到里面
@PropertySource({"classpath:student.properties"})
7.springboot热部署
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> <!-- 防止将devtools依赖传递到其他模块中 --> </dependency>
compile----build project auto...
ctrl+shift+a 输入registry... 勾选compile-automake-----run......
然后修改代码按ctrl+f9即可
其深层原理是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为 restart ClassLoader
,这样在有代码更改的时候,原来的restart ClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间(5秒以内)
Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志内容
回想直接给大家讲解ceki的故事,所以这里肯定选择logback
Logback是log4j框架的作者开发的新一代日志框架,它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J。
使用:
本来是需要加入如下依赖,但是spring-boot-starter种已经包含了,也就是不需要了
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-logging --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> <version>2.2.1.RELEASE</version> </dependency>
日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出。
Spring Boot中默认配置ERROR、WARN和INFO级别的日志输出到控制台
配置文件加入
#logging.file.name=/springboot.log
#logging.file.path=F:\\
#logging.level.*=debug
#logging.level.root=info
#logging.level.cn.cdqf=debug
#logging.file.max-size=10MB
logging.config=classpath:logback-spring.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"> <contextName>logback</contextName> <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 --> <property name="log.path" value="F://" /> <!--0. 日志格式和颜色渲染 --> <!-- 彩色日志依赖的渲染类 --> <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}}"/> <!--1. 输出到控制台--> <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> <!--2. 输出到文档--> <!-- 2.1 level为 DEBUG 日志,时间滚动输出 --> <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/logback_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}/web-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> <!-- 2.2 level为 INFO 日志,时间滚动输出 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/logback_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}/web-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> <!-- 2.3 level为 WARN 日志,时间滚动输出 --> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/logback_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}/web-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> <!-- 2.4 level为 ERROR 日志,时间滚动输出 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文档的路径及文档名 --> <file>${log.path}/logback_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}/web-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级别: 【logging.level.org.mybatis=debug logging.level.dao=debug】 --> <!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 不能设置为INHERITED或者同义词NULL。默认是DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 --> <!-- 4. 最终的策略 --> <!-- 4.1 开发环境:打印控制台--> <springProfile name="dev"> <logger name="cn.cdqf" level="debug"/> </springProfile> <root level="debug"> <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> <!-- 4.2 生产环境:输出到文档 <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>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
#javaMail配置(下面的 spring.mail.host 为发送邮箱的邮箱服务器) spring.mail.host=smtp.163.com spring.mail.username=18193982136@163.com spring.mail.password=13187573490l spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true @Autowired private JavaMailSender javaMailSender; @GetMapping("mail") public String mail() throws MessagingException { //建立邮件消息 MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); mimeMessageHelper.setFrom("18193982136@163.com"); mimeMessageHelper.setTo("383657231@qq.com"); mimeMessageHelper.setSubject("期末考试成绩"); StringBuffer sb = new StringBuffer(); sb.append("<html><h1>大标题-h1</h1>") .append("<p style='color:#F00'>红色字</p>") .append("<p style='text-align:right'>右对齐</p></html>"); mimeMessageHelper.setText(sb.toString()); javaMailSender.send(mimeMessage); return "邮箱发送成功"; }
垃圾邮件常规处理方式
message.addHeader("X-Mailer","Microsoft Outlook Express 6.00.2900.2869");
也不能百分之百避免被打入垃圾,垃圾邮件跟不同服务商的规则有关
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
2.需要开启支持
注意:在springboot种以后会遇到很多以Enable开头的注解,每个Enable代表一个组件的开启
在启动类加上下面注解,表示整个项目都可使用,也可以在单独的类上面加,表示只有该类支持
@EnableAsync
package cn.cdqf.springboot_001.async; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Component; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Component @Slf4j public class AsyncDemo { /** * 没有返回值的异步任务:例如发送短信邮箱 */ @Async public void asyncNoResult() throws InterruptedException { log.info("没有返回值的线程名称为:{}",Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(2); } /** * 有返回值的 :例如文件上传 需要拿到文件存储的位置/id */ @Async public Future<String> asyncResult ()throws InterruptedException{ log.info("有返回值的线程名称为:{}",Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(2); return new AsyncResult<>("异步任务返回成功"); } }
package cn.cdqf.springboot_001.async; import lombok.extern.slf4j.Slf4j; 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; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @RestController @RequestMapping("async") @Slf4j public class AsyncController { @Autowired private AsyncDemo asyncDemo; @GetMapping("noResult") public String noResult() throws InterruptedException { asyncDemo.asyncNoResult(); return "没有返回值的线程执行成功"; } @GetMapping("result") public String result() throws InterruptedException, ExecutionException { Future<String> stringFuture = asyncDemo.asyncResult(); return stringFuture.get(); } }
5.springboot线程池配置
使用线程池可以减少线程的创建和销毁,提高性能!!
package cn.cdqf.springboot_001.async; import lombok.extern.slf4j.Slf4j; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.lang.reflect.Method; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; /** * springboot线程池简单配置 */ @Slf4j @Configuration public class AsyncPoolConfig implements AsyncConfigurer { /** * @return:返回一个线程池,配置了这个线程池就会覆盖springboot自带的线程池 */ @Bean @Override public Executor getAsyncExecutor() { //创建线程池 ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); //设置核心线程数 默认值是1 threadPoolTaskExecutor.setCorePoolSize(10); //线程的最大数量,当核心线程使用完了,而且缓冲队列满了 就会创建其它线程 threadPoolTaskExecutor.setMaxPoolSize(15); //缓冲队列的个数 :队列作为一个缓冲的工具, //当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务 threadPoolTaskExecutor.setQueueCapacity(20); //非核心线程,如果空闲超过100秒就被回收 threadPoolTaskExecutor.setKeepAliveSeconds(100); //设置线程的前缀名称 threadPoolTaskExecutor.setThreadNamePrefix("cdqf_"); //用来设置线程池关闭的时候等待所有任务都完成(可以设置时间) threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); //设置上面的时间 threadPoolTaskExecutor.setAwaitTerminationSeconds(100); //拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝 /** * ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常 * ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常 * ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列 * ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务 */ threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //初始化线程池 threadPoolTaskExecutor.initialize(); return threadPoolTaskExecutor; } /** * @return:异步任务的异常处理 * 处理没有返回值的异步处理 * 有返回值的,会返回抛出的异常,自己去处理 */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new MyAsyncExceptionHandler(); } class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{ /** * @param throwable:异常信息 * @param method:抛出异常的异步方法 * @param objects:传递给线程异常处理的参数,(开发没用到) */ @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { log.info("异常:{},方法:{},参数:",throwable.getMessage(),method.getName(),objects); log.error(throwable.getMessage()); throwable.printStackTrace(); } } }package cn.cdqf.springboot_001.async; import lombok.extern.slf4j.Slf4j; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.lang.reflect.Method; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; /** * springboot线程池简单配置 */ @Slf4j @Configuration public class AsyncPoolConfig implements AsyncConfigurer { /** * @return:返回一个线程池,配置了这个线程池就会覆盖springboot自带的线程池 */ @Bean @Override public Executor getAsyncExecutor() { //创建线程池 ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); //设置核心线程数 默认值是1 threadPoolTaskExecutor.setCorePoolSize(10); //线程的最大数量,当核心线程使用完了,而且缓冲队列满了 就会创建其它线程 threadPoolTaskExecutor.setMaxPoolSize(15); //缓冲队列的个数 :队列作为一个缓冲的工具, //当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务 threadPoolTaskExecutor.setQueueCapacity(20); //非核心线程,如果空闲超过100秒就被回收 threadPoolTaskExecutor.setKeepAliveSeconds(100); //设置线程的前缀名称 threadPoolTaskExecutor.setThreadNamePrefix("cdqf_"); //用来设置线程池关闭的时候等待所有任务都完成(可以设置时间) threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true); //设置上面的时间 threadPoolTaskExecutor.setAwaitTerminationSeconds(100); //拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝 /** * ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常 * ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常 * ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列 * ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务 */ threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //初始化线程池 threadPoolTaskExecutor.initialize(); return threadPoolTaskExecutor; } /** * @return:异步任务的异常处理 * 处理没有返回值的异步处理 * 有返回值的,会返回抛出的异常,自己去处理 */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new MyAsyncExceptionHandler(); } class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler{ /** * @param throwable:异常信息 * @param method:抛出异常的异步方法 * @param objects:传递给线程异常处理的参数,(开发没用到) */ @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { log.info("异常:{},方法:{},参数:",throwable.getMessage(),method.getName(),objects); log.error(throwable.getMessage()); throwable.printStackTrace(); } } }
别忘了在使用线程的地方,指定线程池的名字,默认方法名小写
@Async("getAsyncExecutor")
package cn.cdqf.springboot_001.mail; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.MimeMessageHelper; public interface MailService { /** * 发送文本 * @param subject 主题 * @param content 内容 * @param toWho 需要发送的人 * @param ccPeoples 需要抄送的人 * @param bccPeoples 需要密送的人 * @param attachments 需要附带的附件 */ void sendSimpleTextMailActual(String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples,String[] attachments); /** * 发送Html * @param subject 主题 * @param content 内容 * @param toWho 需要发送的人 */ void sendHtmlMail(String subject,String content,String[] toWho); /** * 处理二进制邮件的基本信息,比如需要带附件的文本邮件、HTML文件、图片邮件、模板邮件等等 * @param mimeMessageHelper:二进制文件的包装类 * @param subject:邮件主题 * @param content:邮件内容 * @param toWho:收件人 * @param ccPeoples:抄送人 * @param bccPeoples:暗送人 * @param isHtml:是否是HTML文件,用于区分带附件的简单文本邮件和真正的HTML文件 * @return :返回这个过程中是否出现异常,当出现异常时会取消邮件的发送 */ boolean handleBasicInfo(MimeMessageHelper mimeMessageHelper, String subject, String content, String[] toWho, String[] ccPeoples, String[] bccPeoples, boolean isHtml); /** * 用于填充简单文本邮件的基本信息 * @param simpleMailMessage:文本邮件信息对象 * @param subject:邮件主题 * @param content:邮件内容 * @param toWho:收件人 * @param ccPeoples:抄送人 * @param bccPeoples:暗送人 */ void handleBasicInfo(SimpleMailMessage simpleMailMessage, String subject, String content, String[] toWho, String[] ccPeoples, String[] bccPeoples); /** * 发送html * @param subject:邮件主题 * @param content:邮件内容 * @param toWho:收件人 * @param mimeMessageHelper:二进制文件的包装类 */ void handleBasicInfo(MimeMessageHelper mimeMessageHelper,String subject, String content, String[] toWho); /** * 用于处理附件信息,附件需要 MimeMessage 对象 * @param mimeMessageHelper:处理附件的信息对象 * @param subject:邮件的主题,用于日志记录 * @param attachmentFilePaths:附件文件的路径,该路径要求可以定位到本机的一个资源 */ void handleAttachment(MimeMessageHelper mimeMessageHelper,String subject,String[] attachmentFilePaths); }
实现类
package cn.cdqf.springboot_001.mail; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.io.File; /** * 发送邮件功能具体实现类 * @author Xuan * @date 2019/8/4 11:01 */ @Service @Slf4j public class MailServiceImpl implements MailService { //默认编码 public static final String DEFAULT_ENCODING = "UTF-8"; //本身邮件的发送者,来自邮件配置 @Value("${spring.mail.username}") private String userName; @Value("${spring.mail.nickname}") private String nickname; //模板引擎解析对象,用于解析模板 @Autowired(required = false) private JavaMailSender mailSender; @Override @Async("getAsyncExecutor") public void sendSimpleTextMailActual(String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples,String[] attachments){ //检验参数:邮件主题、收件人、邮件内容必须不为空才能够保证基本的逻辑执行 if(subject == null||toWho == null||toWho.length == 0||content == null){ log.error("邮件-> {} 无法继续执行,因为缺少基本的参数:邮件主题、收件人、邮件内容",subject); throw new RuntimeException("模板邮件无法继续发送,因为缺少必要的参数!"); } log.info("开始发送简单文本邮件:主题->{},收件人->{},抄送人->{},密送人->{},附件->{}",subject,toWho,ccPeoples,bccPeoples,attachments); //附件处理,需要处理附件时,需要使用二进制信息,使用 MimeMessage 类来进行处理 if(attachments != null&&attachments.length > 0){ try{ //附件处理需要进行二进制传输 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,DEFAULT_ENCODING); //设置邮件的基本信息:这些函数都会在后面列出来 boolean continueProcess = handleBasicInfo(helper,subject,content,toWho,ccPeoples,bccPeoples,false); //如果处理基本信息出现错误 if(!continueProcess){ log.error("邮件基本信息出错: 主题->{}",subject); return; } //处理附件 handleAttachment(helper,subject,attachments); //发送该邮件 mailSender.send(mimeMessage); log.info("发送邮件成功: 主题->{}",subject); } catch (MessagingException e) { e.printStackTrace(); log.error("发送邮件失败: 主题->{}",subject); } }else{ //创建一个简单邮件信息对象 SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); //设置邮件的基本信息 handleBasicInfo(simpleMailMessage,subject,content,toWho,ccPeoples,bccPeoples); //发送邮件 mailSender.send(simpleMailMessage); log.info("发送邮件成功: 主题->{}",subject,toWho,ccPeoples,bccPeoples,attachments); } } @Async("getAsyncExecutor") @Override public void sendHtmlMail(String subject, String content, String[] toWho) { //检验参数:邮件主题、收件人、邮件内容必须不为空才能够保证基本的逻辑执行 if(subject == null||toWho == null||toWho.length == 0||content == null){ log.error("邮件-> {} 无法继续执行,因为缺少基本的参数:邮件主题、收件人、邮件内容",subject); throw new RuntimeException("模板邮件无法继续发送,因为缺少必要的参数!"); } log.info("开始发送Html邮件:主题->{},收件人->{}",subject,toWho); //html MimeMessage mimeMessage = mailSender.createMimeMessage(); try { MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,DEFAULT_ENCODING); //设置邮件的基本信息 handleBasicInfo(helper,subject,content,toWho); //发送邮件 mailSender.send(mimeMessage); log.info("html邮件发送成功"); } catch (MessagingException e) { log.error("发送邮件出错->{}",subject); } log.info("发送邮件成功: 主题->{}",subject,toWho); } @Override public boolean handleBasicInfo(MimeMessageHelper mimeMessageHelper,String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples,boolean isHtml){ try{ //设置必要的邮件元素 //设置发件人 mimeMessageHelper.setFrom(nickname+'<'+userName+'>'); //设置邮件的主题 mimeMessageHelper.setSubject(subject); //设置邮件的内容,区别是否是HTML邮件 mimeMessageHelper.setText(content,isHtml); //设置邮件的收件人 mimeMessageHelper.setTo(toWho); //设置非必要的邮件元素,在使用helper进行封装时,这些数据都不能够为空 if(ccPeoples != null) //设置邮件的抄送人:MimeMessageHelper # Assert.notNull(cc, "Cc address array must not be null"); mimeMessageHelper.setCc(ccPeoples); if(bccPeoples != null) //设置邮件的密送人:MimeMessageHelper # Assert.notNull(bcc, "Bcc address array must not be null"); mimeMessageHelper.setBcc(bccPeoples); return true; }catch(MessagingException e){ e.printStackTrace(); log.error("邮件基本信息出错->{}",subject); } return false; } @Override public void handleBasicInfo(SimpleMailMessage simpleMailMessage,String subject,String content,String[] toWho,String[] ccPeoples,String[] bccPeoples){ //设置发件人 simpleMailMessage.setFrom(nickname+'<'+userName+'>'); //设置邮件的主题 simpleMailMessage.setSubject(subject); //设置邮件的内容 simpleMailMessage.setText(content); //设置邮件的收件人 simpleMailMessage.setTo(toWho); //设置邮件的抄送人 simpleMailMessage.setCc(ccPeoples); //设置邮件的密送人 simpleMailMessage.setBcc(bccPeoples); } @Override public void handleBasicInfo(MimeMessageHelper mimeMessageHelper,String subject,String content,String[] toWho){ try { //设置发件人 mimeMessageHelper.setFrom(nickname+'<'+userName+'>'); //设置邮件的主题 mimeMessageHelper.setSubject(subject); //设置邮件的内容 mimeMessageHelper.setText(content,true); //设置邮件的收件人 mimeMessageHelper.setTo(toWho); } catch (MessagingException e) { log.error("html邮件基本信息出错->{}",subject); } } @Override public void handleAttachment(MimeMessageHelper mimeMessageHelper,String subject,String[] attachmentFilePaths){ //判断是否需要处理邮件的附件 if(attachmentFilePaths != null&&attachmentFilePaths.length > 0) { FileSystemResource resource; String fileName; //循环处理邮件的附件 for (String attachmentFilePath : attachmentFilePaths) { //获取该路径所对应的文件资源对象 resource = new FileSystemResource(new File(attachmentFilePath)); //判断该资源是否存在,当不存在时仅仅会打印一条警告日志,不会中断处理程序。 // 也就是说在附件出现异常的情况下,邮件是可以正常发送的,所以请确定你发送的邮件附件在本机存在 if (!resource.exists()) { log.warn("邮件->{} 的附件->{} 不存在!", subject, attachmentFilePath); //开启下一个资源的处理 continue; } //获取资源的名称 fileName = resource.getFilename(); try { //添加附件 mimeMessageHelper.addAttachment(fileName, resource); } catch (MessagingException e) { e.printStackTrace(); log.error("邮件->{} 添加附件->{} 出现异常->{}", subject, attachmentFilePath, e.getMessage()); } } } } }
测试
package cn.cdqf.springboot_001.mail; import org.apache.commons.lang3.ArrayUtils; 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; import java.util.Arrays; @RestController @RequestMapping("mail") public class MailController { @Autowired private MailService mailService; @GetMapping("mailText") public String mailText(){ String[] add = ArrayUtils.add(new String[0], "383657231@qq.com"); mailService.sendSimpleTextMailActual("关于春节放假须知","最终定于1月17日--1月30日为2019春节放假时间,请各部门做好放假相关事宜" ,add ,ArrayUtils.add(new String[0],"260855393@qq.com"), null,ArrayUtils.add(new String[0],"F://timg.jpg")); return "mail text success"; } }
upload.path=F://upload// upload.filePrefix=${random.uuid}
package cn.cdqf.springboot_001.file; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; @Component @PropertySource({"classpath:file.properties"}) @ConfigurationProperties(prefix = "upload") @Data public class FileProperties { private String path; private String filePrefix; public String getFilePrefix(){ return filePrefix.replace("-",""); } }
package cn.cdqf.springboot_001.file; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; @RestController @RequestMapping("file") public class FileController { @Autowired private FileProperties fileProperties; @PostMapping("upload") public String upload(@RequestParam("images")MultipartFile[] multipartFiles) throws IOException { //文件名字前缀 String filePrefix = fileProperties.getFilePrefix(); for (MultipartFile multipartFile : multipartFiles) { //文件名称 String originalFilename = multipartFile.getOriginalFilename(); //获得后缀名 String suffixName = originalFilename.substring(originalFilename.lastIndexOf(".")); File file = new File(fileProperties.getPath(), filePrefix + suffixName); multipartFile.transferTo(file); } return "文件上传成功"; } }
暂时使用:为了熟悉文件上传,与多线程结合,以后项目会以这个为基础修改
package cn.cdqf.springboot_001.file; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @Component @Slf4j public class FileUtils { @Autowired private FileProperties fileProperties; /** * @param multipartFile 上传的文件 * @return 返回文件上传后的名字(项目中使用公共返回值,更方便判断code) */ @Async("getAsyncExecutor") public Future<String> upload(MultipartFile multipartFile){ //项目中需要提取字符串为常量 if(multipartFile==null)return new AsyncResult<>("上传的文件为null"); File file = new File(fileProperties.getPath()); //文件夹不存在就创建 if(!file.exists())file.mkdirs(); String fileName = getFileName(multipartFile.getOriginalFilename()); File uploadFile = new File(file, fileName); try { multipartFile.transferTo(uploadFile); return new AsyncResult<>("文件上传成功"); } catch (IOException e) { e.printStackTrace(); log.error("文件上传失败:{}",e.getMessage()); return new AsyncResult<>("文件上传失败"); } } public Future<String[]> upload(MultipartFile[] multipartFiles) throws ExecutionException, InterruptedException { if(multipartFiles==null)return new AsyncResult<>(null); String[] strings = new String[multipartFiles.length]; int i = 0; for (MultipartFile multipartFile : multipartFiles) { Future<String> upload = upload(multipartFile); strings[i++] = upload.get(); } return new AsyncResult<>(strings); } private String getFileName(String fileName){ StringBuilder finalFileName = new StringBuilder(fileProperties.getFilePrefix()); finalFileName.append(fileName.substring(fileName.lastIndexOf("."))); return finalFileName.toString(); } }
#文件大小配置
#单个文件的大小
spring.servlet.multipart.max-file-size=5MB
#单次总文件大小
spring.servlet.multipart.max-request-size=20MB
7.注意事项
basicErrorController
项目中在写完整的自定义异常整合+邮箱发送,跟springmvc几乎相同,这儿仅做测试使用
package com.cdqf.springboot_02.exception; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice @Slf4j public class MyExceptionHandler { @ExceptionHandler(Exception.class) public String exception(Exception e){ log.info("拦截到项目出现的不可控异常:{}",e.getMessage()); //发邮箱等 return "异常拦截"; } }
package com.cdqf.springboot_02.filter; import com.google.common.collect.Lists; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.ArrayList; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean filterRegistrationBean(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); Log2Filter log2Filter = new Log2Filter(); filterFilterRegistrationBean.setFilter(log2Filter); filterFilterRegistrationBean.addUrlPatterns("/*"); filterFilterRegistrationBean.setOrder(3); // filterFilterRegistrationBean.setEnabled(true); return filterFilterRegistrationBean; } @Bean public FilterRegistrationBean filterRegistrationBean2(){ FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>(); LogFilter logFilter = new LogFilter(); filterFilterRegistrationBean.setFilter(logFilter); filterFilterRegistrationBean.addUrlPatterns("/*"); // filterFilterRegistrationBean.setEnabled(true); //注册多个filter 调整一下优先级 filterFilterRegistrationBean.setOrder(4); return filterFilterRegistrationBean; } }
1.filter先执行,可以整合任意框架,无法获取要执行的方法
2.拦截器次执行,依赖框架存在,可以获得要执行的方法,没法获得参数值
package com.cdqf.springboot_02.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j @Component public class MyInterceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("拦截器一执行了...."); if(handler instanceof HandlerMethod){ HandlerMethod handlerMethod = (HandlerMethod) handler; log.info("获得当前要执行的方法:{}",handlerMethod.getMethod().getName()); log.info("获得当前执行的类为:{}",handlerMethod.getBean().getClass()); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("拦截器一,有异常就不会执行的方法:postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("拦截器一,不管有没有异常都会执行的方法:afterCompletion"); } }
拦截器二
package com.cdqf.springboot_02.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j @Component public class MyInterceptor2 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("拦截器二执行了...."); if(handler instanceof HandlerMethod){ HandlerMethod handlerMethod = (HandlerMethod) handler; log.info("获得当前要执行的方法:{}",handlerMethod.getMethod().getName()); log.info("获得当前执行的类为:{}",handlerMethod.getBean().getClass()); } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("有异常就不会执行的方法:postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("不管有没有异常都会执行的方法:afterCompletion"); } }
配置拦截器
package com.cdqf.springboot_02.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @Slf4j public class InterceptorConfig implements WebMvcConfigurer { @Autowired private MyInterceptor1 myInterceptor1; @Autowired private MyInterceptor2 myInterceptor2; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(myInterceptor1).order(1).addPathPatterns("/**"); registry.addInterceptor(myInterceptor2).order(2).addPathPatterns("/**"); } }
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
package com.cdqf.springboot_02.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Arrays; @Aspect @Component @Slf4j public class MyAspect { @Pointcut("execution(* com.cdqf.springboot_02.aspect.*.*(..))") public void pointCut(){} @Around("pointCut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { MethodSignature signature = (MethodSignature)proceedingJoinPoint.getSignature(); Method method = signature.getMethod(); Parameter[] parameters = method.getParameters(); log.info("获得参数的类型为:{},名称为:{}",parameters[0].getType(),parameters[0].getName()); log.info("获得controller方法执行的参数为:{}",proceedingJoinPoint.getArgs()); Object proceed = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); return proceed; } }
执行顺序
2.拦截器(能获得要执行的方法,及类,无法获得方法的参数值) 依赖springmvc
3.切面(均可获得)依赖springmvc
@EnableScheduling
创建测试类
package com.cdqf.springboot_02.task; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * 构建执行定时任务 * TODO */ @Component @Slf4j public class ScheduledTask { private int fixedDelayCount = 1; private int fixedRateCount = 1; private int initialDelayCount = 1; private int cronCount = 1; @Scheduled(fixedDelay = 5000) //fixedDelay = 5000表示当前方法执行完毕5000ms后,Spring scheduling会再次调用该方法 public void testFixDelay() { log.info("===fixedDelay: 第{}次执行方法", fixedDelayCount++); } @Scheduled(fixedRate = 5000) //fixedRate = 5000表示当前方法开始执行5000ms后,Spring scheduling会再次调用该方法 public void testFixedRate() { log.info("===fixedRate: 第{}次执行方法", fixedRateCount++); } @Scheduled(initialDelay = 1000, fixedRate = 5000) //initialDelay = 1000表示延迟1000ms执行第一次任务 public void testInitialDelay() { log.info("===initialDelay: 第{}次执行方法", initialDelayCount++); } @Scheduled(cron = "0 0/1 * * * ?") //cron接受cron表达式,根据cron表达式确定定时规则 public void testCron() { log.info("===initialDelay: 第{}次执行方法", cronCount++); } }
cron表达式
0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点
"0 0 12 * * ?" 每天中午12点触发
"0 15 10 ? * *" 每天上午10:15触发
"0 15 10 * * ?" 每天上午10:15触发
"0 15 10 * * ? *" 每天上午10:15触发
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发
"0 15 10 15 * ?" 每月15日上午10:15触发
"0 15 10 L * ?" 每月最后一日的上午10:15触发
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发
看前面打印是一个线程在执行定时任务,多线程执行
package com.cdqf.springboot_02.task; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; @Configuration public class SchedulerThreadPoolConfig { @Bean public TaskScheduler taskScheduler(){ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(10); threadPoolTaskScheduler.setThreadNamePrefix("cdq_scheduler_"); return threadPoolTaskScheduler; } }
实体类常用注解
package com.cdqf.springboot_03.json; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor @Builder //@JsonIgnoreProperties({"username",""}) public class Student implements Serializable { @JsonIgnore //转换为json字符串的时候 忽略该属性 private String username; @JsonProperty("Age") private int age; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date birthday; }
jackson配置类
package com.cdqf.springboot_02.json; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.IOException; @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper(){ ObjectMapper objectMapper = new ObjectMapper(); //通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化 // Include.Include.ALWAYS 默认 // Include.NON_DEFAULT 属性为默认值不序列化 // Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的。这样对移动端会更省流量 // Include.NON_NULL 属性为NULL 不序列化 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 允许出现特殊字符和转义符 objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); // 允许出现单引号 objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); //字段保留,将null值转为"" objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() { @Override public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeString(""); } }); return objectMapper; } }
也可以在properties中配置
spring.jackson.date-format= 配置日期序列化和反序列化格式, yyyy-MM-dd HH:mm:ss.
spring.jackson.default-property-inclusion= 控制序列化期间包含的属性。配置了Jackson的JsonInclude.Include枚举中的一个值,若配置一般配置non_null表示序列化时忽略属性为null的值,always, non_null, non_absent, non_default, non_empty
spring.jackson.deserialization.*= Jackson反序列化的开关,取值true, false
spring.jackson.generator.*= 开启关闭jackson的生成器,取值true, false
spring.jackson.joda-date-time-format= # 配置日期格式序列化为string的格式,不配置默认使用date-format配置
spring.jackson.locale= 本地化配置
spring.jackson.mapper.*= 开启或者关闭jackson,取值true, false
spring.jackson.parser.*= 开启关闭jsonson的解析器 ,取值true, false
spring.jackson.property-naming-strategy=配置json的key值和实体属性名称之间的转换关系,值一般为PropertyNamingStrategy类中常数或者实现PropertyNamingStrategy子类的全限定名
spring.jackson.serialization.*= Jackson序列化的开关,取值true, false
spring.jackson.time-zone= 格式化日期时使用的时区。例如,“America / Los_Angeles”或“GMT + 10”
spring.jackson.visibility.*= 修改实体类属性域的可见性
使用
package com.cdqf.springboot_02.json; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.Date; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class JacksonConfigTest { @Autowired private ObjectMapper objectMapper; @Test public void test() throws JsonProcessingException { Student student = Student.builder().age(10).name("张三").birthday(new Date()).build(); String s = objectMapper.writeValueAsString(student); log.info("objectMapper转为json字符串:{}",s); Student student1 = objectMapper.readValue(s, Student.class); log.info("反序列化获得json对象,{}",student1); ArrayList<Student> students = Lists.newArrayList(student); TypeFactory t = TypeFactory.defaultInstance(); // 指定容器结构和类型(这里是ArrayList和clazz) List<Student> list = objectMapper.readValue(objectMapper.writeValueAsString(students), t.constructCollectionType(ArrayList.class,Student.class)); log.info("获得反序列化的集合为:{}",list); } }
# 这4个参数key里不带druid也可以,即可以还用上面的这个4个参数 spring.datasource.druid.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC spring.datasource.druid.username=root spring.datasource.druid.password=123456 spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver # 初始化时建立物理连接的个数 spring.datasource.druid.initial-size=5 # 最大连接池数量 spring.datasource.druid.max-active=30 # 最小连接池数量 spring.datasource.druid.min-idle=5 # 获取连接时最大等待时间,单位毫秒 spring.datasource.druid.max-wait=60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 spring.datasource.druid.time-between-eviction-runs-millis=60000 # 连接保持空闲而不被驱逐的最小时间 spring.datasource.druid.min-evictable-idle-time-millis=300000 # 用来检测连接是否有效的sql,要求是一个查询语句 spring.datasource.druid.validation-query=SELECT 1 FROM DUAL # 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 spring.datasource.druid.test-while-idle=true # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 spring.datasource.druid.test-on-borrow=false # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 spring.datasource.druid.test-on-return=false # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 spring.datasource.druid.pool-prepared-statements=true # 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。 spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计 spring.datasource.druid.filters=stat,wall # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 # 合并多个DruidDataSource的监控数据 spring.datasource.druid.use-global-data-source-stat=true # druid连接池监控 spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=123 # 排除一些静态资源,以提高效率 spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*