Теперь это блог, который вы хотите прочитать, если вы заинтересованы в создании 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
|
@Aspectpublic 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
|
@Servicepublic 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 - HomeINFO : com.captaindebug.audit.controller.HomeController - Welcome home! the client locale is en_USINFO : com.captaindebug.audit.service.AuditService - Audit: Fred - Help |
Код для этого и следующего блога доступен на github: https://github.com/roghughe/captaindebug/tree/master/audit-aspectj
