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
guqing 2022-10-09 11:36:30 +08:00 committed by GitHub
parent 18a3fc51a5
commit a4609f68d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 25 additions and 56 deletions

View File

@ -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();
}

View File

@ -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();

View File

@ -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")))

View File

@ -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();
}

View File

@ -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(

View File

@ -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)"

View File

@ -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());
}
}