一文带你玩转SpringBoot常用功能

5,285 阅读6分钟

前言

说道SpringBoot,我想大家都已经非常熟悉了,其中两块比较核心的部分是IOC(控制反转) 和 AOP(面向切面编程)。SpringBoot的相关插件种类繁多,扩展能力非常强,这个优势得益于Spring拥有强大的包容能力,让很多第三方应用能够轻松投入Spring。SpringBoot框架上手简单,相信广大Java开发者们经常在用这个框架,那么SpringBoot的一些常用功能你是否都用到了呢,下面我会为大家细细道来。

自定义拦截器

为了方便我们一般情况会用HandlerInterceptor接口的实现类HandlerInterceptorAdapter类。
使用场景:权限认证、日志、统计等。 第一步、创建拦截器

package com.wxd.interceptor;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author wxd
 * @version V1.0
 * @description BaseInterceptor
 * @date 2022/10/11 10:46
 **/
public class BaseInterceptor extends HandlerInterceptorAdapter {
    /**
     * 目标方法执行前执行
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return super.preHandle(request, response, handler);
    }

    /**
     * 目标方法执行后执行
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }

    /**
     * 请求完成时执行
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
    }
}

第二步、注册拦截器

package com.wxd.config;

import com.wxd.interceptor.BaseInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

/**
 * @author wxd
 * @version V1.0
 * @description WebAuthConfig
 * @date 2022/10/11 11:11
 **/
@Configuration
public class WebAuthConfig extends WebMvcConfigurationSupport {

    @Bean
    public BaseInterceptor getBaseInterceptor() {
        return new BaseInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new BaseInterceptor());
    }
}

启动时运行

SpringBoot提供了两种实现方式:

ApplicationRunner

package com.wxd.component;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

/**
 * @author wxd
 * @version V1.0
 * @description TestRunner
 * @date 2022/10/11 11:27
 **/
@Component
@Slf4j
public class TestRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("SpringBoot启动后我运行啦");
    }
}

image.png 实现ApplicationRunner接口,重写run方法,在该方法中实现自己定制化需求。
如果项目中有多个类实现了ApplicationRunner接口,可以使用@Order(n)注解,n的值越小越先执行。当然也可以通过@Priority注解指定顺序。

CommandLineRunner

package com.wxd.component;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

/**
 * @author wxd
 * @version V1.0
 * @description ApplicationStartupRunner
 * @date 2022/10/11 11:30
 **/
@Component
@Slf4j
public class ApplicationStartupRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("ApplicationStartupRunner run method Started !!");
    }
}

image.png

全局异常处理

如果不做全局异常处理,当我们的接口放生异常时,会直接报错“# Whitelabel Error Page”:

package com.wxd.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author wxd
 * @version V1.0
 * @description TestExceptionController
 * @date 2022/10/12 14:12
 **/
@RequestMapping("/error")
@RestController
public class TestExceptionController {

    @GetMapping("/test")
    public String test() {
        int a = 10 / 0;
        return "ok";
    }
}

image.png

这种给用户的体验比较差,为了优化用户体验,需要做全局异常处理,针对不同异常做出不同的提示:

package com.wxd.advice;

import com.wxd.entity.ActionResult;
import com.wxd.enums.ResultCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @version: V1.0
 * @author: wxd
 * @description: 全局异常处理以及返回值的统一封装
 * @Date 2022/7/26 16:24
 */
