Я пытаюсь преобразовать приложение Spring в приложение SpringBoot. Приложение работает нормально, когда я запускаю его с помощью графического интерфейса Intellij, но если я использую терминал и пытаюсь запустить его с помощью команды java -cp
, оно выдает исключение. Полный поток приложения вместе с сообщением об ошибке прилагается ниже:
Основной класс -
package com.company.data;
@SpringBootApplication
@ImportResource("classpath:spring/data-server-context.xml")
public class DataServer {
// other fields
private static DataServer dataServer;
// other fields
//Constructor
public DataServer( args ){
//assignments
}
public static void main(String[] args) throws Exception {
SpringApplication.run(DataServer.class,args);
LOG.info("Starting Data Server...");
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/data-server-context.xml");
try {
dataServer = ctx.getBean(DataServer.class)
//perform tasks
dataServer.func1()
}
catch (Exception e) {
//LOG error
throw e;
}
LOG.info("Data server started successfully!");
}
public void func1(){
//func def
}
}
data-server-context.xml выглядит примерно так
<beans xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns = "http://www.springframework.org/schema/beans"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource = "classpath:spring/properties-context.xml"/>
<!-- other bean definitions-->
<bean id = "dataServer" class = "com.company.data.DataServer">
<constructor-arg name = "arg1" ref = "arg1_ref"/>
<constructor-arg name = "arg2" ref = "arg2_ref"/>
</bean>
<!-- other bean definitions -->
<beans profile = "!test"> <!-- Don't create scheduler beans if spring test profile passed -->
<bean id = "dataReloadJob" class = "org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name = "targetObject" ref = "dataServer"/>
<property name = "targetMethod" value = "func1"/>
<property name = "concurrent" value = "false"/>
</bean>
<bean id = "dataReloadTrigger" class = "org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name = "jobDetail" ref = "dataReloadJob"/>
<property name = "cronExpression" value = "some_expression"/>
</bean>
<bean class = "org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name = "triggers">
<list>
<ref bean = "dataReloadTrigger"/>
</list>
</property>
</bean>
</beans>
</beans>
свойства-context.xml -
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id = "propertyConfigurer" class = "com.company.config.PropertyPlaceholderConfigurerAdapter">
<constructor-arg name = "name" value = "application"/>
</bean>
</beans>
Теперь, как уже упоминалось выше, если я запускаю DataServer
из графического интерфейса, он работает нормально. Но если я попытаюсь запустить его через терминал, скомпилировав jar, я получу сообщение об ошибке ниже:
06:38:36.909 [main] WARN org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to register bean definition with name 'dataServer'
Offending resource: class path resource [spring/data-server-context.xml]; nested exception is org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'dataServer' defined in class path resource [spring/data-server-context.xml]: Cannot register bean definition [Generic bean: class [com.company.data.DataServer]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring/data-server-context.xml]] for bean 'dataServer': There is already [Generic bean: class [com.company.data.DataServer]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
06:38:36.916 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to register bean definition with name 'dataServer'
Offending resource: class path resource [spring/data-server-context.xml]; nested exception is org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'dataServer' defined in class path resource [spring/data-server-context.xml]: Cannot register bean definition [Generic bean: class [com.company.data.DataServer]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring/data-server-context.xml]] for bean 'dataServer': There is already [Generic bean: class [com.company.data.DataServer]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:119)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:104)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(DefaultBeanDefinitionDocumentReader.java:314)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseDefaultElement(DefaultBeanDefinitionDocumentReader.java:197)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:176)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:149)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:96)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:511)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:338)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:224)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:195)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromImportedResources$0(ConfigurationClassBeanDefinitionReader.java:378)
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromImportedResources(ConfigurationClassBeanDefinitionReader.java:345)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:147)
at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:120)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:332)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:237)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:280)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:96)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:707)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:533)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:405)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at com.company.data.DataServer.main(DataServer.java:88)
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'dataServer' defined in class path resource [spring/data-server-context.xml]: Cannot register bean definition [Generic bean: class [com.company.data.DataServer]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring/data-server-context.xml]] for bean 'dataServer': There is already [Generic bean: class [com.company.data.DataServer]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:945)
at org.springframework.beans.factory.support.BeanDefinitionReaderUtils.registerBeanDefinition(BeanDefinitionReaderUtils.java:164)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.processBeanDefinition(DefaultBeanDefinitionDocumentReader.java:311)
... 30 common frames omitted
Я добавил spring.main.allow-bean-definition-overriding=true
к application.properties
@M.Deinum, не могли бы вы показать это в коде?
Я попробовал ApplicationContext ctx = SpringApplication.run(DataServer.class,args);
и удалил ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/data-server-context.xml");
. Но у меня возникла та же ошибка, хотя на графическом интерфейсе все работало.
Вы прочитали ошибку? Удалите bean-компонент из вашего XML, вы уже автоматически создаете его, потому что это @SpringBootApplication
.
Но мне нужен компонент, поскольку я использую его в data-server-context.xml в планировщике кварца. И как это работает нормально, когда я запускаю его в IDE, откуда @SpringBootApplication получает параметры конструктора?
Как уже говорилось, он уже существует из-за @SpringBootApplication
, который в конце является @Component
и, таким образом, зарегистрирован как bean-компонент. Если у вас есть параметры конструктора (они здесь не отображаются). Самый простой — переместить имеющийся у вас метод main
в другой класс вместо того, чтобы добавлять его в DataServer
.
Как уже отмечал М. Дейнум в комментариях вы фактически создаете второй (ненужный) контекст, ведущий к описанной вами ошибке.
package com.company.data;
public class DataServer {
// your fields
public DataServer(X arg1, Y arg2) {
// [...]
}
public void func1() {
// [...]
}
// other stuff
}
package com.company.data;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
// this combination will tell spring to parse your XML file and create the defined beans
@SpringBootApplication
@ImportResource("classpath:spring/data-server-context.xml")
public class DataServerApp {
public static void main(String[] args) {
SpringApplication.run(DataServerApp.class, args);
}
}
package com.company.data;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import com.company.data.DataServer;
@Component
final class AppStartupComponent {
private final DataServer dataServer;
// inject your bean using constructor arg (the recommended way)
public AppStartupComponent(DataServer dataServer) {
this.dataServer = dataServer;
}
// gets called (precisely) 'on this beans creation' which is effectivly 'on app startup'
@PostConstruct
public void onAppSartup() {
// do your startup logic, from your exmaple:
try {
dataServer.func1()
} catch (Exception e) {
// logging and re-throw
throw e;
}
}
}
Ваше повторно созданное исключение будет «заполнено полностью» и остановит запуск приложения с помощью exit code
из 1
.
Если вам нужен другой код выхода, посмотрите здесь
Если @PostConstruct
нежелательно, вы также можете просто поместить свою «логику запуска» внутри конструктора класса, если у вас есть «инъекция конструктора».
Вместо этого вы также можете реализовать интерфейс InitializingBean
или ApplicationListener<ContextRefreshedEvent>
, каждый из которых имеет немного другой момент выполнения. Порядок будет таким: «Конструктор > PostConstruct > InitializingBean».
В вашем случае установка spring.main.allow-bean-definition-overriding
не требуется, и ее следует избегать.
Из Spring Boot уже имеется достаточное количество журналов о «запуске», таких как Starting DataServerApp using Java [...]
и Started DataServerApp in X seconds [...]
, поэтому нет необходимости в таком простом ведении журнала вручную.
Если возможно, попробуйте перейти на объявление bean-компонента на основе Java-кода (как предлагает весна)
Спасибо. Я постараюсь внедрить ваши предложения
Ваш основной метод не имеет смысла. Вы воссоздаете контекст и дважды запускаете приложение.
SpringApplication.run
уже возвращает контекст приложения, используйте его вместо этого.