How to develop plugin pf4j in kr - songeunwoo/ngrinder GitHub Wiki

Plugin Framework for Java (PF4J)๊ฐœ๋ฐœ๋ฐฉ๋ฒ•

์ด ๋ฌธ์„œ๋Š” ํ•œ๊ตญ ์‚ฌ์šฉ์ž๋“ค์˜ nGrinder PF4J plugin ๊ฐœ๋ฐœ ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ž‘์„ฑ ๋˜์—ˆ๋‹ค. spring, java, maven์œผ๋กœ ๊ฐœ๋ฐœํ•œ๋‹ค๋Š” ์ „์žฌํ•˜์— ์ž‘์„ฑ ๋˜์—ˆ๋‹ค.

๋„์ž… ๋ฐฐ๊ฒฝ

opensource์ธ nGrinder๋Š” ์™ธ๋ถ€ ๊ฐœ๋ฐœ์ž๋„ ์ฐธ์—ฌ ๊ฐ€๋Šฅํ•˜๋„๋ก Atlassian Plugin Framework(APF)๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๊ฐ ์‚ฌ์šฉ์ž๋“ค์˜ ํ•„์š”์— ๋งž๋„๋ก plugin์„ ๊ฐœ๋ฐœ์„ ๋„๋ชจ ํ•˜์˜€์œผ๋‚˜, ๊ทธ ํ™œ์šฉ๋„๊ฐ€ ๋ฏธ๋ฏธํ•˜์—ฌ ์ ‘๊ทผํ•˜๊ธฐ ์‰ฌ์šด Plugin Framework for Java (PF4J)๋ฅผ nGrinder 3.4 ๋ฒ„์ „๋ถ€ํ„ฐ ์ฑ„ํƒ ํ•˜์˜€๋‹ค.

PF4J ํ๋ฆ„๋„

  • ์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ์ „๋ฐ˜์ ์ธ ํ๋ฆ„๋„๋ฅผ ๋‚˜ํƒ€๋‚ด์—ˆ๋‹ค.

mindmap

  • ์•„๋ž˜ ์†Œ์Šค ์ฝ”๋“œ๋Š” OnLoginRunnable์„ ๊ตฌํ˜„ํ•œ plugin์„ ํ˜ธ์ถœํ•˜์—ฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ด๋‹ค.
....
  @Autowired
	private PluginManager pluginManager;

  @Override
	public UserDetails loadUserByUsername(String userId) {
		for (OnLoginRunnable each : getPluginManager().getEnabledModulesByClass(OnLoginRunnable.class, defaultPlugin)) {
			User user = each.loadUser(userId);
			if (user != null) {
			  .....
      	return new SecuredUser(user, user.getAuthProviderClass());
			}
		}
	}
....

๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•

  1. nGrinder๋Š” ์‚ฌ์šฉ์ž๋“ค์ด ํ•„์š”์— ๋งž์ถฐ ์ž์œ ๋กœ์šด Plugin์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋„๋ก ์•„๋ž˜์™€ ๊ฐ™์€ ํ™•์žฅ ํฌ์ธํ„ฐ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค. https://github.com/songeunwoo/ngrinder/wiki/How-to-develop-plugin

  2. pom.xml ์„ค์ • - nGrinder repository๋ฅผ pom.xml์— ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. xml <repositories> .... <repository> <id>ngrinder.maven.repo</id> <url>https://github.com/naver/ngrinder/raw/ngrinder.maven.repo/releases</url> </repository> .... </repositories> - ํ™•์žฅ ํฌ์ธํ„ฐ๋ฅผ ์ƒ์† ๋ฐ›๊ธฐ ์œ„ํ•ด ngrinder-core์™€ pf4j์˜ dependency๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. xml <dependency> <groupId>org.ngrinder</groupId> <artifactId>ngrinder-core</artifactId> <version>${ngrinder.core.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>ro.fortsoft.pf4j</groupId> <artifactId>pf4j</artifactId> <version>${pf4j.version}</version> </dependency> - pom.xml ํŒŒ์ผ์— ํ•ด๋‹น ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ฒ€์ƒ‰์„ ์œ„ํ•œ manifestEntries ์„ค์ • ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. ํ•ด๋‹น ์ž…๋ ฅ ์ •๋ณด๋Š” comfile์‹œ ์•„๋ž˜ ๊ฒฝ๋กœ์—์„œ ํ™•์ธํ• ์ˆ˜ ์žˆ๋‹ค. target/classes/META-INF/MANIFEST.MF - ๊ธฐ์žฌ ๋˜์–ด์žˆ๋Š” ์ •๋ณด๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. Manifest-Version: 1.0 plugin-Id: siteminder-sso Archiver-Version: Plexus Archiver Built-By: NAVER plugin-Provider: NAVER plugin-Version: 4.0.0 plugin-Dependencies: plugin-Class: org.ngrinder.sso.SiteminderSSOPlugin Created-By: Apache Maven 3.0.5 Build-Jdk: 1.8.0_91 - pom.xml ๋นŒ๋“œ ์„ค์ •์€ ์•„๋ž˜์™€ ๊ฐ™์ด JARํŒŒ์ผ๋กœ ํ•ด์ฃผ๋ฉฐ, manifestEntries ์„ค์ •์€ ์•„๋ž˜๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ๋œ๋‹ค.

```xml
  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <java.version>1.7</java.version>
      <ngrinder.core.version>3.4</ngrinder.core.version>
      <plugin.id>ngrinder-jvm-monitor-plugin</plugin.id>
      <plugin.class>org.ngrinder.pluginName.helloPlugin</plugin.class>
      <plugin.version>1.0.0</plugin.version>
      <plugin.provider>NAVER</plugin.provider>
      <plugin.dependencies />
  </properties>

  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-jar-plugin</artifactId>
              <version>2.4</version>
              <configuration>
                  <archive>
                      <manifestEntries>
                          <plugin-Id>${plugin.id}</plugin-Id>
                          <plugin-Class>${plugin.class}</plugin-Class>
                          <plugin-Version>${plugin.version}</plugin-Version>
                          <plugin-Provider>${plugin.provider}</plugin-Provider>
                          <plugin-Dependencies>${plugin.dependencies}</plugin-Dependencies>
                      </manifestEntries>
                  </archive>
              </configuration>
          </plugin>
      </plugins>
  </build>
```
  1. ์ฝ”๋“œ ์ž‘์„ฑ์‹œ - ์ฝ”๋“œ ์ž‘์„ฑ์‹œ์—๋Š” PF4J plugin์„ ์ƒ์† ๋ฐ›์•„์ค€ํ›„ ์ƒ์„ฑ์ž๋กœ PluginWrapper๋ฅผ ์ฃผ์ž…ํ•ด ์ค€๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ ngrinder-core project๋Š” Maven Multi Module Project๋กœ ๊ตฌ์„ฑ์ด ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ createApplicationContext()๋Š” null์„ ๋ฐ˜ํ™˜ํ•ด ์ค€๋‹ค. ApplicationContext๋Š” ์˜์กด์„ฑ ์ฃผ์ž…ํŒŒํŠธ์—์„œ ๋‹ค์‹œ ์„ค๋ช… ํ•˜๊ณ˜๋‹ค.
```java
  public class NetworkOverFlow extends SpringPlugin {

    public NetworkOverFlow(PluginWrapper wrapper) {
      super(wrapper);
    }

    @Override
    protected ApplicationContext createApplicationContext() {
      return null;
    }

  }
```

- ๋‚ด๋ถ€ ํด๋ ˆ์Šค๋กœ ngrinder-core์˜ ํ™•์žฅ ํฌ์ธํ„ฐ๋“ค ์ค‘์—์„œ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ ํ•œ ํ›„ @Extension ์–ด๋…ธํ…Œ์ด์…˜์„ ์ฃผ์–ด PF4J ์ปดํŒŒ์ผ์‹œ์— ์ธ๋ฑ์Šค ๋ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค.
```java
    @Extension
    public static class NetworkOverFlowExtension implements OnTestSamplingRunnable {

        @Override
        public User loadUser(final String userId) {
            ....
        }

    }
```

