在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 {@Overridepublic void contextInitialized(ServletContextEvent sce) {sce.getServletContext().setAttribute("myApp", "config-demo");System.out.println("MyServletContextListener:监听器启动");}@Overridepublic void contextDestroyed(ServletContextEvent sce) {System.out.println(sce.getServletContext().getAttribute("myApp"));System.out.println("MyServletContextListener:监听器销毁");}
}
注解 @WebListener 标记为一个监听器类(没啥用)
@Beanpublic 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> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("MyApplicationListener: 监听到上下文刷新" + event);}@Overridepublic boolean supportsAsyncExecution() {return ApplicationListener.super.supportsAsyncExecution();}
}
配置类中注册 监听器
@Beanpublic 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 {@EventListenerpublic void handleContextRefreshEvent(ContextRefreshedEvent event) {System.out.println("MyMultiEventListener.handleContextRefreshEvent:监听到上下文刷新");}@EventListenerpublic 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 {@Resourceprivate 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 {@Resourceprivate MyEventPublisher myEventPublisher;public static void main(String[] args) {SpringApplication.run(ConfigDemoApplication.class, args);}@Overridepublic void run(String... args) throws Exception {myEventPublisher.publishEvent();}
}
监听自定义事件
@EventListenerpublic 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 {@Overrideprotected 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());}
}
在配置类中注册过滤器
@Beanpublic 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 {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 在控制器方法执行之前调用System.out.println("MyCustomInterceptor: Pre Handle method is Calling");// 如果返回 false,将中断请求,不会调用后续的拦截器和控制器方法return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 在控制器方法执行之后,视图渲染之前调用System.out.println("yCustomInterceptor: Post Handle method is Calling");}@Overridepublic 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 {@Resourceprivate MyCustomInterceptor myCustomInterceptor;@Beanpublic FilterRegistrationBean<MyCustomFilter> myCustomFilter() {FilterRegistrationBean<MyCustomFilter> filterRegistrationBean = new FilterRegistrationBean<>();filterRegistrationBean.setFilter(new MyCustomFilter());// 设置特定过滤路径filterRegistrationBean.addUrlPatterns("/*");// 设置过滤器顺序filterRegistrationBean.setOrder(1);return filterRegistrationBean;}@Overridepublic 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 {@Resourceprivate MyEventPublisher myEventPublisher;public static void main(String[] args) {SpringApplication.run(ConfigDemoApplication.class, args);}@Overridepublic 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());}
}
配置类注册定时任务类
@Beanpublic MyCustomScheduleTask myCustomScheduleTask() {return new MyCustomScheduleTask();}
TaskScheduler
接口
如果你需要更细粒度的控制,比如任务池、线程管理等,你可以直接使用TaskScheduler
接口。首先,定义一个TaskScheduler
的bean
@Beanpublic 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 {@Resourceprivate TaskScheduler taskScheduler;@Overridepublic 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 {@Overridepublic 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: mySecretKeyexpirationTime: 864000000 # 10 天excludes:- /shore/demo/user/login- /shore/demo/user/register- /shore/demo/user/querysnowflake:workerId: 1datacenterId: 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;
}
配置类中注册配置文件类
@Beanpublic 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 {@Resourceprivate CustomAuthFilterConfig customConfig;@Resourceprivate KeyPair keyPair;@Overridepublic 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 管理注册。