mirror of https://github.com/halo-dev/halo
fix: the exception of plugin context has been closed when plugin restart (#2518)
#### What type of PR is this? /kind bug /area core /milestone 2.0 #### What this PR does / why we need it: BasePlugin 持有了一个当前插件的 PluginApplicationContext 当插件停止后重新启用会获取到旧的 PluginApplicationContext,之前之所以这样写的目的只是插件的这个 BasePlugin 不支持依赖注入,目前已经支持所以不在这样解决问题 日志: ``` Caused by: java.lang.IllegalStateException: run.halo.app.plugin.PluginApplicationContext@6baf2671 has been closed already at org.springframework.context.support.AbstractApplicationContext.assertBeanFactoryActive(AbstractApplicationContext.java:1125) ~[spring-context-6.0.0-M5.jar:6.0.0-M5] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1158) ~[spring-context-6.0.0-M5.jar:6.0.0-M5] at run.halo.app.plugin.SpringExtensionFactory.create(SpringExtensionFactory.java:102) ~[classes/:2.0.0-alpha.1] at org.pf4j.ExtensionWrapper.getExtension(ExtensionWrapper.java:37) ~[pf4j-3.7.0.jar:3.7.0] at org.pf4j.AbstractPluginManager.getExtensions(AbstractPluginManager.java:971) ~[pf4j-3.7.0.jar:3.7.0] at run.halo.app.plugin.HaloPluginManager.getExtensions(HaloPluginManager.java:118) ~[classes/:2.0.0-alpha.1] at run.halo.app.plugin.ExtensionComponentsFinder.getExtensions(ExtensionComponentsFinder.java:36) ~[classes/:2.0.0-alpha.1] at run.halo.app.theme.dialect.CommentElementTagProcessor.doProcess(CommentElementTagProcessor.java:49) ~[classes/:2.0.0-alpha.1] at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.ProcessorTemplateHandler.handleStandaloneElement(ProcessorTemplateHandler.java:918) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.StandaloneElementTag.beHandled(StandaloneElementTag.java:228) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.Model.process(Model.java:282) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.ProcessorTemplateHandler.handleStandaloneElement(ProcessorTemplateHandler.java:1204) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.StandaloneElementTag.beHandled(StandaloneElementTag.java:228) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.Model.process(Model.java:282) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1587) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.TemplateModel.process(TemplateModel.java:136) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:592) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1103) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1077) ~[thymeleaf-3.1.0.M2.jar:3.1.0.M2] at run.halo.app.theme.engine.SpringWebFluxTemplateEngine.lambda$createFullStream$0(SpringWebFluxTemplateEngine.java:202) ~[classes/:2.0.0-alpha.1] at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:58) ~[reactor-core-3.5.0-M4.jar:3.5.0-M4] at reactor.core.publisher.Mono.subscribe(Mono.java:4321) ~[reactor-core-3.5.0-M4.jar:3.5.0-M4] at reactor.core.publisher.MonoSubscribeOn$SubscribeOnSubscriber.run(MonoSubscribeOn.java:126) ~[reactor-core-3.5.0-M4.jar:3.5.0-M4] at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84) ~[reactor-core-3.5.0-M4.jar:3.5.0-M4] at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37) ~[reactor-core-3.5.0-M4.jar:3.5.0-M4] at java.base/java.util.concurrent.FutureTask.run(Unknown Source) ~[na:na] at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:na] at java.base/java.lang.Thread.run(Unknown Source) ~[na:na] ``` #### Which issue(s) this PR fixes: Fixes # #### Special notes for your reviewer: how to test: 1. 安装一个插件 2. 启用它再停止它,如此反复,没有抛出异常即可 /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2524/head
parent
18a3fc51a5
commit
a4609f68d1
|
@ -1,7 +1,6 @@
|
|||
package run.halo.app.config;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -54,6 +53,7 @@ import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
|||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||
import run.halo.app.theme.router.TemplateRouteManager;
|
||||
|
@ -183,10 +183,12 @@ public class ExtensionConfiguration {
|
|||
}
|
||||
|
||||
@Bean
|
||||
Controller attachmentController(ExtensionClient client, PluginManager pluginManager,
|
||||
Controller attachmentController(ExtensionClient client,
|
||||
ExtensionComponentsFinder extensionComponentsFinder,
|
||||
ExternalUrlSupplier externalUrl) {
|
||||
return new ControllerBuilder("attachment-controller", client)
|
||||
.reconciler(new AttachmentReconciler(client, pluginManager, externalUrl))
|
||||
.reconciler(
|
||||
new AttachmentReconciler(client, extensionComponentsFinder, externalUrl))
|
||||
.extension(new Attachment())
|
||||
.build();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.springdoc.core.fn.builders.requestbody.Builder;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -43,6 +42,7 @@ import run.halo.app.extension.ReactiveExtensionClient;
|
|||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.extension.router.IListRequest;
|
||||
import run.halo.app.extension.router.IListRequest.QueryListRequest;
|
||||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
@ -50,11 +50,12 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
|||
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final ExtensionComponentsFinder extensionComponentsFinder;
|
||||
|
||||
public AttachmentEndpoint(ReactiveExtensionClient client, PluginManager pluginManager) {
|
||||
public AttachmentEndpoint(ReactiveExtensionClient client,
|
||||
ExtensionComponentsFinder extensionComponentsFinder) {
|
||||
this.client = client;
|
||||
this.pluginManager = pluginManager;
|
||||
this.extensionComponentsFinder = extensionComponentsFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -108,7 +109,7 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
|||
})
|
||||
// find the proper handler to handle the attachment
|
||||
.flatMap(uploadOption -> Flux.fromIterable(
|
||||
pluginManager.getExtensions(AttachmentHandler.class))
|
||||
extensionComponentsFinder.getExtensions(AttachmentHandler.class))
|
||||
.concatMap(uploadHandler -> uploadHandler.upload(uploadOption)
|
||||
.doOnNext(attachment -> {
|
||||
var spec = attachment.getSpec();
|
||||
|
|
|
@ -6,7 +6,6 @@ import java.util.HashSet;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
@ -22,20 +21,22 @@ import run.halo.app.extension.controller.Reconciler;
|
|||
import run.halo.app.extension.controller.Reconciler.Request;
|
||||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
|
||||
@Slf4j
|
||||
public class AttachmentReconciler implements Reconciler<Request> {
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final ExtensionComponentsFinder extensionComponentsFinder;
|
||||
|
||||
private final ExternalUrlSupplier externalUrl;
|
||||
|
||||
public AttachmentReconciler(ExtensionClient client, PluginManager pluginManager,
|
||||
public AttachmentReconciler(ExtensionClient client,
|
||||
ExtensionComponentsFinder extensionComponentsFinder,
|
||||
ExternalUrlSupplier externalUrl) {
|
||||
this.client = client;
|
||||
this.pluginManager = pluginManager;
|
||||
this.extensionComponentsFinder = extensionComponentsFinder;
|
||||
this.externalUrl = externalUrl;
|
||||
}
|
||||
|
||||
|
@ -51,7 +52,7 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
|||
client.fetch(ConfigMap.class, policy.getSpec().getConfigMapRef().getName())
|
||||
.orElseThrow();
|
||||
var deleteOption = new DeleteOption(attachment, policy, configMap);
|
||||
Flux.fromIterable(pluginManager.getExtensions(AttachmentHandler.class))
|
||||
Flux.fromIterable(extensionComponentsFinder.getExtensions(AttachmentHandler.class))
|
||||
.concatMap(handler -> handler.delete(deleteOption)).next().switchIfEmpty(
|
||||
Mono.error(() -> new NotFoundException(
|
||||
"No suitable handler found to delete the attachment")))
|
||||
|
|
|
@ -14,27 +14,10 @@ import org.pf4j.PluginWrapper;
|
|||
@Slf4j
|
||||
public class BasePlugin extends Plugin {
|
||||
|
||||
private PluginApplicationContext applicationContext;
|
||||
|
||||
public BasePlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Lazy initialization plugin application context,
|
||||
* avoid being unable to get context when system start scan plugin.</p>
|
||||
* <p>The plugin application context is not created until the plug-in is started.</p>
|
||||
*
|
||||
* @return Plugin application context.
|
||||
*/
|
||||
public final synchronized PluginApplicationContext getApplicationContext() {
|
||||
if (applicationContext == null) {
|
||||
applicationContext =
|
||||
getPluginManager().getPluginApplicationContext(this.wrapper.getPluginId());
|
||||
}
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
private HaloPluginManager getPluginManager() {
|
||||
return (HaloPluginManager) getWrapper().getPluginManager();
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.DefaultPluginManager;
|
||||
import org.pf4j.ExtensionFactory;
|
||||
|
@ -26,7 +24,6 @@ import org.springframework.beans.BeansException;
|
|||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.plugin.event.HaloPluginBeforeStopEvent;
|
||||
import run.halo.app.plugin.event.HaloPluginLoadedEvent;
|
||||
|
@ -111,24 +108,6 @@ public class HaloPluginManager extends DefaultPluginManager
|
|||
return new YamlPluginDescriptorFinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getExtensions(Class<T> type) {
|
||||
// we will collect implementations from Halo core at last.
|
||||
return Stream.concat(
|
||||
this.getExtensions(extensionFinder.find(type))
|
||||
.stream(),
|
||||
rootApplicationContext.getBeansOfType(type)
|
||||
.values()
|
||||
.stream()
|
||||
.sorted(AnnotationAwareOrderComparator.INSTANCE))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> getExtensions(Class<T> type, String pluginId) {
|
||||
return this.getExtensions(extensionFinder.find(type, pluginId));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void firePluginStateEvent(PluginStateEvent event) {
|
||||
rootApplicationContext.publishEvent(
|
||||
|
|
|
@ -165,7 +165,9 @@ public class SpringExtensionFactory implements ExtensionFactory {
|
|||
" Extension class ' " + nameOf(extensionClass) + "' belongs to halo-plugin '"
|
||||
+ nameOf(plugin)
|
||||
+ "' and will be autowired by using its application context.");
|
||||
applicationContext = ((BasePlugin) plugin).getApplicationContext();
|
||||
applicationContext = ExtensionContextRegistry.getInstance()
|
||||
.getByPluginId(plugin.getWrapper().getPluginId());
|
||||
return Optional.of(applicationContext);
|
||||
} else if (this.pluginManager instanceof HaloPluginManager && plugin != null) {
|
||||
log.debug(" Extension class ' " + nameOf(extensionClass)
|
||||
+ "' belongs to a non halo-plugin (or main application)"
|
||||
|
|
|
@ -16,7 +16,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.pf4j.PluginManager;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
@ -29,6 +28,7 @@ import run.halo.app.extension.ConfigMap;
|
|||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class AttachmentEndpointTest {
|
||||
|
@ -37,7 +37,7 @@ class AttachmentEndpointTest {
|
|||
ReactiveExtensionClient client;
|
||||
|
||||
@Mock
|
||||
PluginManager pluginManager;
|
||||
ExtensionComponentsFinder extensionComponentsFinder;
|
||||
|
||||
@InjectMocks
|
||||
AttachmentEndpoint endpoint;
|
||||
|
@ -116,7 +116,8 @@ class AttachmentEndpointTest {
|
|||
attachment.setMetadata(metadata);
|
||||
|
||||
when(handler.upload(any())).thenReturn(Mono.just(attachment));
|
||||
when(pluginManager.getExtensions(AttachmentHandler.class)).thenReturn(List.of(handler));
|
||||
when(extensionComponentsFinder.getExtensions(AttachmentHandler.class)).thenReturn(
|
||||
List.of(handler));
|
||||
when(client.create(attachment)).thenReturn(Mono.just(attachment));
|
||||
|
||||
var builder = new MultipartBodyBuilder();
|
||||
|
@ -143,7 +144,7 @@ class AttachmentEndpointTest {
|
|||
verify(client).get(Policy.class, "fake-policy");
|
||||
verify(client).get(ConfigMap.class, "fake-configmap");
|
||||
verify(client).create(attachment);
|
||||
verify(pluginManager).getExtensions(AttachmentHandler.class);
|
||||
verify(extensionComponentsFinder).getExtensions(AttachmentHandler.class);
|
||||
verify(handler).upload(any());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue