How to develop plugin pf4j in kr - songeunwoo/ngrinder GitHub Wiki
์ด ๋ฌธ์๋ ํ๊ตญ ์ฌ์ฉ์๋ค์ nGrinder PF4J plugin ๊ฐ๋ฐ ํธ์๋ฅผ ์ํด ์์ฑ ๋์๋ค. spring, java, maven์ผ๋ก ๊ฐ๋ฐํ๋ค๋ ์ ์ฌํ์ ์์ฑ ๋์๋ค.
opensource์ธ nGrinder๋ ์ธ๋ถ ๊ฐ๋ฐ์๋ ์ฐธ์ฌ ๊ฐ๋ฅํ๋๋ก Atlassian Plugin Framework(APF)๋ฅผ ์ ๊ณตํ์ฌ ๊ฐ ์ฌ์ฉ์๋ค์ ํ์์ ๋ง๋๋ก plugin์ ๊ฐ๋ฐ์ ๋๋ชจ ํ์์ผ๋, ๊ทธ ํ์ฉ๋๊ฐ ๋ฏธ๋ฏธํ์ฌ ์ ๊ทผํ๊ธฐ ์ฌ์ด Plugin Framework for Java (PF4J)๋ฅผ nGrinder 3.4 ๋ฒ์ ๋ถํฐ ์ฑํ ํ์๋ค.
- PF4J ์๊ฐ : https://github.com/decebals/pf4j
- PF4J-UPDATE : https://github.com/decebals/pf4j-update
- login plugin repo : https://github.com/naver/ngrinder-siteminder-sso
- networkoverflow repo : https://github.com/naver/ngrinder-networkoverflow
- jvm monitor repo : https://github.com/songeunwoo/ngrinder-jvm-monitor-plugin
- ์๋ ์ด๋ฏธ์ง๋ ์ ๋ฐ์ ์ธ ํ๋ฆ๋๋ฅผ ๋ํ๋ด์๋ค.
- ์๋ ์์ค ์ฝ๋๋ 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());
}
}
}
....
- PluginManager : https://github.com/naver/ngrinder/blob/master/ngrinder-controller/src/main/java/org/ngrinder/infra/plugin/PluginManager.java
-
nGrinder๋ ์ฌ์ฉ์๋ค์ด ํ์์ ๋ง์ถฐ ์์ ๋ก์ด Plugin์ ๊ฐ๋ฐํ ์ ์๋๋ก ์๋์ ๊ฐ์ ํ์ฅ ํฌ์ธํฐ๋ค์ ์ ๊ณตํ๊ณ ์๋ค. https://github.com/songeunwoo/ngrinder/wiki/How-to-develop-plugin
-
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>
```
- ์ฝ๋ ์์ฑ์ - ์ฝ๋ ์์ฑ์์๋ 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
```
- ๋น๋ํ ์์ฑ๋ jarํ์ผ์ .ngrinder/plugins ํด๋์ ๋ฃ์ด์ค ํ ngrinder๋ฅผ ์ฌ ์์ ํ๋ค.
- ์์กด์ฑ์ฃผ์
- 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;
}
}
```
-
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์ ๊ฐ๋ฐ ํ์์ผ๋ฉด ํ๋ค.