#spring #logging #aop
#spring #ведение журнала #aop
Вопрос:
Я написал простое приложение Spring2.5 для демонстрации / тестирования AOP; в частности, я хочу регистрировать вход и выход каждого метода каждого класса в определенном пакете. Это то, что у меня есть…
(примечание: я использую контроллеры аннотаций; Я опускаю детали, не связанные напрямую с aop, потому что моя базовая настройка работает нормально — я включаю только детали, связанные с aop — дайте мне знать, если вам нужно увидеть больше)
applicationContext.xml :
(...)
<bean id="loggerInterceptor" class="aspect.LoggerInterceptor" />
(...)
dispatcher-servlet.xml :
(...)
<aop:aspectj-autoproxy proxy-target-class="true" />
(...)
HomeController.java :
public class HomeController() {
public HomeController() { }
public ModelAndView get() {
System.out.println("In HomeController#get()...");
this.somePrivateMethod();
this.somePublicMethod();
return new ModelAndView( "home" );
}
private void somePrivateMethod() {
System.out.println("In HomeController#somePrivateMethod()...");
}
public void somePublicMethod() {
System.out.println("In HomeController#somePublicMethod()...");
}
}
LoggerInterceptor.java :
public class LoggerInterceptor {
@Pointcut("execution(* controller.*.*(..))")
private void anyOperationInControllerPackage() {
/* nothing to do here;
* this just defines that we want to catch all methods
* in the controller-package
*/
}
@Around("anyOperationInControllerPackage()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Entering " joinPoint.getSignature().getDeclaringTypeName() "#" joinPoint.getSignature().getName() "() using arguments: " Arrays.toString( joinPoint.getArgs() ) );
try {
Object result = joinPoint.proceed();
System.out.println("Leaving " joinPoint.getSignature().getDeclaringTypeName() "#" joinPoint.getSignature().getName() "()." );
return resu<
} catch (Throwable ex) {
ex.printStackTrace();
throw ex;
}
}
}
Вот что я получаю при вызове HomeController#get():
Entering controller.HomeController#get() using arguments: []
In HomeController#get()...
In HomeController#somePrivateMethod()...
In HomeController#somePublicMethod()...
Leaving controller.HomeController#get().
Как вы можете видеть, единственный метод, который перехватывается, — это HomeController#get() . Когда #get() вызывает #somePrivateMethod() или #somePublicMethod(), перехватчик их не улавливает. Я ожидал бы, по крайней мере, что #somePublicMethod () также будет пойман (и поскольку я использую cglib, я также ожидал бы, что #somePrivateMethod () будет пойман).
Итак, я предполагаю, что мой вопрос заключается в том, что мне нужно изменить / добавить, чтобы разрешить (по крайней мере) перехватывать все общедоступные методы в пакете controller, даже если другой метод в этом пакете вызвал их и сам был пойман первым???
Я надеюсь, что это имеет смысл. 😀
РЕДАКТИРОВАТЬ(25АПР2011 в 13:13)
applicationContext.xml :
(...)
<context:load-time-weaver /> <!-- added -->
<bean id="loggerInterceptor"... />
(...)
aop.xml :
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in this package -->
<include within="controller.*" />
</weaver>
<aspects>
<!-- use only this aspect for weaving -->
<aspect name="aspect.LoggerInterceptor" />
</aspects>
</aspectj>
В «Свойствах проекта» Netbean на вкладке «Выполнить» я добавил эту строку в «Параметры виртуальной машины» :
-javaagent:C:UsersbgreshamDocumentslibrariesspring-framework-2.5distweavingspring-agent.jar
Как и раньше, я не получаю никаких ошибок — я просто не получаю «вложенное» ведение журнала, которое я ищу.
???
Комментарии:
1. У меня такое же ограничение в 2022 году 🙂 Есть ли решение / обходной путь для этой ситуации?
Ответ №1:
Если вы используете Spring AOP, вы должны вызывать только метод, к которому был применен аспект через ссылку, возвращаемую Spring, не через this
, и я не думаю, что вы можете применять точечные сокращения и к частным методам (возможно, это неверно в последней части). Это потому, что Spring AOP применяет точечные сокращения через прокси-объект, а не путем перезаписи класса (что и делает AspectJ). Преимущество этого жесткого ограничения в том, что его гораздо проще заставить работать в контейнерах (я знаю по опыту, что Spring AOP отлично работает внутри Tomcat), потому что нет споров о том, какие биты куда подключены.
Лучший способ сделать это — разделить определение класса так, чтобы вы никогда не вызывали метод через this
, но если это невозможно, то вы всегда можете попробовать предоставить компоненту ссылку на себя, производную от Spring:
private HomeController self;
@Required
public void setSelf(HomeController self) { this.self = self; }
public ModelAndView get() {
System.out.println("In HomeController#get()...");
self.somePrivateMethod();
self.somePublicMethod();
return new ModelAndView( "home" );
}
(Это довольно аккуратно; self
это ключевое слово в ряде языков, но не в Java, поэтому относительно легко запомнить, для чего вы его используете.)
Комментарии:
1. В Spring docs упоминается еще один грубый трюк, но для этого требуется огромное количество связей, поэтому я не рекомендую даже смотреть. Хотя вышесказанное относительно аккуратно и определенно работает; я получил его в процессе развертывания.
2. @Donal: пожалуйста, простите меня, я новичок в Spring. 🙂 Но этот пример не совсем работает в моей среде. Я получаю следующую ошибку: Вызвано: java.lang. Исключение IllegalStateException: ContainerBase.addChild: start: исключение LifecycleException: org.springframework.beans.factory. Исключение BeanInitializationException: для компонента ‘HomeController’ требуется свойство ‘self’
3. Я понимаю, что традиционный способ — настроить ваш компонент в xml и предоставить элемент <property …>; но я использую почти исключительно автоматическое подключение и аннотации, поэтому HomeController не отображается в моем xml (и я бы предпочел сохранить его таким образом). Есть ли способ получить ссылку на self с помощью аннотаций? Или, может быть, в HomeController#constructor(), чтобы получить себя из контекста spring?
4. @Bane: На самом деле я пошел другим путем в своих приложениях и явно перечислил все. Это позволяет моим инструментам предоставлять мне аккуратную визуализацию архитектуры приложения, что полезно для отчетов. 🙂 Насколько мне известно , достаточно аннотировать компонент с помощью
@Component
и параметр установки свойств с помощью@Autowired
, но я подчеркиваю, что я этого не пробовал.5. @Donal: это не сработало. : ( Тем не менее, я, вероятно, в любом случае пойду по вашему пути — у меня всего около 3 контроллеров, так что ничего страшного — если я вернусь и сделаю это, я обязательно задокументирую все решение здесь для следующего пользователя. Большое вам спасибо за вашу помощь!
Ответ №2:
Вы используете spring aop для поддержки аспектов. Spring aop будет работать только на spring beans. Итак, pointcut не работает на фактическом экземпляре класса, т. е. когда контроллер вызывает любой из своих общедоступных или частных методов. Чтобы протоколировать все методы в контроллере, вам необходимо использовать AspectJ для поддержки вашего aop, включив либо время загрузки, либо время компиляции переплетения всех классов, которые вы хотите перехватить. Редактировать:
Для распределения времени загрузки вам потребуется следующее :
aop.xml
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options="-Xset:weaveJavaxPackages=true -verbose -showWeaveInfo -debug">
<include within="*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="your.logger.impl.LoggingImpl"/>
</aspects>
</aspectj>
Это подразумевает объединение всех ваших файлов (‘внутри =*’, измените по своему усмотрению) с указанными аспектами. Во время загрузки вы должны увидеть подробную информацию о переплетении классов.
Конфигурации в конфигурациях spring :
<context:load-time-weaver aspectj-weaving="autodetect"
weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
Обратите внимание, что класс weaving должен находиться в пути к библиотеке сервера, а не в пути к вашему приложению.
Вышеуказанные конфигурации должны делать то, что вы хотите сделать.
Комментарии:
1. Во-первых, я ценю ответ! 🙂 Однако, когда я пытаюсь добавить этот дополнительный тег, я замечаю, что NB не показывает его как допустимый параметр. Добавление его в любом случае (и повторное развертывание) выдает мне эту ошибку: ADM5603: ошибка прослушивателя событий [ContainerBase.addChild: start: LifecycleException: org.xml.sax.SAXParseException: cvc-complex-type.3.2.2: атрибуту ‘mode’ запрещено появляться в элементе ‘aop: aspectj-autoproxy’.]
2. Итак, я удалил его, но добавил поддержку для ткачества во время загрузки AspectJ в соответствии с несколькими найденными мной онлайн-руководствами. Но я все еще не получаю вложенное ведение журнала, которое я ищу. Пожалуйста, посмотрите мою подробную правку к моему оригинальному сообщению выше.
3. @Bane Если вы запускаете его на сервере, подобном tomcat, то параметры LTW должны быть установлены с помощью tomcat, а не JVM. Параметры jvm spring agent должны работать с кодом SE.
4. @Turbochrgd: спасибо за отзыв! Мы работаем под управлением glassfish, поэтому спасибо, что обратили на это внимание для меня. В нынешнем виде наша среда довольно строгая — я не уверен, что буду продолжать в этом направлении, поскольку это требует изменения env сервера. Отличная информация для будущих новичков, так что спасибо! 🙂
5. @Bane В этом случае вам следует рассмотреть возможность использования переплетения во время компиляции перед развертыванием приложения