- ์ธ๋ฑ์‹ฑ๋œ ํŒŒ์ผ ์ •๋ณด๋Š” ์•„๋ž˜ ๊ฒฝ๋กœ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ ํ•˜๋‹ค. loadPlugins์‹œ ํ•ด๋‹น ์ •๋ณด๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ load๊ฐ€ ๋œ๋‹ค.

```
target/classes/META-INF/extensions.idx
```
  1. ๋นŒ๋“œํ›„ ์ƒ์„ฑ๋œ jarํŒŒ์ผ์„ .ngrinder/plugins ํด๋”์— ๋„ฃ์–ด์ค€ ํ›„ ngrinder๋ฅผ ์žฌ ์‹œ์ž‘ ํ•œ๋‹ค.

customizing for ngrinder

  1. ์˜์กด์„ฑ์ฃผ์ž… - ngrinder-controller์™€ ngrinder-core ํ”„๋กœ์ ํŠธ๊ฐ„์— ์˜์กด์„ฑ ์ฃผ์ž… ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ ํ•˜๊ณ ์ž pf4j-spring์„ ์ถ”๊ฐ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์˜€๋‹ค. xml <dependency> <groupId>ro.fortsoft.pf4j</groupId> <artifactId>pf4j-spring</artifactId> <version>0.2.0</version> </dependency>
- pf4j-spring์˜ SpringExtensionFactory๋ฅผ ์ƒ์†๋ฐ›์•„ ngrinder์˜ ApplicationContext๋ฅผ ์ฃผ์ž…ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

```java
      pf4j-spring -  SpringExtensionFactory
      .......
      @Override
      public Object create(Class<?> extensionClass) {
          Object extension = createWithoutSpring(extensionClass);
          if (autowire && extension != null) {
              // test for SpringBean
              PluginWrapper pluginWrapper = pluginManager.whichPlugin(extensionClass);
              if (pluginWrapper != null) {
                  Plugin plugin = pluginWrapper.getPlugin();
                  if (plugin instanceof SpringPlugin) {
                      // autowire
                      ApplicationContext pluginContext = ((SpringPlugin) plugin).getApplicationContext();
                      pluginContext.getAutowireCapableBeanFactory().autowireBean(extension);
                  }
              }
          }

          return extension;
      }
      ......
```

    ์•„๋ž˜๋Š” Override ํ•ด์ค€ ์ฝ”๋“œ์ด๋‹ค.

```java
  @Component
  public class NGrinderSpringExtensionFactory extends SpringExtensionFactory {

    private final PluginManager pluginManager;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    public NGrinderSpringExtensionFactory(PluginManager pluginManager) {
    	super(pluginManager);
    	this.pluginManager = pluginManager;
    }

    protected void setApplicationContext(ApplicationContext applicationContext) {
    	this.applicationContext = applicationContext;
    }

    @Override
    public Object create(Class<?> extensionClass) {
    	Object extension = createWithoutSpring(extensionClass);
    	if (extension != null) {
    		PluginWrapper pluginWrapper = pluginManager.whichPlugin(extensionClass);
    		if (pluginWrapper != null) {
            applicationContext.getAutowireCapableBeanFactory().autowireBean(extension);
    		}
    	}
    	return extension;
    }

  }
```
  1. JARํŒŒ์ผ ์ง€์› - ๊ธฐ์กด PF4J๋Š” ์ปดํŒŒ์ผ๋œ ํด๋” ํŒŒ์ผ ํ˜•์‹๋งŒ ์ฝ๋„๋ก ๋˜์–ด ์žˆ์—ˆ๋‹ค. nGrinder์—์„œ๋Š” ๊ฐœ๋ฐœ์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•˜์—ฌ JARํŒŒ์ผ์„ ์ฝ์„์ˆ˜ ์žˆ๋„๋ก ๊ฐœ์„  ํ•˜์˜€๋‹ค.

    pf4j์˜ AbstractExtensionFinder๋ฅผ ์ƒ์†๋ฐ›์•„ NGrinderDefaultExtensionFinder์—์„œ ๊ตฌํ˜„ ํ•˜์˜€์œผ๋ฉฐ,readPluginsStorages()์—์„œ findResource๋ฅผ ์ฐพ์„๋•Œ์—๋Š” @Extension ์–ด๋…ธํ…Œ์ด์…˜์„ ์ฃผ์–ด ์ƒ์„ฑํ•ด ๋†“์€ "META-INF/extensions.idx" ํŒŒ์ผ์„ ์ฐธ์กฐ ํ•˜์˜€๋‹ค.

