Теперь это блог, который вы хотите прочитать, если вы заинтересованы в создании Spring MVC Webapp, который использует Aspect-Oriented Programming (AOP) в форме аннотаций Aspectj’s @Aspect
и @Before
для аудита посещения пользователем экрана.
Как я уже говорил в моем последнем блоге, аудит посещений пользователем экрана является одной из тех немногих сквозных проблем, которые очень хорошо решает АСП. Идея моего демо-кода заключается в том, что вы добавляете аннотацию к соответствующим контроллерам, и каждый раз, когда пользователь посещает страницу, этот визит записывается. Используя эту технику, вы можете создать изображение самых популярных экранов и, следовательно, самых популярных функциональных блоков в вашем приложении. Знание этих подробностей облегчает решение о том, куда направить ваши усилия по разработке, поскольку разработка тех частей вашего приложения, которые вряд ли кто-либо когда-либо использует, не окупается.
Для демонстрационного кода я создал простое приложение Spring MVC, которое имеет два экрана: домашняя страница и страница справки. Вдобавок к этому я создал простую аннотацию: @Audit
, которая используется для обозначения контроллера как @Audit
аудита (не все из них будут, особенно если вы решите проверять функциональные точки, а не отдельные экраны) и сообщать объект совета идентификатор экрана. Это я продемонстрировал в фрагменте кода ниже:
1
2
3
|
@Audit ( "Home" ) @RequestMapping (value = "/" , method = RequestMethod.GET) public String home(Locale locale, Model model) { |
Прежде чем приступить к рассмотрению AspectJ, сначала нужно создать стандартное веб-приложение Spring MVC с использованием Spring Template, предназначенного для этой работы:
Следующее, что нужно сделать, это внести целый ряд изменений в файл POM, как описано в моем предыдущем блоге . Они необходимы для того, чтобы все работало, хотя не все они необходимы; однако убедитесь, что вы добавили зависимость aspectJwearver
и удалили определение плагина AspectJ.
Приложение имеет два контроллера и два простых JSP. Первый контроллер — это HomeController
взятый из приложения Spring MVC, а второй — это HelpController
предназначенный для отображения справки на любой странице приложения. Я включил HelpController
showHelp(…)
ниже, но это только для полноты. В данном случае не имеет значения, что контроллеры делают, если есть пара для аудита.
01
02
03
04
05
06
07
08
09
10
11
12
|
@Controller () public class HelpController { @Audit ( "Help" ) // User has visited the help page @RequestMapping (value = "/help" , method = RequestMethod.GET) public String showHelp( @RequestParam int pageId, Model model) { String help = getHelpPage(pageId); model.addAttribute( "helpText" , help); return "help" ; } |
Из приведенного выше кода вы можете видеть, что оба моих метода @Audit
аннотацией @Audit
, поэтому следующим шагом является определение:
1
2
3
4
5
6
|
@Retention (RetentionPolicy.RUNTIME) @Target (ElementType.METHOD) public @interface Audit { String value(); } |
Ключевыми моментами в этом коде являются политика хранения и цель. Политика хранения должна быть установлена в RetentionPolicy.RUNTIME
, что означает, что компилятор не выбрасывает аннотацию и гарантирует, что он там, загруженный в JVM, во время выполнения. @Target
определяет, где вы можете применить аннотацию. В этом случае я хочу, чтобы он применялся только к методам, поэтому целью является ElementType.METHOD
. Аннотация ДОЛЖНА содержать значение, которое в этом случае используется для хранения имени экрана, который пользователь посещает в данный момент.
Следующая ключевая абстракция — это класс AuditAdvice
как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@Aspect public class AuditAdvice { @Autowired private AuditService auditService; /** * Advice for auditing a user's visit to a page. The rule is that the Before annotation * applies to any method in any class in the com.captaindebug.audit.controller package * where the class name ends in 'Controller' and the method is annotated by @Audit. * * @param auditAnnotation * Audit annotation holds the name of the screen we're auditing. */ @Before ( "execution(public String com.captaindebug.audit.controller.*Controller.*(..)) && @annotation(auditAnnotation) " ) public void myBeforeLogger(Audit auditAnnotation) { auditService.audit(auditAnnotation.value()); } } |
Это аннотируется двумя аннотациями AspectJ: @Aspect
и @Before
. Аннотация AuditAdvice
класс AuditAdvice
как аспект , в то время @Before
аннотация @Before
означает, что метод auditScreen(…)
перед любым методом, определение которого соответствует выражению, @Before
аргументом аннотации @Before
.
Это выражение идея довольно крутая. Я уже рассмотрел конструкцию выражения execution
в своем блоге об использовании @AfterThrowing AdviceJ в вашем приложении Spring ; однако, чтобы подвести итог, я собираюсь применить аннотированный метод @Before
к любому методу, который имеет публичную видимость, возвращает строку, находится в пакете com.captaindebug.audit.controller
и имеет слово Controller
как часть имя класса Другими словами, я затрудняю применение этого выражения выполнения ко всему, кроме контроллеров моего приложения, и эти контроллеры ДОЛЖНЫ быть аннотированы аннотацией @Audit
как описано выражением @annotation(auditAnnotation)
и auditScreen(…)
метода auditScreen(…)
Аргумент Audit auditAnnotation
. Это означает, что я не могу случайно применить аннотацию @Audit
ни к чему, кроме контроллера
Класс AuditAdvice
делегирует ответственность за фактический аудит AuditService
. Это фиктивная служба, поэтому вместо того, чтобы делать что-то полезное, например, сохранять событие аудита в базе данных, она просто добавляет его в файл журнала.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Service public class AuditService { private static Logger logger = LoggerFactory.getLogger(AuditService. class ); /** * Audit this screen against the current user name * * It's more useful to put this info into a database so that that you can count visits to * pages and figure out how often they're used. That way, you can focus your design on the * popular parts of your application. The logger is just for demo purposes. */ public void audit(String screenName) { String userName = getCurrentUser(); logger.info( "Audit: {} - {}" , userName, screenName); } /** * Get the current logged on user name by whatever mechanism available */ private String getCurrentUser() { return "Fred" ; } } |
Итак, этот код рассмотрен, все, что осталось сделать, — это разобраться в конфигурационном файле Spring, и здесь не так много дел. Во-первых, как и в любом приложении AOP, вам необходимо добавить следующую строку AOP:
1
|
< aop:aspectj-autoproxy /> |
… Вместе с деталями схемы:
1
2
|
xmlns:aop="http://www.springframework.org/schema/aop" |
И, во-вторых, вам нужно сообщить контексту Spring о ваших классах рекомендаций, обновив элемент context:components-scan
:
1
2
3
4
|
< context:component-scan base-package = "com.captaindebug.audit" > < context:include-filter type = "aspectj" expression = "com.captaindebug.audit.aspectj.AuditAdvice" /> </ context:component-scan > |
Вы также можете при желании удалить номера версий из конца URI расположения схемы. Например:
будет выглядеть так:
Причиной этого является то, что это упрощает обновление версий Spring в какой-то момент в будущем, поскольку URI схемы без номеров версий, похоже, указывают на последнюю версию этой схемы.
Для полноты, мой конфигурационный файл выглядит так:
01
02
03
04
05
06
07
08
09
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
35
36
37
|
<? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure --> <!-- Enables the Spring MVC @Controller programming model --> < annotation-driven /> <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory --> < resources mapping = "/resources/**" location = "/resources/" /> <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory --> < beans:bean class = "org.springframework.web.servlet.view.InternalResourceViewResolver" > < beans:property name = "prefix" value = "/WEB-INF/views/" /> < beans:property name = "suffix" value = ".jsp" /> </ beans:bean > < aop:aspectj-autoproxy /> < context:component-scan base-package = "com.captaindebug.audit" > < context:include-filter type = "aspectj" expression = "com.captaindebug.audit.aspectj.AuditAdvice" /> </ context:component-scan > </ beans:beans > |
Наконец, при запуске приложения посещение пользователя на домашней странице записывается. Когда пользователь нажимает на ссылку справки, посещение страницы справки также записывается. Вывод в файле журнала выглядит примерно так:
1
2
3
|
INFO : com.captaindebug.audit.service.AuditService - Audit: Fred - Home INFO : com.captaindebug.audit.controller.HomeController - Welcome home! the client locale is en_US INFO : com.captaindebug.audit.service.AuditService - Audit: Fred - Help |
Код для этого и следующего блога доступен на github: https://github.com/roghughe/captaindebug/tree/master/audit-aspectj