• 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/*
  • 相关阅读:
    2019.9.10 IEnumerable
    2019.9.02 按位或,按位与, 按位异或
    2019.9.01 五大基本原则
    2019.9.01 运算符重载
    2019.9.01 封装、继承、多态
    2019.8.22 1.属性
    2019.8.22 1.封装
    2019.8.22 1.隐式转换&显示转换
    2019.8.21 Class & InterFace &abstract& 属性
    2019.8.20 1.C#中this.關鍵字的應用 2.枚舉類的定義和簡單調用 3.struct(結構體)與Class(類)的定義與區別
  • 原文地址:https://www.cnblogs.com/jikeyi/p/13412893.html
Copyright © 2020-2023  润新知