在Spring/Spring Boot中,如何使Bean在Tomcat之前初始化?

huangapple go评论76阅读模式
英文:

In Spring/Spring Boot how to make Bean initialised before Tomcat?

问题

在Spring/Spring Boot中,如何在Tomcat之前初始化Bean?
这是在日志中出现以下行之前:

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
o.apache.catalina.core.StandardService   : Starting service [Tomcat]
org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.73]
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 439 ms

(而不是之后)

原因:存在一个初始化开销较大的Bean,如果它失败(即Bean创建失败),则没有必要启动Tomcat或服务器应用程序。

英文:

In Spring/Spring Boot how to make Bean initialised before Tomcat?
That is before these lines in log:

o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
o.apache.catalina.core.StandardService   : Starting service [Tomcat]
org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.73]
o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 439 ms

(and not after)

Reason: There is a Bean with heavy initialization, and if it fails
(i.e. Bean creation fails), then there is no need to start Tomcat or the server app at all.

答案1

得分: 1

Here's the translated code:

假设有这样的接口

public interface MyInterface {
    public String greeting();
}

以及这样的实现类

package com.example.demo.service;

public class MyImpl1 implements MyInterface {
    @Override
    public String greeting() {
        return "MyImpl1 greeting";
    }
}

添加一个实现`BeanDefinitionRegistryPostProcessor``@Component`,如下所示

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;

@Component
public class BeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
    private static Logger logger = LoggerFactory.getLogger(BeanFactoryPostProcessor.class);

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String beanName = "myInterface"; // 接口名称,首字母小写
        String className = "com.example.demo.service.MyImpl1"; // 实现类的完整类名
        BeanDefinition beanDefn = new RootBeanDefinition(className);
        registry.registerBeanDefinition(beanName, beanDefn);
        logger.info("注册Bean {}:{}", beanName, className);
        // 这里可以添加其他早期的Bean定义
    }

    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // 什么都不做
    }
}

在正常情况下使用它:

@Autowired
MyInterface myInterface;

移除类上的@Component@Bean注解,以避免它被构建两次。

英文:

Assuming and interface like this

public interface MyInterface {
public String greeting();
}

and an implementing class like this

package com.example.demo.service;
public class MyImpl1 implements MyInterface {
@Override
public String greeting() {
return "MyImpl1 greeting";
}
}

Add a @Component that implements BeanDefinitionRegistryPostProcessor like this:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
@Component
public class BeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private static Logger logger = LoggerFactory.getLogger(BeanFactoryPostProcessor.class);
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String beanName = "myInterface"; // interface name with lowercase first letter
String className = "com.example.demo.service.MyImpl1"; // full class name of the implemention class
BeanDefinition beanDefn= new RootBeanDefinition(className);
registry.registerBeanDefinition(beanName, beanDefn);
logger.info("Registering bean {}:{}", beanName, className);
// any other early beans here
}
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// do nothing
}
}

Use it in the normal way:

@Autowired
MyInterface myInterface;

Remove the @Component or @Bean annotations on the class so that it is not built twice.

答案2

得分: 1

这可以使用 SpringApplicationBuilder 实现。它提供了父级和子级的方法。如果您将您的"重"配置bean定义为父级,通常的应用程序类定义为子级,您将得到您想要的结果。如果父级出现异常,它的子级将不会启动。

解决方案

重配置类:

@Configuration
public class HeavyConfig {
    @Bean
    public static HeavyConfig myBean() {
        throw new NullPointerException("autch!");
    }
}

主应用程序方法:

public static void main(String[] args) {
    new SpringApplicationBuilder()
        .parent(HeavyConfig.class)
        .child(NormalApplication.class)
        .run(args);
}

在发生异常时的启动日志:

Starting RestApiApplication using Java 20 with PID 8076
No active profile set, falling back to 1 default profile: "default"
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myBean' defined
in pl.edosoft.restapi.HeavyConfig: Failed to instantiate [pl.edosoft.restapi.HeavyConfig]: Factory
method 'myBean' threw exception with message: autch!
Application run failed
英文:

This can be implemented using SpringApplicationBuilder. It provides methods parent and child. If you define your "heavy" configuration bean as parent and the usual application class as child you will get what you wanted. In case of exception in parent its child will be not started.

Solution

Heavy Config class:

@Configuration
public class HeavyConfig {
@Bean
public static HeavyConfig myBean() {
throw new NullPointerException("autch!");
}
}

Main application method:

public static void main(String[] args) {
new SpringApplicationBuilder()
.parent(HeavyConfig.class)
.child(NormalApplication.class)
.run(args);

Startup logs in case of an exception:

Starting RestApiApplication using Java 20 with PID 8076 
No active profile set, falling back to 1 default profile: "default"
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myBean' defined
in pl.edosoft.restapi.HeavyConfig: Failed to instantiate [pl.edosoft.restapi.HeavyConfig]: Factory
method 'myBean' threw exception with message: autch!
Application run failed

huangapple
  • 本文由 发表于 2023年4月13日 23:25:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76007236.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定