В этой статье разработаем плагин для Atlassian Jira, где с помощью JavaConfig определим бин с областью видимости прототип, залогируем вызовы методов бина, используя AOP, и выведем информацию из внешних бинов (ApplicationProperties, JiraAuthenticationContext и ConstantsManager).
Исходный код плагина можно взять вот здесь.
Для этого нужно открыть терминал и ввести:
На заданные в терминале вопросы нужно ответить вот так:
Необходимо изменить область видимости atlassian-spring-scanner-annotation с compile на provided.
Удалить зависимость atlassian-spring-scanner-runtime.
Изменить свойство atlassian.spring.scanner.version на 2.0.0
Добавить следующие зависимости:
Добавить в maven-jira-plugin в тэг instructions следующую строчку:
Данная строчка позволит плагину находить классы Spring во время исполнения.
HelloWorld.java
HelloWorldImpl.java
Класс принимает три внешних бина и выводит в методе getMessage данные из этих бинов.
JiraBeansImporter.java
Данный класс нужен лишь для того, чтобы JavaConfig увидел требуемые нам внешние бины.
HijackBeforeMethod.java
Данный класс логирует информацию перед вызовом метода объекта.
HijackAroundMethod.java
Данный класс логирует информацию перед вызовом и после вызова метода объекта.
В JavaConfig мы создаем:
Сервлеты будем использовать для тестирования нашего приложения.
Открываем терминал и выполняем:
Когда будет задан вопрос о типе создаваемого модуля, то выбираем 21 (Сервлет):
Далее на вопросы отвечаем следующим образом:
В результате у нас сформируются файлы MyServlet1.java и MyServlet2.java. Меняем код в этих файлах:
MyServlet1.java
Мы передаем в сервлет наш прокси бин helloWorldBeforeProxy. То есть при обращении к HelloWorld будет логироваться информация перед вызовом методов HelloWorld.
MyServlet2.java
Мы передаем в сервлет наш прокси бин helloWorldAroundProxy. То есть при обращении к HelloWorld будет логироваться информация перед и после вызова методов HelloWorld.
Мы должны проверить следующее:
Откроем терминал и введем:
После того, как Jira запустилась, откроем Jira в браузере по адресу localhost:2990/jira/ и залогинимся в Jira.
Установим уровень логирования пакета ru.matveev.alexey в DEBUG. Для этого нужно зайти в System-> Logging and Profiling:
Переходим по адресу localhost:2990/jira/plugins/servlet/myservlet1 для вызыва MyServlet1.java.
Из скриншота мы видим, что наш плагин работает, и информация из внешних бинов передается. Мы успешно проверили первые два пункта.
Для проверки того, что helloWorld имеет область видимости прототип, мы обновим страницу в браузере:
Мы видим, что сообщение «Hello World!!!» заменилось на «message changed MyServlet». Так как у нас у helloWorld область видимости прототип, то при обращении к MyServlet2.java мы должны получить значение «Hello World!!!» (если ли бы область видимости была синглетон, то мы получили бы сообщение «message changed MyServlet»).
Обращаемся к MyServlet2.java по адресу localhost:2990/jira/plugins/servlet/myservlet2:
Мы видим, что в надписи появилось сообщение «Hello World!!!», а значит действительно область видимости helloWorld прототип.
Дальше проверим была ли залогирована информация при обращении к методам helloWorld. В логах мы находим:
Из логов мы видим, что логирование информации о вызываемых методах произошло.
Таким образом мы успешно справились с поставленными целями.
Исходный код плагина можно взять вот здесь.
1. Создадим плагин.
Для этого нужно открыть терминал и ввести:
atlas-create-jira-plugin
На заданные в терминале вопросы нужно ответить вот так:
Define value for groupId: : ru.matveev.alexey.plugins.spring
Define value for artifactId: : spring-tutorial
Define value for version: 1.0.0-SNAPSHOT: :
Define value for package: ru.matveev.alexey.plugins.spring: :
groupId: ru.matveev.alexey.plugins.spring
artifactId: spring-tutorial
version: 1.0.0-SNAPSHOT
package: ru.matveev.alexey.plugins.spring
Y: : Y
2. Внесем изменения в pom.xml
Необходимо изменить область видимости atlassian-spring-scanner-annotation с compile на provided.
<dependency>
<groupId>com.atlassian.plugin</groupId>
<artifactId>atlassian-spring-scanner-annotation</artifactId>
<version>${atlassian.spring.scanner.version}</version>
<scope>compile</scope>
</dependency>
Удалить зависимость atlassian-spring-scanner-runtime.
Изменить свойство atlassian.spring.scanner.version на 2.0.0
Добавить следующие зависимости:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.5.RELEASE</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.5.RELEASE</version>
<scope>provided</scope>
</dependency>
Добавить в maven-jira-plugin в тэг instructions следующую строчку:
<DynamicImport-Package>*</DynamicImport-Package>
Данная строчка позволит плагину находить классы Spring во время исполнения.
3. Создадим интерфейс и имплементацию сущности HelloWorld.
HelloWorld.java
package ru.matveev.alexey.plugins.spring.api;
public interface HelloWorld {
String getMessage();
void setMessage(String value);
}
HelloWorldImpl.java
package ru.matveev.alexey.plugins.spring.impl;
import com.atlassian.jira.config.ConstantsManager;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.sal.api.ApplicationProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.matveev.alexey.plugins.spring.api.HelloWorld;
public class HelloWorldImpl implements HelloWorld {
private static final Logger LOG = LoggerFactory.getLogger(HelloWorldImpl.class);
private String message = "Hello World!!!";
private final ApplicationProperties applicationProperties;
private final ConstantsManager constantsManager;
private final JiraAuthenticationContext jiraAuthenticationContext;
public HelloWorldImpl(ApplicationProperties applicationProperties, JiraAuthenticationContext jiraAuthenticationContext, ConstantsManager constantsManager) {
this.applicationProperties = applicationProperties;
this.constantsManager = constantsManager;
this.jiraAuthenticationContext = jiraAuthenticationContext;
}
public String getMessage() {
LOG.debug("getMessage executed");
return applicationProperties.getDisplayName() + " logged user: " + jiraAuthenticationContext.getLoggedInUser().getName() + " default priority: " + constantsManager.getDefaultPriority().getName() + " " + this.message;
}
public void setMessage(String value) {
LOG.debug("setMessage executed");
message = value;
}
}
Класс принимает три внешних бина и выводит в методе getMessage данные из этих бинов.
4. Создадим класс для импотра экспортируемых Jira бинов.
JiraBeansImporter.java
import com.atlassian.jira.config.ConstantsManager;
package ru.matveev.alexey.plugins.spring.impl;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.ApplicationProperties;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class JiraBeansImporter {
@Inject
public JiraBeansImporter(@ComponentImport ApplicationProperties applicationProperties,
@ComponentImport JiraAuthenticationContext jiraAuthenticationContext,
@ComponentImport ConstantsManager constantsManager
) {
}
}
Данный класс нужен лишь для того, чтобы JavaConfig увидел требуемые нам внешние бины.
5. Создадим классы для логирования данных о вызываемых методах объектов.
HijackBeforeMethod.java
Данный класс логирует информацию перед вызовом метода объекта.
package ru.matveev.alexey.plugins.spring.aop;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.MethodBeforeAdvice;
public class HijackBeforeMethod implements MethodBeforeAdvice
{
private static final Logger LOG = LoggerFactory.getLogger(HijackBeforeMethod.class);
public void before(Method method, Object[] objects, Object o) throws Throwable {
LOG.debug("HijackBeforeMethod : method {} in", method.toString());
}
}
HijackAroundMethod.java
Данный класс логирует информацию перед вызовом и после вызова метода объекта.
package ru.matveev.alexey.plugins.spring.aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
public class HijackAroundMethod implements MethodInterceptor {
private static final Logger LOG = LoggerFactory.getLogger(HijackAroundMethod.class);
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
LOG.debug("HijackAroundMethod : Method name : "
+ methodInvocation.getMethod().getName());
LOG.debug("HijackAroundMethod : Method arguments : "
+ Arrays.toString(methodInvocation.getArguments()));
LOG.debug("HijackAroundMethod : Before method hijacked!");
try {
Object result = methodInvocation.proceed();
LOG.debug("HijackAroundMethod : Before after hijacked!");
return result;
} catch (IllegalArgumentException e) {
LOG.debug("HijackAroundMethod : Throw exception hijacked!");
throw e;
}
}
}
6. Создадим JavaConfig
package ru.matveev.alexey.plugins.spring.config;
import com.atlassian.jira.config.ConstantsManager;
import com.atlassian.jira.security.JiraAuthenticationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import ru.matveev.alexey.plugins.spring.aop.HijackAroundMethod;
import ru.matveev.alexey.plugins.spring.aop.HijackBeforeMethod;
import com.atlassian.sal.api.ApplicationProperties;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ru.matveev.alexey.plugins.spring.api.HelloWorld;
import ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl;
@Component
@Configuration
public class Config{
@Bean(name = "helloWorld")
@Scope("prototype")
public HelloWorld helloWorld(ApplicationProperties applicationProperties,
JiraAuthenticationContext jiraAuthenticationContext,
ConstantsManager constantsManager) {
return new HelloWorldImpl(applicationProperties, jiraAuthenticationContext, constantsManager);
}
@Bean(name="hijackBeforeMethodBean")
public HijackBeforeMethod hijackBeforeMethod() {
return new HijackBeforeMethod();
}
@Bean(name="hijackAroundMethodBean")
public HijackAroundMethod hijackAroudnMethod() {
return new HijackAroundMethod();
}
@Bean (name = "helloWorldBeforeProxy")
@Scope("prototype")
public ProxyFactoryBean proxyBeforeFactoryBean(ApplicationProperties applicationProperties,
JiraAuthenticationContext jiraAuthenticationContext,
ConstantsManager constantsManager) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(helloWorld(applicationProperties,jiraAuthenticationContext,constantsManager));
proxyFactoryBean.setProxyTargetClass(true);
proxyFactoryBean.setInterceptorNames("hijackBeforeMethodBean");
return proxyFactoryBean;
}
@Bean (name = "helloWorldAroundProxy")
@Scope("prototype")
public ProxyFactoryBean proxyAroundFactoryBean(ApplicationProperties applicationProperties,
JiraAuthenticationContext jiraAuthenticationContext,
ConstantsManager constantsManager) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(helloWorld(applicationProperties,jiraAuthenticationContext,constantsManager));
proxyFactoryBean.setProxyTargetClass(true);
proxyFactoryBean.setInterceptorNames("hijackAroundMethodBean");
return proxyFactoryBean;
}
}
В JavaConfig мы создаем:
- бин helloWorld с областью видимости прототип, что означает, что инстанс бина будет создаваться каждый раз при обращении к нему.
- бины hijackBeforeMethodBean и hijackAroundMethodBean, которые логируют информацию перед и после вызовов методов объектов.
- бины helloWorldBeforeProxy и helloWorldAroundProxy, которые проксируют бин helloWorld и при обращении к методам бина helloWorld логируют информацию с помощью бинов hijackBeforeMethodBean и hijackAroundMethodBean
7. Создадим два сервелета.
Сервлеты будем использовать для тестирования нашего приложения.
Открываем терминал и выполняем:
atlas-create-jira-plugin-module
Когда будет задан вопрос о типе создаваемого модуля, то выбираем 21 (Сервлет):
Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 21
Далее на вопросы отвечаем следующим образом:
Enter New Classname MyServlet: : MyServlet1
Enter Package Name ru.matveev.alexey.plugins.spring.servlet: :
Show Advanced Setup? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : Y
Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 21
Enter New Classname MyServlet: : MyServlet2
Enter Package Name ru.matveev.alexey.plugins.spring.servlet: :
Show Advanced Setup? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : N
В результате у нас сформируются файлы MyServlet1.java и MyServlet2.java. Меняем код в этих файлах:
MyServlet1.java
Мы передаем в сервлет наш прокси бин helloWorldBeforeProxy. То есть при обращении к HelloWorld будет логироваться информация перед вызовом методов HelloWorld.
package ru.matveev.alexey.plugins.spring.servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import ru.matveev.alexey.plugins.spring.api.HelloWorld;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyServlet1 extends HttpServlet{
private static final Logger log = LoggerFactory.getLogger(MyServlet1.class);
private final HelloWorld helloWorld;
@Inject
public MyServlet1(@Qualifier("helloWorldBeforeProxy") HelloWorld helloWorld) {
this.helloWorld = helloWorld;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
log.debug("MyServlet1 called");
resp.setContentType("text/html");
String message = "<html><body>" + helloWorld.getMessage() + "</body></html>";
helloWorld.setMessage("message changed MyServlet");
resp.getWriter().write(message);
}
}
MyServlet2.java
Мы передаем в сервлет наш прокси бин helloWorldAroundProxy. То есть при обращении к HelloWorld будет логироваться информация перед и после вызова методов HelloWorld.
package ru.matveev.alexey.plugins.spring.servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import ru.matveev.alexey.plugins.spring.api.HelloWorld;
import javax.inject.Inject;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyServlet2 extends HttpServlet{
private static final Logger log = LoggerFactory.getLogger(MyServlet2.class);
private final HelloWorld helloWorld;
@Inject
public MyServlet2(@Qualifier("helloWorldAroundProxy") HelloWorld helloWorld) {
this.helloWorld = helloWorld;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
log.debug("MyServlet2 called");
resp.setContentType("text/html");
String message = "<html><body>" + helloWorld.getMessage() + "</body></html>";
helloWorld.setMessage("message changed MyServlet");
resp.getWriter().write(message);
}
}
8. Проверим результат.
Мы должны проверить следующее:
- Наш плагин запускается.
- Выдается информации из имортируемых нами внешних бинов (ApplicationProperties, JiraAuthenticationContext, ConstantsManager)
- helloWorld рабоает в области видимости прототип.
- Логируется информация о вызовах методов helloWorld.
Откроем терминал и введем:
atlas-run
После того, как Jira запустилась, откроем Jira в браузере по адресу localhost:2990/jira/ и залогинимся в Jira.
Установим уровень логирования пакета ru.matveev.alexey в DEBUG. Для этого нужно зайти в System-> Logging and Profiling:
Переходим по адресу localhost:2990/jira/plugins/servlet/myservlet1 для вызыва MyServlet1.java.
Из скриншота мы видим, что наш плагин работает, и информация из внешних бинов передается. Мы успешно проверили первые два пункта.
Для проверки того, что helloWorld имеет область видимости прототип, мы обновим страницу в браузере:
Мы видим, что сообщение «Hello World!!!» заменилось на «message changed MyServlet». Так как у нас у helloWorld область видимости прототип, то при обращении к MyServlet2.java мы должны получить значение «Hello World!!!» (если ли бы область видимости была синглетон, то мы получили бы сообщение «message changed MyServlet»).
Обращаемся к MyServlet2.java по адресу localhost:2990/jira/plugins/servlet/myservlet2:
Мы видим, что в надписи появилось сообщение «Hello World!!!», а значит действительно область видимости helloWorld прототип.
Дальше проверим была ли залогирована информация при обращении к методам helloWorld. В логах мы находим:
[INFO] [talledLocalContainer] 2018-04-01 17:06:52,609 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [r.m.a.p.spring.servlet.MyServlet1] MyServlet1 called
[INFO] [talledLocalContainer] 2018-04-01 17:06:52,610 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [r.m.a.p.spring.aop.HijackBeforeMethod] HijackBeforeMethod : method public java.lang.String ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl.getMessage() in
[INFO] [talledLocalContainer] 2018-04-01 17:06:52,610 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [r.m.a.p.spring.impl.HelloWorldImpl] getMessage executed
[INFO] [talledLocalContainer] 2018-04-01 17:06:52,611 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [r.m.a.p.spring.aop.HijackBeforeMethod] HijackBeforeMethod : method public void ru.matveev.alexey.plugins.spring.impl.HelloWorldImpl.setMessage(java.lang.String) in
[INFO] [talledLocalContainer] 2018-04-01 17:06:52,611 http-nio-2990-exec-4 DEBUG admin 1026x406x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet1 [r.m.a.p.spring.impl.HelloWorldImpl] setMessage executed
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,024 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.servlet.MyServlet2] MyServlet2 called
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,025 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Method name : getMessage
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Method arguments : []
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Before method hijacked!
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.impl.HelloWorldImpl] getMessage executed
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Before after hijacked!
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Method name : setMessage
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Method arguments : [message changed MyServlet]
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Before method hijacked!
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.impl.HelloWorldImpl] setMessage executed
[INFO] [talledLocalContainer] 2018-04-01 17:06:55,026 http-nio-2990-exec-3 DEBUG admin 1026x407x1 1fz8ddf 127.0.0.1 /plugins/servlet/myservlet2 [r.m.a.p.spring.aop.HijackAroundMethod] HijackAroundMethod : Before after hijacked!
Из логов мы видим, что логирование информации о вызываемых методах произошло.
Таким образом мы успешно справились с поставленными целями.