Статьи

Аудит Spring MVC Webapp с AspectJ.

Теперь это блог, который вы хотите прочитать, если вы заинтересованы в создании 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/>

… Вместе с деталями схемы:

И, во-вторых, вам нужно сообщить контексту 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"?>
 
 <!-- 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