Теперь это блог, который вы хотите прочитать, если вы заинтересованы в создании Spring MVC Webapp, который использует Aspect-Oriented Programming (AOP) в форме аннотаций Aspectj’s @Aspect и @Before для аудита посещения пользователем экрана.
Как я уже говорил в моем последнем блоге Аудит посещений пользователем экрана — одна из немногих сквозных задач, которые очень хорошо решает Аспектно-ориентированное программирование (AOP). Идея моего демо-кода заключается в том, что вы добавляете аннотацию к соответствующим контроллерам, и каждый раз, когда пользователь посещает страницу, этот визит записывается. Используя эту технику, вы можете создать изображение самых популярных экранов и, следовательно, самых популярных функциональных блоков в вашем приложении. Знание этих подробностей облегчает решение о том, куда направить ваши усилия по разработке, поскольку разработка тех частей вашего приложения, которые вряд ли кто-либо когда-либо использует, не окупается.
Для демонстрационного кода я создал простое приложение Spring MVC, которое имеет два экрана: домашняя страница и страница справки. Вдобавок к этому я создал простую аннотацию: @Audit, которая используется для обозначения контроллера как требующего аудита (не все из них будут, особенно если вы решите проверять функциональные точки, а не отдельные экраны) и сообщать объект совета идентификатор экрана. Это я продемонстрировал в фрагменте кода ниже:
@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’sshowHelp (…) ниже, но это только для полноты. В данном случае не имеет значения, что контроллеры делают, если есть пара для аудита.
@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";}
Из приведенного выше кода вы можете видеть, что оба моих метода RequestMapping снабжены аннотацией @Audit, поэтому следующим шагом является определение:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Audit {
String value();}
Ключевыми моментами в этом коде являются политика хранения и цель. Политика хранения должна быть установлена на RetentionPolicy.RUNTIME, что означает, что компилятор не выбрасывает аннотацию и гарантирует, что он там, загруженный в JVM, во время выполнения. @Target определяет, где вы можете применить аннотацию. В этом случае я хочу, чтобы он применялся только к методам, поэтому целью является ElementType.METHOD. Аннотация ДОЛЖНА содержать значение, которое в этом случае используется для хранения имени экрана, который пользователь посещает в данный момент.
Следующая ключевая абстракция — это класс AuditAdvice, как показано ниже:
@Aspectpublic class AuditAdvice {
@Autowiredprivate 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. Аннотация @Aspect помечает класс AuditAdvice как аспект , в то время как аннотация @Before означает, что метод audScreen (…) вызывается перед любым методом, определение которого соответствует выражению, являющемуся аргументом аннотации @Before.
Это выражение идея довольно крутая. Я уже рассмотрел конструкцию выражения выполнения в своем блоге об использовании @AfterThrowing AdviceJ в вашем приложении Spring.; однако, чтобы подвести итог, я собираюсь применить аннотированный метод @Before к любому методу, который имеет публичную видимость, возвращает строку, находится в пакете com.captaindebug.audit.controller и имеет слово Controlleras, являющееся частью класса имя. Другими словами, я затрудняю применение этого выражения выполнения ко всему, кроме контроллеров моего приложения, и эти контроллеры ДОЛЖНЫ быть аннотированы аннотацией @Audit, как описано выражением @annotation (auditAnnotation) и аудитом метода AuditScreen (…) Аргумент аудита аннотации. Это означает, что я не могу случайно применить аннотацию @ Audit ни к чему, кроме контроллера
Класс AuditAdvice делегирует ответственность за фактический аудит AuditService. Это фиктивная служба, поэтому вместо того, чтобы делать что-то полезное, например, сохранять событие аудита в базе данных, она просто добавляет его в файл журнала.
@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:
<aop:aspectj-autoproxy/>
… вместе с деталями схемы:
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
И, во-вторых, вам нужно сообщить контексту Spring о ваших классах рекомендаций, обновив элемент context: components-scan:
<context:component-scan base-package="com.captaindebug.audit"> <context:include-filter type="aspectj" expression="com.captaindebug.audit.aspectj.AuditAdvice" /> </context:component-scan>
Вы также можете при желании удалить номера версий из конца URI расположения схемы. Например:
http://www.springframework.org/schema/context/spring-context.3.0.xsd
будет выглядеть так:
http://www.springframework.org/schema/context/spring-context.xsd
Причиной этого является то, что это упрощает обновление версий Spring в какой-то момент в будущем, так как URI схемы без номеров версий, похоже, указывают на последнюю версию этой схемы.
Для полноты, мой конфигурационный файл выглядит так:
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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>
Наконец, при запуске приложения посещение пользователя на домашней странице записывается. Когда пользователь нажимает на ссылку справки, посещение страницы справки также записывается. Вывод в файле журнала выглядит примерно так:
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