diff --git a/src/main/java/run/halo/app/plugin/PluginApplicationContext.java b/src/main/java/run/halo/app/plugin/PluginApplicationContext.java index 108de64b8..33ae252de 100644 --- a/src/main/java/run/halo/app/plugin/PluginApplicationContext.java +++ b/src/main/java/run/halo/app/plugin/PluginApplicationContext.java @@ -1,17 +1,6 @@ package run.halo.app.plugin; -import java.lang.reflect.Field; -import java.util.Set; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.PayloadApplicationEvent; -import org.springframework.context.event.ApplicationEventMulticaster; -import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.ResolvableType; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; /** * The generic IOC container for plugins. @@ -25,56 +14,6 @@ public class PluginApplicationContext extends GenericApplicationContext { private String pluginId; - /** - *

覆盖父类方法中判断context parent不为空时使用parent context广播事件的逻辑。 - * 如果主应用桥接事件到插件中且设置了parent会导致发布事件时死循环.

- * - * @param event the event to publish (may be an {@link ApplicationEvent} or a payload object - * to be turned into a {@link PayloadApplicationEvent}) - * @param eventType the resolved event type, if known - */ - @Override - protected void publishEvent(@NonNull Object event, @Nullable ResolvableType eventType) { - Assert.notNull(event, "Event must not be null"); - - // Decorate event as an ApplicationEvent if necessary - ApplicationEvent applicationEvent; - if (event instanceof ApplicationEvent) { - applicationEvent = (ApplicationEvent) event; - } else { - applicationEvent = new PayloadApplicationEvent<>(this, event); - if (eventType == null) { - eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType(); - } - } - - // Multicast right now if possible - or lazily once the multicaster is initialized - Set earlyApplicationEvents = getEarlyApplicationEvents(); - if (earlyApplicationEvents != null) { - earlyApplicationEvents.add(applicationEvent); - } else { - getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); - } - } - - private ApplicationEventMulticaster getApplicationEventMulticaster() { - ConfigurableListableBeanFactory beanFactory = getBeanFactory(); - return beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, - ApplicationEventMulticaster.class); - } - - @SuppressWarnings("unchecked") - protected Set getEarlyApplicationEvents() { - try { - Field earlyApplicationEventsField = - AbstractApplicationContext.class.getDeclaredField("earlyApplicationEvents"); - return (Set) earlyApplicationEventsField.get(this); - } catch (NoSuchFieldException | IllegalAccessException e) { - // ignore this exception - return null; - } - } - public String getPluginId() { return pluginId; } diff --git a/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java b/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java index 1950ca2a2..11ffd5722 100644 --- a/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java +++ b/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java @@ -24,9 +24,12 @@ public class PluginApplicationInitializer { protected final HaloPluginManager haloPluginManager; private final ExtensionContextRegistry contextRegistry = ExtensionContextRegistry.getInstance(); + private final SharedApplicationContextHolder sharedApplicationContextHolder; public PluginApplicationInitializer(HaloPluginManager springPluginManager) { this.haloPluginManager = springPluginManager; + sharedApplicationContextHolder = springPluginManager.getRootApplicationContext() + .getBean(SharedApplicationContextHolder.class); } public ApplicationContext getRootApplicationContext() { @@ -40,7 +43,11 @@ public class PluginApplicationInitializer { StopWatch stopWatch = new StopWatch("initialize-plugin-context"); stopWatch.start("Create PluginApplicationContext"); PluginApplicationContext pluginApplicationContext = new PluginApplicationContext(); - pluginApplicationContext.setParent(getRootApplicationContext()); + + if (sharedApplicationContextHolder != null) { + pluginApplicationContext.setParent(sharedApplicationContextHolder.getInstance()); + } + pluginApplicationContext.setClassLoader(pluginClassLoader); // populate plugin to plugin application context pluginApplicationContext.setPluginId(pluginId); diff --git a/src/main/java/run/halo/app/plugin/PluginCompositeRouterFunction.java b/src/main/java/run/halo/app/plugin/PluginCompositeRouterFunction.java index 906bf5247..6b9fc9611 100644 --- a/src/main/java/run/halo/app/plugin/PluginCompositeRouterFunction.java +++ b/src/main/java/run/halo/app/plugin/PluginCompositeRouterFunction.java @@ -1,5 +1,6 @@ package run.halo.app.plugin; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -75,17 +76,19 @@ public class PluginCompositeRouterFunction implements RouterFunction reverseProxyRouterFunction = reverseProxyRouterFunctionFactory.create(pluginApplicationContext); - routerFunctions(pluginApplicationContext) - .stream() + List> routerFunctions = + routerFunctions(pluginApplicationContext); + + List> combinedRouterFunctions = + new ArrayList<>(routerFunctions); + if (reverseProxyRouterFunction != null) { + combinedRouterFunctions.add(reverseProxyRouterFunction); + } + + combinedRouterFunctions.stream() .reduce(RouterFunction::and) - .map(compositeRouterFunction -> { - if (reverseProxyRouterFunction != null) { - compositeRouterFunction.andOther(reverseProxyRouterFunction); - } - return compositeRouterFunction; - }) - .ifPresent(routerFunction -> { - routerFunctionRegistry.put(plugin.getPluginId(), routerFunction); + .ifPresent(compositeRouterFunction -> { + routerFunctionRegistry.put(plugin.getPluginId(), compositeRouterFunction); }); } @@ -98,9 +101,6 @@ public class PluginCompositeRouterFunction implements RouterFunction> routerFunctions( PluginApplicationContext applicationContext) { - // TODO: Since the parent of the ApplicationContext of the plugin is RootApplicationContext - // obtaining the RouterFunction here will obtain the existing in the parent - // resulting in a loop when there is no matching route List> functions = applicationContext.getBeanProvider(RouterFunction.class) .orderedStream() diff --git a/src/main/java/run/halo/app/plugin/SharedApplicationContext.java b/src/main/java/run/halo/app/plugin/SharedApplicationContext.java new file mode 100644 index 000000000..fae42efe3 --- /dev/null +++ b/src/main/java/run/halo/app/plugin/SharedApplicationContext.java @@ -0,0 +1,15 @@ +package run.halo.app.plugin; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.GenericApplicationContext; + +/** + *

An {@link ApplicationContext} implementation shared by plugins.

+ *

Beans in the Core that need to be shared with plugins will be injected into this + * {@link SharedApplicationContext}.

+ * + * @author guqing + * @since 2.0.0 + */ +public class SharedApplicationContext extends GenericApplicationContext { +} diff --git a/src/main/java/run/halo/app/plugin/SharedApplicationContextHolder.java b/src/main/java/run/halo/app/plugin/SharedApplicationContextHolder.java new file mode 100644 index 000000000..490013435 --- /dev/null +++ b/src/main/java/run/halo/app/plugin/SharedApplicationContextHolder.java @@ -0,0 +1,64 @@ +package run.halo.app.plugin; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import run.halo.app.extension.DefaultSchemeManager; +import run.halo.app.extension.ExtensionClient; + +/** + *

This {@link SharedApplicationContextHolder} class is used to hold a singleton instance of + * {@link SharedApplicationContext}.

+ *

If sharedApplicationContext cache is null when calling the {@link #getInstance()} method, + * then it will call {@link #createSharedApplicationContext()} to create and cache it. Otherwise, + * it will be obtained directly.

+ *

It is thread safe.

+ * + * @author guqing + * @since 2.0.0 + */ +@Component +public class SharedApplicationContextHolder { + + private final ApplicationContext rootApplicationContext; + private volatile SharedApplicationContext sharedApplicationContext; + + public SharedApplicationContextHolder(ApplicationContext applicationContext) { + this.rootApplicationContext = applicationContext; + } + + /** + * Get singleton instance of {@link SharedApplicationContext}. + * + * @return a singleton instance of {@link SharedApplicationContext}. + */ + public SharedApplicationContext getInstance() { + if (this.sharedApplicationContext == null) { + synchronized (SharedApplicationContextHolder.class) { + if (this.sharedApplicationContext == null) { + this.sharedApplicationContext = createSharedApplicationContext(); + } + } + } + return this.sharedApplicationContext; + } + + SharedApplicationContext createSharedApplicationContext() { + // TODO Optimize creation timing + SharedApplicationContext sharedApplicationContext = new SharedApplicationContext(); + sharedApplicationContext.refresh(); + + DefaultListableBeanFactory beanFactory = + (DefaultListableBeanFactory) sharedApplicationContext.getBeanFactory(); + + // register shared object here + ExtensionClient extensionClient = rootApplicationContext.getBean(ExtensionClient.class); + beanFactory.registerSingleton("extensionClient", extensionClient); + DefaultSchemeManager defaultSchemeManager = + rootApplicationContext.getBean(DefaultSchemeManager.class); + beanFactory.registerSingleton("schemeManager", defaultSchemeManager); + // TODO add more shared instance here + + return sharedApplicationContext; + } +} diff --git a/src/test/java/run/halo/app/plugin/SharedApplicationContextHolderTest.java b/src/test/java/run/halo/app/plugin/SharedApplicationContextHolderTest.java new file mode 100644 index 000000000..0bf05ef13 --- /dev/null +++ b/src/test/java/run/halo/app/plugin/SharedApplicationContextHolderTest.java @@ -0,0 +1,37 @@ +package run.halo.app.plugin; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * Tests for {@link SharedApplicationContextHolder}. + * + * @author guqing + * @since 2.0.0 + */ +@SpringBootTest +@AutoConfigureTestDatabase +class SharedApplicationContextHolderTest { + + @Autowired + SharedApplicationContextHolder sharedApplicationContextHolder; + + @Test + void getInstance() { + SharedApplicationContext instance1 = sharedApplicationContextHolder.getInstance(); + SharedApplicationContext instance2 = sharedApplicationContextHolder.getInstance(); + assertThat(instance1).isNotNull(); + assertThat(instance1).isEqualTo(instance2); + } + + @Test + void createSharedApplicationContext() { + SharedApplicationContext sharedApplicationContext = + sharedApplicationContextHolder.createSharedApplicationContext(); + assertThat(sharedApplicationContext).isNotNull(); + } +} \ No newline at end of file