springboot的一些操作,比如异步编程,定时任务,发邮件,文件上传等等

6.java程序读取配置文件的内容

1.@Value

在配置文件添加

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;
    }

2.随机数配置

# 随机数
# 随机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将上面学生的配置剪切到里面

然后在StudentProperties类种加上

@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秒以内)

8.springboot配置日志

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>

9.springboot发送邮箱

<!-- 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");

也不能百分之百避免被打入垃圾,垃圾邮件跟不同服务商的规则有关

10.springboot异步编程(邮件发送工具)

1.需要导入web支持

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.需要开启支持

注意:在springboot种以后会遇到很多以Enable开头的注解,每个Enable代表一个组件的开启

在启动类加上下面注解,表示整个项目都可使用,也可以在单独的类上面加,表示只有该类支持

@EnableAsync

3.编写异步任务

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<>("异步任务返回成功");

    }
}

4.测试

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线程池配置

使用线程池可以减少线程的创建和销毁,提高性能!!

从刚才的线程测试可以看出,每次启动线程,springboot都会从新创建一个线程,线程不重用,显然效率太低,这是因为spring boot自带线程池过于简单,所以在开发中,都会自己配置线程池的属性

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")

6.使用线程编写邮件工具类

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";
    }
}

11.springboot文件上传

1.文件配置file.properties

upload.path=F://upload//
upload.filePrefix=${random.uuid}

2.读取配置文件类

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("-","");
    }
}

3.controller编写

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 "文件上传成功";
    }
}

4.多线程编写工具类

暂时使用:为了熟悉文件上传,与多线程结合,以后项目会以这个为基础修改

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();
    }
}

6.文件上传大小配置

#文件大小配置
#单个文件的大小
spring.servlet.multipart.max-file-size=5MB
#单次总文件大小
spring.servlet.multipart.max-request-size=20MB

7.注意事项

攻击:html结尾,js结尾

 

 

12.springboot异常处理机制

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 "异常拦截";
        }
    }

11springboot整合filter

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.拦截器次执行,依赖框架存在,可以获得要执行的方法,没法获得参数值

12.springboot整合拦截器

拦截器一

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("/**");
    }
}

13.springboot切面编程

导入依赖

 <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;
    }
}

执行顺序

1.过滤器(可兼容性最高,无法获得执行的方法及类)

2.拦截器(能获得要执行的方法,及类,无法获得方法的参数值) 依赖springmvc

3.切面(均可获得)依赖springmvc

 

 

 

 

14.springboot定时任务

在启动类加注解

@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;
    }
}

15.springboot+jackson

springboot自带的json工具为,jackson,在第一个项目使用了fastjson希望大家下来自己百度,如何替换为fastjson,该课程使用默认的jackson

实体类常用注解

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);
    }
}

16.springboot+druid

druid配置

# 这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/*
原文地址:https://www.cnblogs.com/jikeyi/p/13412893.html