在Spring Boot中,配置类是一种特殊的类,用于定义和配置Spring应用程序的各种组件、服务和属性。这些配置类通常使用Java注解来声明,并且可以通过Spring的依赖注入机制来管理和使用。
Spring 容器初始化时会加载被@Component、@Service、@Repository、@Controller等注解标识的类,除了注解标识的类,Spring容器还会加载配置类中定义的Bean。配置类通常使用
@Configuration注解标识,它们中定义的方法会被Spring容器识别为Bean的创建方法。
本篇主要讲 Springboot 配置类一些常见的用法,按照 SpringBoot 加载顺序配置监听器,过滤器,拦截器,定时器以及读取自定义配置文件。
定义配置类
package com.shore.confittestdemo.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
}
监听器
`ServletContextListener接口
主要用于监听ServletContext对象的创建和销毁事件。它关注的是Web应用的启动和关闭,以及ServletContext对象的状态变化。
创建一个监听器类,实现ServletContextListener接口
package com.shore.configdemo.config.listener;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
sce.getServletContext().setAttribute("myApp", "config-demo");
System.out.println("MyServletContextListener:监听器启动");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println(sce.getServletContext().getAttribute("myApp"));
System.out.println("MyServletContextListener:监听器销毁");
}
}
注解 @WebListener 标记为一个监听器类(没啥用)
@Bean
public ServletContextListener servletContextListener() {
return new MyServletContextListener();
}
系统启动时上下文添加 app 信息,系统退出时,获取上下文的 app 信息
ApplicationListener 接口
主要用于监听Spring应用生命周期中的事件,如应用启动、上下文刷新、自定义事件等。它关注的是Spring容器内部的事件。
监听上下文刷新事件
创建一个监听器类,实现 ApplicationListener 接口,指定要监听的事件类型,ContextRefreshedEvent 或者 ContextClosedEvent 或者其他自定义类型的事件,重写 onApplicationEvent 方法,编写监听到事件后需要执行的逻辑。
package com.shore.configdemo.config.listener;
import jakarta.servlet.annotation.WebListener;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@WebListener
//@Component
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("MyApplicationListener: 监听到上下文刷新" + event);
}
@Override
public boolean supportsAsyncExecution() {
return ApplicationListener.super.supportsAsyncExecution();
}
}
配置类中注册 监听器
@Bean
public MyApplicationListener listener() {
return new MyApplicationListener();
}
项目启动创建上下文时被 ServletContextListener 监听到,更新上下文信息,然后被 ApplicationListener 监听器捕获到。
监听上下文刷新和上下文关闭事件
有时候我们希望在一个监听器中监听多个事件,而不是每个事件增加一个监听器类,这时我们可以采用在自定义方法上增加注解 @EventListener,指定要监听的事件类型,一个类中可以有多个注解监听多个事件。
package com.shore.configdemo.config.listener;
import jakarta.servlet.annotation.WebListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
@WebListener
public class MyMultiEventListener {
@EventListener
public void handleContextRefreshEvent(ContextRefreshedEvent event) {
System.out.println("MyMultiEventListener.handleContextRefreshEvent:监听到上下文刷新");
}
@EventListener
public void handleContextClosedEvent(ContextClosedEvent event) {
System.out.println("MyMultiEventListener.handleContextClosedEvent:监听到上下文关闭");
}
}
监听多个自定义事件
实际业务中,我们可能需要监听一些其他的业务,比如类初始化等,可以通过自定义事件来完成。
创建一个类继承 ApplicationEvent 定义一个事件
package com.shore.configdemo.config.event;
import org.springframework.context.ApplicationEvent;
public class MyCustomEvent extends ApplicationEvent {
private String message;
public MyCustomEvent(Object source) {
super(source);
}
public MyCustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
定义一个类,调用 ApplicationEventPublisher 接口发布事件
package com.shore.configdemo.config.publisher;
import com.shore.configdemo.config.event.MyCustomEvent;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
@Component
public class MyEventPublisher {
@Resource
private ApplicationEventPublisher eventPublisher;
public void publishEvent() {
MyCustomEvent myCustomEvent = new MyCustomEvent(this, "Hello, this is custom event");
eventPublisher.publishEvent(myCustomEvent);
}
}
在启动类或其他地方调用发布事件方法
package com.shore.configdemo;
import com.shore.configdemo.config.publisher.MyEventPublisher;
import jakarta.annotation.Resource;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConfigDemoApplication implements CommandLineRunner {
@Resource
private MyEventPublisher myEventPublisher;
public static void main(String[] args) {
SpringApplication.run(ConfigDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
myEventPublisher.publishEvent();
}
}
监听自定义事件
@EventListener
public void handleMyCustomEvent(MyCustomEvent event) {
System.out.println("MyMultiEventListener.handleContextClosedEvent:监听到自定义事件:" + event.getMessage());
}
过滤器
Spring Boot 中的过滤器(Filter)主要用于对请求和响应进行预处理或后处理,常用于处理诸如日志记录、身份验证、头信息处理、请求修改等任务。
新建一个过滤器类,继承 javax.servlet.Filter
接口,也可以继承 OncePerRequestFilter
类,它确保在一次请求中只通过一次filter,无需显式检查是否已过滤。
package com.shore.configdemo.config.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class MyCustomFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 过滤器前执行的操作
System.out.println("MyCustomFilter: Before doFilter");
System.out.println("Request URI: " + request.getRequestURI());
System.out.println("Request Method: " + request.getMethod());
// 继续过滤器链
filterChain.doFilter(request, response);
// 过滤器后执行的逻辑
System.out.println("MyCustomFilter: After doFilter");
System.out.println("Response Status: " + response.getStatus());
}
}
在配置类中注册过滤器
@Bean
public FilterRegistrationBean<MyCustomFilter> myCustomFilter() {
FilterRegistrationBean<MyCustomFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new MyCustomFilter());
// 设置特定过滤路径
filterRegistrationBean.addUrlPatterns("/*");
// 设置过滤器顺序
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
通过postman 或者 http 请求在配置类中配置范围的控制器
拦截器
Spring Boot 中的拦截器(Interceptor)主要用于拦截和处理 Spring MVC 框架中的 HTTP 请求和响应。与过滤器(Filter)相比,拦截器更侧重于对 Spring MVC 框架内的请求进行处理,例如,处理控制器方法的前后逻辑。
创建一个拦截器类实现 HandlerInterceptor
接口,重写接口的三个方法 preHandle
、postHandle
和 afterCompletion
,分别用于在请求处理前、请求处理后(但在视图渲染之前)以及整个请求完成后进行拦截。
package com.shore.configdemo.config.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class MyCustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在控制器方法执行之前调用
System.out.println("MyCustomInterceptor: Pre Handle method is Calling");
// 如果返回 false,将中断请求,不会调用后续的拦截器和控制器方法
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在控制器方法执行之后,视图渲染之前调用
System.out.println("yCustomInterceptor: Post Handle method is Calling");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求完成之后调用(包括视图渲染和异常处理)
System.out.println("Request and Response is completed");
}
}
配置类中注册拦截器,与监听器和过滤器不同,注册拦截器的配置类需要实现 WebMvConfigurer 接口,并重写他的 addInterceptors 方法。
package com.shore.configdemo.config;
import com.shore.configdemo.config.filter.MyCustomFilter;
import com.shore.configdemo.config.interceptor.MyCustomInterceptor;
import com.shore.configdemo.config.listener.MyApplicationListener;
import com.shore.configdemo.config.listener.MyMultiEventListener;
import com.shore.configdemo.config.listener.MyServletContextListener;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletContextListener;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
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.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Resource
private MyCustomInterceptor myCustomInterceptor;
@Bean
public FilterRegistrationBean<MyCustomFilter> myCustomFilter() {
FilterRegistrationBean<MyCustomFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new MyCustomFilter());
// 设置特定过滤路径
filterRegistrationBean.addUrlPatterns("/*");
// 设置过滤器顺序
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myCustomInterceptor)
.addPathPatterns("/**") // 拦截特定的请求
.excludePathPatterns("/login"); // 排除特定的请求
}
}
一旦拦截器被注册到 Spring Boot 应用中,它就会自动对匹配的请求起作用。根据你的配置(如路径模式),每次符合条件的请求都会先经过拦截器,然后再继续处理。
我们在控制器新增两个方法,一个 post 方法用来访问,一个 get 方法用来拦截器重定向,拦截器建议重定向 get 方法
package com.shore.configdemo.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyCustomController {
@PostMapping("/sayHello")
public String sayHello() {
System.out.println("sayHello");
return "Hello World";
}
@GetMapping("/login")
public String login() {
System.out.println("login");
return "login success";
}
}
我们来分析以下上图的链路,首先我们调用 sayHllo 方法走到过滤器的链路,打印过滤器链之前的逻辑,然后继续后面的过滤器链,接着请求被拦截器拦截到,执行拦截器 preHandler 方法,该方法 重定向到 login 并结束当前链路,不再访问 sayHello,接着 login 又被过滤器捕获到,执行过滤前逻辑,然后被拦截器放行,访问到 login 接口,正常返回又被过滤器捕获到,执行过滤器后置逻辑,至此链路结束。
我们将拦截器中判断 session 返回 false 的逻辑删掉,重新访问 sayHello,执行一次完整的拦截器逻辑.
这里我们可以看到先走到过滤器前置逻辑,接着拦截器 preHandle 方法,然后走到sayHello 接口,然后走到拦截器 postMethod 方法,然后是拦截器 afterCompletion 方法,最后走到过滤器后值逻辑。
定时器
在Spring Boot中,实现定时器功能通常有几种方法,包括使用@Scheduled
注解、TaskScheduler
接口以及通过@EnableScheduling
和配置类来设定定时任务。
@Scheduled
注解
@Scheduled
注解提供了最简便的方式来声明定时任务。你只需在相应的方法上添加此注解,并指定相应的cron表达式或其他定时规则。
首先,确保你的Spring Boot应用已经启用了定时任务支持,这通常通过在主应用类或配置类上添加@EnableScheduling
注解来实现。
package com.shore.configdemo;
import com.shore.configdemo.config.publisher.MyEventPublisher;
import jakarta.annotation.Resource;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ConfigDemoApplication implements CommandLineRunner {
@Resource
private MyEventPublisher myEventPublisher;
public static void main(String[] args) {
SpringApplication.run(ConfigDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
myEventPublisher.publishEvent();
}
}
创建一个定时任务类,方法使用 @Scheduled
注解标记
package com.shore.configdemo.config.schedule;
import org.springframework.scheduling.annotation.Scheduled;
import java.util.Date;
public class MyCustomScheduleTask {
@Scheduled(cron = "0 * * * * *")
public void myScheduleTask() {
System.out.println("MyCustomScheduleTask: " + new Date());
}
}
配置类注册定时任务类
@Bean
public MyCustomScheduleTask myCustomScheduleTask() {
return new MyCustomScheduleTask();
}
TaskScheduler
接口
如果你需要更细粒度的控制,比如任务池、线程管理等,你可以直接使用TaskScheduler
接口。首先,定义一个TaskScheduler
的bean
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
return taskScheduler;
}
然后,你可以注入TaskScheduler
并使用它来调度任务
package com.shore.configdemo.service.impl;
import com.shore.configdemo.service.MyService;
import jakarta.annotation.Resource;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class MyServiceImpl implements MyService {
@Resource
private TaskScheduler taskScheduler;
@Override
public void sayHello(String name) {
}
public void scheduleTask() {
taskScheduler.scheduleWithFixedDelay(() -> {
System.out.println("执行定时任务: " + new Date());
}, 5000); // 任务间隔5秒
}
}
@EnableScheduling
和配置类
前面已经提到了@EnableScheduling
注解,它用于启用Spring的计划任务调度功能。通常,你只需在主应用类或任何配置类上添加此注解,然后就可以在你的应用中使用@Scheduled
注解来声明定时任务了。
除了简单的启用定时任务外,你还可以通过实现SchedulingConfigurer
接口来自定义定时任务的配置,比如设置任务执行器(TaskExecutor
)或任务调度器(TaskScheduler
)的属性。
package com.shore.configdemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import java.util.Date;
@Configuration
@EnableScheduling
public class MyCustomSchedulingTask implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
() -> System.out.println("执行基于触发器的任务: " + new Date()),
triggerContext -> {
// 自定义触发器逻辑,比如根据时间或条件来决定是否执行任务
return new CronTrigger("0 * * * * *")
.nextExecutionTime(triggerContext)
.toInstant();
}
);
}
}
配置文件属性
自定义配置文件 custom-config.yaml
my-app:
auth-filter:
secretKey: mySecretKey
expirationTime: 864000000 # 10 天
excludes:
- /shore/demo/user/login
- /shore/demo/user/register
- /shore/demo/user/query
snowflake:
workerId: 1
datacenterId: 1
application.yaml 文件配置自定义配置文件的路径
spring:
config:
import: classpath:custom-config.yaml
创建配置文件类,指定要读取的标签,定义要读取的属性
package com.shore.my_spring_demo.web.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@ConfigurationProperties(prefix = "my-app.auth-filter")
@Data
public class CustomAuthFilterConfig {
private String secretKey;
private long expirationTime;
private List<String> excludes;
}
配置类中注册配置文件类
@Bean
public CustomAuthFilterConfig customAuthFilterConfig() throws NoSuchAlgorithmException {
return new CustomAuthFilterConfig();
}
最后就可以在服务中生命使用了
package com.shore.my_spring_demo.service.jwt;
import com.shore.my_spring_demo.common.enums.ErrorEnums;
import com.shore.my_spring_demo.exception.UsersException;
import com.shore.my_spring_demo.web.config.CustomAuthFilterConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.security.KeyPair;
import java.util.Date;
@Slf4j
@Service
public class JwtServiceImpl implements JwtService {
@Resource
private CustomAuthFilterConfig customConfig;
@Resource
private KeyPair keyPair;
@Override
public String generateTokenAsymmetric(String username) {
return Jwts.builder()
.claim("sub", username).claim("role", "admin")
.expiration(new Date(System.currentTimeMillis() + customConfig.getExpirationTime()))
.signWith(SignatureAlgorithm.RS256, keyPair.getPrivate())
.compact();
}
}
总结
上述所有在配置类中注册的类,大多都可以直接通过注解 @Compent 标记这种简单的方法进行注册,但是如果想要更加精细化的操作,还是需要在配置类中通过 bean 管理注册。