Хабрахабр

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.

<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. Проверим результат.

Мы должны проверить следующее:

  1. Наш плагин запускается.
  2. Выдается информации из имортируемых нами внешних бинов (ApplicationProperties, JiraAuthenticationContext, ConstantsManager)
  3. helloWorld рабоает в области видимости прототип.
  4. Логируется информация о вызовах методов 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!

Из логов мы видим, что логирование информации о вызываемых методах произошло.
Таким образом мы успешно справились с поставленными целями.

Показать больше

Похожие публикации

Кнопка «Наверх»