@RestControllerAdvice(value = "com.wxd.controller")
@Slf4j
public class ResponseAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    /**
     * 统一包装
     *
     * @param o
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        /**
         * String、ActionResult不需要再包一层(不想包一层ActionResult对象的可以在这里把这个对象过滤掉)
         */
        if (o instanceof String || o instanceof ActionResult) {
            return o;
        }
        return ActionResult.defaultOk(o);
    }

    /**
     * 系统内部异常捕获
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public Object exceptionHandler(Exception e) {
        log.error("系统内部异常,异常信息", e);
        return ActionResult.defaultFail(ResultCodeEnum.RC500);
    }


    /**
     * 忽略参数异常处理器;触发例子:带有@RequestParam注解的参数未给参数
     *
     * @param e 忽略参数异常
     * @return ResponseResult
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Object parameterMissingExceptionHandler(MissingServletRequestParameterException e) {
        log.error("缺少Servlet请求参数异常", e);
        return ActionResult.defaultFail(ResultCodeEnum.RC1004);
    }

    /**
     * 缺少请求体异常处理器;触发例子:不给请求体参数
     *
     * @param e 缺少请求体异常
     * @return ResponseResult
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Object parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
        log.error("参数请求体异常", e);
        return ActionResult.defaultFail(ResultCodeEnum.RC1005);
    }


    /**
     * 统一处理请求参数绑定错误(实体对象传参);
     *
     * @param e BindException
     * @return ResponseResult
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public Object validExceptionHandler(BindException e) {
        log.error("方法参数绑定错误(实体对象传参)", e);
        return ActionResult.defaultFail(ResultCodeEnum.RC1006);
    }

    /**
     * 统一处理请求参数绑定错误(实体对象请求体传参);
     *
     * @param e 参数验证异常
     * @return ResponseResult
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Object parameterExceptionHandler(MethodArgumentNotValidException e) {
        log.error("方法参数无效异常(实体对象请求体传参)", e);
        return ActionResult.defaultFail(ResultCodeEnum.RC1007);
    }

}

Bean初始化前后

当你想在某个Bean初始化前后做一些操作的时候,就需要实现这个接口BeanPostProcessor,此接口有两个方法,分别是:

  • postProcessBeforeInitialization
  • postProcessAfterInitialization
package com.wxd.config;

import com.wxd.entity.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author wxd
 * @version V1.0
 * @description BeanPostProcessor
 * @date 2022/10/12 15:14
 **/
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {

    /**
     * 该在初始化方法之前调用
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }

    /**
     * 该方法再初始化方法之后调用
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User) {
            //修改注册时间
            ((User) bean).setRegisterDate(new Date());
        }
        return bean;
    }
}

定时任务

如何开启,在启动类或者配置类上加注解如下所示: image.png 创建定时任务类:

package com.wxd.schedule;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @author wxd
 * @version V1.0
 * @description CustomSchedule
 * @date 2022/10/12 15:24
 **/
@Slf4j
@Component
public class CustomSchedule {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    /**
     * 每隔5秒执行一次
     */
    @Scheduled(cron = "0/5 * * * * ? ")
    public void schedule() {
        log.info("schedule -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 上一次开始执行时间点之后3000毫秒再执行
     */
    @Scheduled(fixedRate = 3000)
    public void schedule01() {
        log.info("schedule01 -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 上一次执行完毕时间点之后3s再执行
     */
    @Scheduled(fixedDelay = 3000)
    public void schedule02() {
        log.info("schedule02 -> {}", LocalDateTime.now().format(formatter));
    }

    /**
     * 第一次延迟2s之后执行, 之后按照每3s执行一次
     */
    @Scheduled(initialDelay = 2000, fixedRate = 3000)
    public void schedule03() {
        log.info("schedule03 -> {}", LocalDateTime.now().format(formatter));
    }

}

执行结果如下: image.png

异步任务

确保已引入此依赖:

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

如何开启,在启动类或者配置类上加注解如下所示: image.png 编写异步类:

package com.wxd.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;

/**
 * @author wxd
 * @version V1.0
 * @description CustonSync
 * @date 2022/10/12 15:33
 **/
@Slf4j
@Component
public class CustomSync {
    /**
     * 无返回值异步线程
     *
     * @throws InterruptedException
     */
    @Async
    public void asyncProcess() throws InterruptedException {
        log.info("async process task, current thread name -> {}", Thread.currentThread().getName());
        //模拟处理任务花费时间
        TimeUnit.SECONDS.sleep(1);
    }

    /**
     * 有返回值异步线程
     *
     * @return
     * @throws InterruptedException
     */
    @Async
    public Future<Integer> asyncProcessHasReturn() throws InterruptedException {
        log.info("async process task (has return), current thread name -> {}", Thread.currentThread().getName());
        //模拟处理任务花费时间
        TimeUnit.SECONDS.sleep(1);
        return new AsyncResult<Integer>(100);
    }

}

异步线程池的配置小编就省略不写了。。。

logback日志配置

在resorces目录下创建文件:logback-spring.xml,输入以下内容: 按照如下配置后,会每天创建一个日志文件,分为info和error,方便我们后期排查问题

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>generate_code</contextName>
    <property name="log.path" value="log" />
    <property name="log.maxHistory" value="15" />
    <property name="log.colorPattern" value="%magenta(%d{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %yellow(%thread) %green(%logger) %msg%n"/>
    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %thread %logger %msg%n"/>

    <!--输出到控制台-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.colorPattern}</pattern>
            <!-- 设置字符集 -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件-->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/info/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <MaxHistory>${log.maxHistory}</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/error.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <root level="debug">
        <appender-ref ref="console" />
    </root>

    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>

生成的日志目录如下所示:

image.png

Spring容器获取Bean

在我们的日常开发中,经常要从Spring容器中获取Bean,实现BeanFactoryAware此接口即可轻松从Spring容器中获取Bean。
实现BeanFactoryAware接口,然后重写setBeanFactory方法,就能从该方法中获取到spring容器对象。

package com.wxd.service;

import com.wxd.config.WebConfig;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;

/**
 * @author wxd
 * @version V1.0
 * @description CustomBeanFactoryAware
 * @date 2022/10/12 16:12
 **/
public class CustomService implements BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void test() {
        WebConfig webConfig = (WebConfig) beanFactory.getBean("webConfig");
    }
}

实现ApplicationContextAware接口,然后重写setApplicationContext方法,也能从该方法中获取到spring容器对象:

package com.wxd.service;

import com.wxd.config.WebConfig;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

/**
 * @author wxd
 * @version V1.0
 * @description CustomService2
 * @date 2022/10/12 16:16
 **/
@Service
public class CustomService2 implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void test() {
        WebConfig webConfig = (WebConfig) applicationContext.getBean("webConfig");
    }
}