前言
说道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启动后我运行啦");
}
}
实现
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 !!");
}
}
全局异常处理
如果不做全局异常处理,当我们的接口放生异常时,会直接报错“# 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";
}
}
这种给用户的体验比较差,为了优化用户体验,需要做全局异常处理,针对不同异常做出不同的提示:
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;
}
}
定时任务
如何开启,在启动类或者配置类上加注解如下所示:
创建定时任务类:
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));
}
}
执行结果如下:
异步任务
确保已引入此依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如何开启,在启动类或者配置类上加注解如下所示:
编写异步类:
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>
生成的日志目录如下所示:
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");
}
}