```java
  NGrinderServiceProviderExtensionFinder
  ....
  private final String EXTENSIONS_RESOURCE_PATH = "META-INF/extensions.idx";

  @Override
  public Map<String, Set<String>> readPluginsStorages() {
      log.debug("Reading extensions storages from plugins");
      Map<String, Set<String>> result = new LinkedHashMap<String, Set<String>>();

      List<PluginWrapper> plugins = pluginManager.getPlugins();
      for (PluginWrapper plugin : plugins) {
          String pluginId = plugin.getDescriptor().getPluginId();
          log.debug("Reading extensions storages for plugin '{}'", pluginId);
          final Set<String> bucket = new HashSet<String>();

          URL url = ((PluginClassLoader) plugin.getPluginClassLoader()).findResource(EXTENSIONS_RESOURCE_PATH);
          if (url != null) {
              Path extensionPath;
              if (url.toURI().getScheme().equals("jar")) {
                  FileSystem fileSystem = FileSystems.newFileSystem(url.toURI(), Collections.<String, Object>emptyMap());
                  extensionPath = fileSystem.getPath(EXTENSIONS_RESOURCE_PATH);
              } else {
                  extensionPath = Paths.get(url.toURI());
              }
              Files.walkFileTree(extensionPath, Collections.<FileVisitOption>emptySet(), 1, new SimpleFileVisitor<Path>() {

                  @Override
                  public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                      log.debug("Read '{}'", file);
                      Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8);
                      ServiceProviderExtensionStorage.read(reader, bucket);
                      return FileVisitResult.CONTINUE;
                  }

              });
          } else {
              log.debug("Cannot find '{}'", EXTENSIONS_RESOURCE_PATH);
          }
        ....
```

๋งˆ์น˜๋ฉฐ..

  • ๊ธฐ์กด Atlassian Plugin Framework(APF)๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์—๋Š” atlas-package ๋ผ๋Š” ๋ณ„๋„์˜ ๋นŒ๋“œ ๋ฐฉ์‹์œผ๋กœ comfile์„ ํ•ด์•ผํ•˜๋Š” ์–ด๋ ค์›€์ด ์žˆ์—ˆ์ง€๋งŒ, PF4J๋Š” ๊ธฐ์กด maven๋นŒ๋“œ ๋ฐฉ์‹ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅ ํ•˜๋‹ค.
  • APF์—์„œ PF4J๋กœ ๋ณ€๊ฒฝ ํ•˜์—ฌ ์•ฝ 4000KB resource๋ฅผ 80KB๋กœ ๊ฐ๋Ÿ‰ ํ• ์ˆ˜ ์žˆ์—ˆ๋‹ค.
  • ๋งŽ์€ ์‚ฌ์šฉ์ž๋ถ„๋“ค์ด PF4J๋ฅผ ํ™œ์šฉํ•˜์—ฌ, ํ•„์š”์— ๋งž์ถฐ ์ž์œ ๋กœ์šด Plugin์„ ๊ฐœ๋ฐœ ํ•˜์˜€์œผ๋ฉด ํ•œ๋‹ค.
โš ๏ธ **GitHub.com Fallback** โš ๏ธ