• 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/*
  • 相关阅读:
    hdu 1028 Ignatius and the Princess III (n的划分)
    CodeForces
    poj 3254 Corn Fields (状压DP入门)
    HYSBZ 1040 骑士 (基环外向树DP)
    PAT 1071 Speech Patterns (25)
    PAT 1077 Kuchiguse (20)
    PAT 1043 Is It a Binary Search Tree (25)
    PAT 1053 Path of Equal Weight (30)
    c++ 常用标准库
    常见数学问题
  • 原文地址:https://www.cnblogs.com/jikeyi/p/13412893.html
Copyright © 2020-2023  润新知