Spring AOP и JavaConfig в плагинах для Atlassian Jira
В этой статье разработаем плагин для Atlassian Jira, где с помощью JavaConfig определим бин с областью видимости прототип, залогируем вызовы методов бина, используя AOP, и выведем информацию из внешних бинов (ApplicationProperties, JiraAuthenticationContext и ConstantsManager).
Исходный код плагина можно взять вот здесь.
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.
com.atlassian.plugin
atlassian-spring-scanner-annotation
${atlassian.spring.scanner.version}
compile
Удалить зависимость atlassian-spring-scanner-runtime.
Изменить свойство atlassian.spring.scanner.version на 2.0.0
Добавить следующие зависимости:
org.springframework
spring-core
4.2.5.RELEASE
provided
org.springframework
spring-context
4.2.5.RELEASE
provided
Добавить в maven-jira-plugin в тэг instructions следующую строчку:
*
Данная строчка позволит плагину находить классы 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 = "" + helloWorld.getMessage() + "
";
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 = "" + helloWorld.getMessage() + "";
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!
Из логов мы видим, что логирование информации о вызываемых методах произошло.
Таким образом мы успешно справились с поставленными целями.