1.自定义条件注解
1.1 @conditional注解
@Conditional注解是Spring框架中的一个条件注解,可以根据不同的条件来决定是否需要创建一个Bean对象。其工作原理是,在Bean创建之前根据条件判断进行筛选,只有当条件满足时才会创建Bean对象。
1.2自定义条件注解
(1)实现Condition接口
public class RedisCustomCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("redis.configuration.off");
if (Strings.isBlank(property)){
return false;
}
return property.equalsIgnoreCase("true");
}
}
(2)自定义注解,这样当我们在@Configuration类上使用@CustomConditionAnnotation注解时,框架会判断CustomCondition类的matches()方法是否返回true,如果是,则创建这个Bean对象。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Conditional(value = RedisCustomCondition.class)
public @interface RedisCustomConditionAnnotation {
}
(3)在条件装配的场景使用该注解
@Configuration
@RedisCustomConditionAnnotation
public class RedisConfig {
....
}
(4)配置
redis.configuration.off = false
应用:可以用作某个配置是否交给spring管理的开关
2. ApplicationContextInitializer
2.1作用
通常用于需要在应用程序上下文中进行一些程序化初始化的 Web 应用程序。例如,注册属性源或激活与上下文环境相关的配置文件。
2.2 加载和初始化时机
通过实现ApplicationContextInitializer
接口,可以在ApplicationContext
创建之前对其进行一些定制化的修改
源码使用位置
org.springframework.boot.SpringApplication#getSpringFactoriesInstances(java.lang.Class<T>)
来获取 META-INF/spring.factories
中配置 key 为 org.springframework.context.ApplicationContextInitializer
的数据
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
ApplicationContextInitializer 的初始化时机:
查看 SpringApplication
的 run(String... args)
方法,如下所示:
public ConfigurableApplicationContext run(String... args) {
//监控器监听容器启动并进行图形化页面处理
Startup startup = Startup.create();
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
//创建默认的引导上下文
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
//图形化页面开启
configureHeadlessProperty();
//添加监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
//加载参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//环境准备
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
//banner打印
Banner printedBanner = printBanner(environment);
//创建Spring应用上下文
context = createApplicationContext();
//设置引导器
context.setApplicationStartup(this.applicationStartup);
/**
* 准备上下文: 期间就会调用ApplicationContextInitializer
*/
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文(包括单实例创建,前置处理和后置处理器,代理对象创建(aop)等一系列过程)
refreshContext(context);
//Spring应用上下文收尾阶段
afterRefresh(context, applicationArguments);
startup.started();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
listeners.started(context, startup.timeTakenToStarted());
//回调工作处理: 处理实现了ApplicationRunner 或 CommandLineRunner 接口的类,做一些容器启动成功后的预处理工作
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, listeners);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
throw handleRunFailure(context, ex, null);
}
return context;
}
进入上述 prepareContext
方法,可以看到applyInitializers(context)方法
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
addAotGeneratedInitializerIfNecessary(this.initializers);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
}
进入上述 applyInitializers(context)
方法,可以看到这里循环处理实现了ApplicationContextInitializer接口的类
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
2.3 应用:
-
修改Spring Boot默认的environment属性。使用configurableApplicationContext.getEnvironment()方法获取到environment对象,从而修改环境变量,例如添加自定义配置文件路径。
-
添加自定义的PropertySource。使用environment.getPropertySources().addLast(propertySource)方法,可以添加自定义的属性源,从而实现更灵活的配置。
-
注册自定义bean。使用configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean)方法,可以注册自定义的bean,从而实现更灵活的依赖注入。
举例:
(1)添加自定义配置文件
/**
* 修改Spring Boot默认的environment属性。
* 使用configurableApplicationContext.getEnvironment()方法获取到environment对象,从而修改环境变量,例如添加自定义配置文件路径。
*
*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
// 添加自定义配置文件路径
try {
System.out.println("InterveneApplicationContextInitializer initialize :" + environment);
environment.getPropertySources().addFirst(new ResourcePropertySource("classpath:liugp.properties"));
System.out.println("InterveneApplicationContextInitializer initialize add FirstResourcePropertySource classpath:zyftest.properties");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码中的InterveneApplicationContextInitializer需要被注册才能生效。可以通过在src/main/resources/META-INF/spring.factories
文件中指定注册项的方式来注册:
org.springframework.context.ApplicationContextInitializer=\
com.example.demo17.config.hbase.InterveneApplicationContextInitializer
(2)添加自定义的属性源,从而实现更灵活的配置
/**
* 添加自定义的PropertySource。
* 使用environment.getPropertySources().addLast(propertySource)方法,可以添加自定义的属性源,从而实现更灵活的配置。
*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
// 添加自定义的PropertySource
PropertySource<?> propertySource = new MyPropertySource("myPropertySource");
ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
environment.getPropertySources().addLast(propertySource);
System.out.println("InterveneApplicationContextInitializer initialize add PropertySource myPropertySource");
}
// 自定义PropertySource
private static class MyPropertySource extends PropertySource<String> {
private static final String MY_PROPERTY_SOURCE_KEY = "my.property.source.key";
public MyPropertySource(String name) {
super(name);
}
@Override
public Object getProperty(String name) {
if (MY_PROPERTY_SOURCE_KEY.equals(name)) {
return "myPropertySourceValue";
}
return null;
}
}
}
这样就可以通过@Value("${my.property.source.key}")
的方式在应用程序中获取到它的值了;
同上:上述代码中的InterveneApplicationContextInitializer需要被注册才能生效
(3)注册自定义bean
/**
* 注册自定义bean。
* 使用configurableApplicationContext.getBeanFactory().registerSingleton(beanName, bean)方法,可以注册自定义的bean,从而实现更灵活的依赖注入。
*/
public class InterveneApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
ConfigurableListableBeanFactory beanFactory = configurableApplicationContext.getBeanFactory();
// 注册自定义Bean
MyBean myBean = new MyBean();
beanFactory.registerSingleton("myBean", myBean);
System.out.println("InterveneApplicationContextInitializer initialize registerSingleton myBean");
}
}
同上:上述代码中的InterveneApplicationContextInitializer需要被注册才能生效
3. ApplicationRunner 接口
应用:在Spring容器启动期间,容器刷新完成后做一些初始化操作(比如:预加载一些元数据,环境预热等)
用法:实现ApplicationRunner接口,交给Spring管理
/**
* 项目启动时执行,预先加载初始化数据
* */
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Override
public void run(String... args) throws Exception {
new Thread(()->{
System.out.println("Hello ApplicationRunner!");
}).start();
}
}
4. CommandLineRunner 接口
用法作用和ApplicationRunner 类似
/**
* 项目启动时执行,预先加载命令行相关数据
* */
@Component
public class CommandLineRunnerTest implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
new Thread(()->{
System.out.println("Hello CommandLineRunner!");
}).start();
}
}
5. SpringApplicationRunListener
SpringApplicationRunListener
是Spring Boot的一个事件监听器,用于在应用程序启动和停止时执行一些操作。
/**
* SpringApplicationRunListener是Spring Boot的一个事件监听器,用于在应用程序启动和停止时执行一些操作。
* 可能需要自定义SpringApplicationRunListener来执行某些特定操作。
* 下面是一个示例,演示如何扩展SpringApplicationRunListener以添加自定义操作
* */
public class InterveneRunListener implements SpringApplicationRunListener {
private final SpringApplication application;
private final String[] args;
public InterveneRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
System.out.println("SpringApplicationRunListener ... starting ");
SpringApplicationRunListener.super.starting(bootstrapContext);
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
System.out.println("SpringApplicationRunListener ... environmentPrepared ");
SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener ... contextPrepared ");
SpringApplicationRunListener.super.contextPrepared(context);
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("SpringApplicationRunListener ... contextLoaded ");
SpringApplicationRunListener.super.contextLoaded(context);
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("SpringApplicationRunListener ... started ");
SpringApplicationRunListener.super.started(context, timeTaken);
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
System.out.println("SpringApplicationRunListener ... ready ");
SpringApplicationRunListener.super.ready(context, timeTaken);
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("SpringApplicationRunListener ... failed ");
SpringApplicationRunListener.super.failed(context, exception);
}
}
上述代码中的InterveneRunListener需要被注册才能生效。可以通过在src/main/resources/META-INF/spring.factories
文件中指定注册项的方式来注册:
org.springframework.boot.SpringApplicationRunListener=\
com.example.demo17.config.hbase.InterveneRunListener
6. BeanFactoryAware
使用场景:可以在bean实例化之后,但还未初始化之前,拿到 BeanFactory
,在这个时候,可以对每个bean进行特殊化的定制。也或者可以把BeanFactory
拿到进行缓存,日后使用。
-
获取其他 bean 实例:通过 BeanFactory 引用,这个 bean 可以动态地获取容器中的其他 bean 实例。
-
访问容器的配置信息:通过 BeanFactory,这个 bean 可以访问容器的配置信息,例如属性文件、环境变量等。
-
控制 bean 的生命周期:通过 BeanFactory,这个 bean 可以在需要时自定义自己的初始化逻辑,或者在销毁时执行一些清理操作。
注册Bean
@Component
public class ApplicationListenerTest implements BeanFactoryAware {
private ConfigurableListableBeanFactory configurableListableBeanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
configurableListableBeanFactory = (ConfigurableListableBeanFactory) beanFactory;
configurableListableBeanFactory.registerSingleton("object",new Object());
}
}
7. ApplicationContextAware
7.1应用场景
ApplicationContextAware可以用于各种场景,其中一些常见的包括:
访问其他Bean: 可以通过ApplicationContextAware获取其他Bean的实例,以便在Bean中调用其他Bean的方法或访问其他Bean的属性。 执行特定逻辑: 可以在获取到Spring容器上下文后执行特定的逻辑,例如执行一些初始化任务或后处理任务。 获取Spring容器信息: 可以获取Spring容器的各种信息,例如Bean的定义、环境属性、配置信息等。
@Component
public class ApplicationListenerTest implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//通过注解获取bean
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(CustomConditionAnnotation.class);
//通过接口获取bean
Map<String, Condition> beansOfType = applicationContext.getBeansOfType(Condition.class);
}
}
8. InitializingBean 和 @PostConstruct
主要适用于处理非静态成员的初始化,所以这个接口的用途就是用来实现初始化数据用的。
比如某些init操作
public class RetryTemplateTest {
private RetryTemplate retryTemplate;
static Long RETRY_TIME = 1000L;
@PostConstruct
public void init() {
rateLimiter = RateLimiter.create(Double.parseDouble(globalArkConfig.getRateLimiterRate()));
this.retryTemplate = createRetryTemplate();
}
/**
* 定义重试策略:可以定义重试的次数、每次重试之间的间隔时间,甚至可以设置不同的重试策略(如指数退避策略)
*/
private static RetryTemplate createRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 设置重试策略,无线重试
AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();
retryTemplate.setRetryPolicy(alwaysRetryPolicy);
//设置重试策略,最高重试100次
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
simpleRetryPolicy.setMaxAttempts(5);
retryTemplate.setRetryPolicy(simpleRetryPolicy);
// 设置回退策略-重试的间隔策略
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
//重试间隔ms
backOffPolicy.setBackOffPeriod(1000);
retryTemplate.setBackOffPolicy(backOffPolicy);
//设置监听器
RetryListener retryListener = new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("---open----在第一次重试时调用");
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("close----在最后一次重试后调用(无论成功与失败)。" + context.getRetryCount());
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
System.out.println("error----在每次调用异常时调用。" + context.getRetryCount());
}
};
retryTemplate.registerListener(retryListener);
return retryTemplate;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool(16);
try {
ArrayList<Integer> list = new ArrayList<>(1000);
for (int i = 0; i < 101; i++) {
list.add(i);
}
System.out.println("数据准备完成。" + Thread.currentThread().getName());
forkJoinPool.submit(() -> {
list.parallelStream().forEach(res -> {
/**
* 通过 RetryTemplate 的 execute 方法来执行需要重试的操作。如果操作成功,则返回结果;
* 如果失败,则根据定义的重试策略进行重试。
*/
retryTemplate.execute(
//每次重试的操作
retryContext -> {
if (res == 100) {
throw new RuntimeException("偶数异常 => " + res);
}
return 0;
},
//达到最大重试次数后执行,并以其返回结果作为最终的返回结果
retryContext -> {
throw new RuntimeException("达到最大重试次数后 => " + res);
}
);
});
}).get();
} catch (Exception e) {
throw new RuntimeException("捕获到线程内部异常 => " + e);
} finally {
forkJoinPool.shutdown();
}
}
}
9. InstantiationAwareBeanPostProcessor
-
InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置
-
postProcessBeforeInstantiation方法是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走
-
postProcessAfterInstantiation方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true, postProcessPropertyValues就会被执行
-
postProcessPropertyValues方法对属性值进行修改(这个时候属性值还未被设置,但是我们可以修改原本该设置进去的属性值)。如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用。可以在该方法内对属性值进行修改
-
父接口BeanPostProcessor的2个方法postProcessBeforeInitialization和postProcessAfterInitialization都是在目标对象被实例化之后,并且属性也被设置之后调用的
10. BeanPostProcessor
BeanProcessor主要应用在以下几个方面:
-
依赖注入的简化: 通过BeanPostProcessor,我们可以轻松实现依赖注入,减少了手动管理对象之间的依赖关系的复杂性,提高了代码的可测试性和可维护性。
-
面向切面编程(AOP)的支持: BeanPostProcessor提供了AOP的支持,使得我们可以更加灵活地实现横切关注点,如日志记录、事务管理等,将这些与核心业务逻辑分离开来,提高了代码的模块化程度。
-
配置管理的优化: 通过BeanPostProcessor,我们可以将配置信息与代码分离,实现了配置的集中管理和动态加载,降低了系统的耦合度,使得系统更易于维护和扩展。
执行顺序
ApplicationContextInitializer
>静态代码块
> 构造方法
> BeanFactoryAware
> ApplicationContextAware
> @PostConstruct
> InitializingBean
> InstantiationAwareBeanPostProcessor
> BeanPostProcessor
> ApplicationRunner
11. Spring Boot 程序的启动时间优化
-
减少依赖项:评估项目的依赖项,并确保只引入必要的依赖。较多的依赖项可能会增加启动时间,因为它们需要被扫描和初始化。通过删除不需要的依赖项或仅引入必要的模块,可以减少类路径的扫描和初始化时间。
-
调整自动配置:Spring Boot 的自动配置是一个强大的特性,但有时可能会引入不必要的组件和功能。通过调整自动配置,可以精确地指定所需的配置,避免加载不必要的组件,从而减少启动时间。
-
启用懒加载:将一些不常用的组件设置为懒加载,即在需要时才进行初始化。通过懒加载,可以避免在启动阶段初始化不必要的组件,从而加快启动时间。可以使用 Spring Framework 的 @Lazy 注解或在配置类中进行相应的配置。
使用 @Lazy 注解:在需要懒加载的组件上使用 Spring Framework 的 @Lazy 注解。将 @Lazy 注解应用于组件的声明处,以指示该组件应该在需要时才进行初始化。
@Component @Lazy public class MyLazyComponent { // ... }
在配置类中进行配置:如果你使用的是配置类来进行组件的配置,你可以在配置类的方法上使用 @Lazy 注解,将需要懒加载的组件进行标记
@Configuration public class MyConfig { @Bean @Lazy public MyLazyComponent myLazyComponent() { return new MyLazyComponent(); } }
-
启用编译时优化:使用 Spring Boot 2.4 及更高版本,你可以通过启用编译时优化来加快启动时间。通过在 pom.xml 文件中设置 <compilerArgs> 属性,使用 --add-opens 选项来启用编译时优化。这可以减少反射操作的开销,从而提高启动性能。
确认使用的 Spring Boot 版本:确保你的项目使用的是 Spring Boot 2.4 或更高版本。编译时优化功能是在 Spring Boot 2.4 中引入的。 配置 pom.xml 文件:在项目的 pom.xml 文件中,找到 <build> 元素,并在其中的 <plugins> 元素下添加 Maven Compiler 插件配置。在 Maven Compiler 插件配置中,使用 <compilerArgs> 属性来设置编译器选项。
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <compilerArgs> <arg>--add-opens</arg> <arg>java.base/java.lang=ALL-UNNAMED</arg> <arg>--add-opens</arg> <arg>java.base/java.util=ALL-UNNAMED</arg> <!-- 添加其他需要的 --add-opens 选项 --> </compilerArgs> </configuration> </plugin> </plugins> </build>
在 <compilerArgs> 属性中,我们使用 --add-opens 选项来指定需要开放的包和模块。上述示例中,我们设置了两个 --add-opens 选项,分别是 java.lang 和 java.util 包。你还可以根据需要添加其他的 --add-opens 选项,以开放其他需要的包和模块。 重新构建应用程序:保存更改后,重新构建应用程序。在编译过程中,编译器将使用指定的编译器选项,启用编译时优化功能。
-
调整日志级别:Spring Boot 默认启用了相对较高的日志级别,这可能会导致大量的日志输出,从而增加启动时间。通过将日志级别调整为更低的级别,如将 INFO 调整为 WARN,可以减少日志输出,从而缩短启动时间。
-
使用缓存:Spring Boot 在启动过程中会进行多个步骤的扫描和初始化。通过使用缓存机制,可以缓存一些元数据和初始化结果,避免重复的扫描和初始化操作,从而提高启动性能。可以使用 Spring Boot 的缓存机制或其他缓存库来实现。
参考原文链接: