Performance Logging with Spring Boot and AOP - java

I'm trying to implement performance logging based off of this post: http://www.baeldung.com/spring-performance-logging. I'd like to log each controller endpoint and every database request. If you would like to see the full project, you can find it here. When I hit the endpoints nothing is logged. Placing a breakpoint within the interceptor class it doesn't stop either. I've already set my logging for the packages to the trace level. What am I missing? I believe it's something to with the #PointCut but after looking at the docs I believe I have it correct.
Interceptor
public class PerformanceMonitorInterceptor extends AbstractMonitoringInterceptor
{
#Override
protected Object invokeUnderTrace(MethodInvocation methodInvocation, Log log) throws Throwable
{
String name = createInvocationTraceName(methodInvocation);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.trace(String.format("Method %s execution start at %s", name, LocalDateTime.now()));
try
{
return methodInvocation.proceed();
}
finally
{
stopWatch.stop();
log.trace(String.format("Method %s execution took %dms (%s)", name,
stopWatch.getTotalTimeMillis(), DurationFormatUtils
.formatDurationWords(stopWatch.getTotalTimeMillis(), true, true)));
}
}
}
Configuration
#Configuration
#EnableAspectJAutoProxy
#Aspect
public class ContactControllerPerfLogConfig
{
#Bean
public PerformanceMonitorInterceptor performanceMonitorInterceptor()
{
return new PerformanceMonitorInterceptor();
}
// Any public method on the ContactController
#Pointcut("execution(public * org.example.phonebookexample.app.contact.ContactController.*(..))")
public void contactControllerMonitor()
{
}
#Bean
public Advisor contactControllerMonitorAdvisor(
PerformanceMonitorInterceptor performanceMonitorInterceptor)
{
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("org.example.phonebookexample.app.contact.ContactControllerPerfLogConfig.contactControllerMonitor()");
return new DefaultPointcutAdvisor(pointcut, performanceMonitorInterceptor);
}
}

I never understood the importance of AbstractTraceInterceptor if you are going to customize it.
I always felt too much code for me which can be achieved by a simple #Around advice if you already have #Aspect in place.
#Around("execution(* com..*.*(..))")
public Object logStartAndEnd(ProceedingJoinPoint pjp) throws Throwable{
long startTime = System.currentTimeMillis();
String className = pjp.getTarget().getClass().getCanonicalName();
String methodName = pjp.getSignature().getName();
log.info("started method : " + className+"."+methodName);
Object obj;
try {
obj = pjp.proceed();
log.info("finished method : " + className+"."+methodName);
return obj;
} catch (Throwable e) {
throw e;
}finally {
log.info("Method "+className+"."+methodName+" execution lasted:"+((System.currentTimeMillis() - startTime )/1000f)+" seconds");
}
}

AbstractTraceInterceptor implements MethodInterceptor and has its invoke() method implemented as follows:
public Object invoke(MethodInvocation invocation) throws Throwable {
Log logger = getLoggerForInvocation(invocation);
if (isInterceptorEnabled(invocation, logger)) {
return invokeUnderTrace(invocation, logger);
}
else {
return invocation.proceed();
}
}
Therefore the logger for the Interceptor class needs to be set to TRACE. For the basic PerformanceMonitorInterceptor, that will be org.springframework.aop.interceptor.PerformanceMonitorInterceptor. Since you've written your own interceptor, you'll have to set logging level for your own class.
Take a look at JamonPerformanceMonitorInterceptor for an example of an alternative approach, allowing to track all invocations regardless of logging level, if required.
For the sake of completeness, I'll also post an xml configuration example, because when it comes to AOP, Spring java configs are not so elegant compared to xml configs:
<bean id="performanceMonitorInterceptor" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
<aop:config>
<aop:pointcut id="contactControllerMonitor" expression="execution(public * org.example.phonebookexample.app.contact.ContactController.*(..))" />
<aop:advisor id="contactControllerMonitorAdvisor" pointcut-ref="contactControllerMonitor" advice-ref="performanceMonitorInterceptor"/>
</aop:config>
This config can be imported into your java configuration as follows:
#ImportResource("classpath:/aop-config.xml")
public class MainConfig { ... }

Related

How to audit methods in Java Spring Boot

I am writing a Spring Boot Application. I want to audit methods with my annotation #AuditMetod: For example I have method foo() with the annotation:
#AuditMetod(name = "SomeValue")
foo() {...}
I want to handle and audit such methods like this (the simplest example):
auditMethod(Method method) {
if (method.hasAnnotation(AuditMethod.class)) {
System.out.println (method.getName() + " was called at " + new Date())
}
}
upd
Thanks to #Karthikeyan #Swapnil Khante and #misha2048 I understood, that I need to use AOP. But I have 2 problems:
The only method in Aspect class in not being called and I don't see the inscription "----------ASPECT METHOD IS CALLED-----------" in log
How can I check in aspect method what method it is intercepting. To get an instance of Method class.
Now I have the following code:
Controller:
#PostMapping
#LoggingRest(executor = "USER", method = "CREATE", model = "SUBSCRIPTION")
public ResponseEntity<?> create(#Valid #RequestBody SubscriptionRequestDto dto) {
...
}
Aspect:
`#Aspect
#Slf4j
#Component
public class AuditAspect {
#Pointcut(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
log.info("----------ASPECT METHOD IS CALLED------------");
}`
And annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LoggingRest {
String executor() default "SYSTEM";
String method() default "";
String model() default "";
}
Auditing is a cross-cutting concern and can be handled using AOP.
Another solution would be to use a low-level solution by writing a custom annotation and using a Spring interceptorto write your business logic.
To use the Spring interceptor you will need to implement the HandlerInterceptor interface
Example of the annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
boolean active() default true;
}
Interceptor example
#Component
public class AuditInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Audit annotation = handlerMethod.getMethodAnnotation(Audit.class);
if (annotation != null && annotation.active()) {
// your business logic
}
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
check this interceptor example
I think one of the solutions here, as #Karthikeyan mentioned, is to use Spring AOP.
If you are not aware a brief introduction - spring-aop module implements the aspect oriented programming paradigm. We extract some common functionality, that we generally want to apply to some subset of functions/methods, to an entity called Aspect (see class annotated with #Aspect). This class will contain out cross-cutting functionality - such as auditing, for instance we want to audit the methods execution time, lets say. We just put the code to be executed, the condition, which tell the spring what exact beans methods should be affect by this aspect, see below.
For example, if I can audit the method execution duration with the following very simple example (in my case I said that any public method, returning void inside the Class com.example.stackoverflow.BusinessLogicClass must be inspected by this Aspect):
#SpringBootApplication
#EnableAspectJAutoProxy
public class StackoverflowApplication implements ApplicationRunner {
#Autowired
private BusinessLogicClass businessLogicClass;
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
businessLogicClass.test();
}
}
#Aspect
#Component
class MyAspectLogicClass {
#Around("execution(public void com.example.stackoverflow.BusinessLogicClass.*(..))")
public Object hangAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long before = System.currentTimeMillis();
Object returnedValue = proceedingJoinPoint.proceed();
long after = System.currentTimeMillis();
System.out.printf("Retruned in '%s' ms %n", (after - before));
return returnedValue;
}
}
#Component
class BusinessLogicClass {
public void test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In my case, I will get the time before method execution, then by the means of
proceedingJoinPoint.proceed() call I delegate the execution to the real method, and then, once I get the response back, I will get the current system time and calculate the execution time, fairly simple.
I hope I have at least directed you somewhere, if you are looking for documentation, this are the resources I suggest you should look for:
https://docs.spring.io/spring-framework/docs/2.5.x/reference/aop.html offical spring doc (stale a bit, but there are some valuable things to learn)
https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html is more fresh doc
Hope it helped :)
The problem was in right annotation. In Aspect class I tried #Around and everything works as I need.
#Aspect
#Slf4j
#Component
public class AuditAspect {
#Around(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
var method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
log.info("----------ASPECT METHOD IS CALLED------------");
}
}
For getting a Method instance I use fallowing code
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();

Not throwing exception from a #Service's methods

I have a notification service(I have control of this class).
If there is any unchecked exception, then I do not want to throw it. But instead want to log it in a specific manner.
I can directly have try catch block in each method's implementation but I am in search of some magic here 😄
Is there a common way that this can be handled through Spring?
Update:
AOP is also a way to do it. For example: https://dzone.com/articles/handling-exceptions-using-springs-aop
Any other direct implementation for this?
This was my requirement but I was not able to find anything with respect to this.
I faced similar issues when dealing with calling multiple apis from rest service, where i was suppose to provide a fallback implementation when error occured. My Aspect was more than what i am giving example here.
Service
#Service
public class SuspiciousService {
final Random random = new Random();
public String beSuspicious() {
final boolean value = random.nextBoolean();
if (value) {
throw new IllegalStateException("Exception occured for: " + value);
}
return "I am not suspicious";
}
}
Sample service which randomly throws an error.
Controller
#RestController
#RequestMapping("/is-suspicious")
#AllArgsConstructor
public class SampleController {
private final SuspiciousService suspiciousService;
#GetMapping
public Map<String, String> get() {
return Map.of("isSuspicious", suspiciousService.beSuspicious());
}
}
Controller which invokes this service.
Finally, Around Aspect catches this exception and provides the sample response.
#Aspect
#Component
#Order(2)
public class AspectAroundSuspiciousService {
#Around("execution(* in.silentsudo.sprintbootresttemplate.SuspiciousService.beSuspicious(..))")
public Object parallelExecuteBeforeAndAfterCompose(ProceedingJoinPoint point) throws Throwable {
try {
return point.proceed();
} catch (RuntimeException re) {
return "Yes, I am suspicious";
}
}
}
The other approach is if you are using ByteBuddy, you can add annotation to the method throwing exception
#Advice.OnMethodExit(onThrowable = RuntimeException.class)
and have an ExceptionHandler to cath this
#ExceptionHandler
private String suspiciousRuntimeException(RuntimeException exception) {
return "Yes, I am suspicious from ex handler, error: " + exception.getMessage();
}
I choose aspect over bytebuddy for simple reason as i was handling ladder of api exception, where as this implementation will catch in general RuntimeException happenning from service#method

Using #EnableBinding in DemoController.class result in spring aop fail

In my application, using #EnableBinding upon DemoController.class result in my Log Aspect fail,have log records no more.
And, if remove #EnableBinding, the log aspect work.
UspeController.class like this:
#RestController
#RequestMapping("/yeah/user")
#EnableBinding({OpenFileSystemOutput.class})
public class UspeController {
#Autowired
OpenFileSystemOutput openFileSystemOutput;
#PutMapping(value = "/applyAccount")
public Result<?> applyAccount() throws Exception {
UserMessage userMessage = new UserMessage(UserInfoContext.getUserId());
openFileSystemOutput.output().send(MessageBuilder.withPayload(userMessage).build());
return ResultUtils.success("");
}
}
LOG Aspect like this:
#Aspect
public class LogAspect {
#Pointcut("within(com.yeah..*) && #target(org.springframework.web.bind.annotation.RestController) ")
public void executionService() {
}
#Before(value = "executionService()")
public void doBefore(JoinPoint joinPoint) {
log.info("Api Interface: [{}], parameters: {}", request.getRequestURI(), JSON.toJSONString(getSerializableObject(joinPoint)));
}
}
Using EnableBinding is deprecated as of 3.1.x versions of Spring Cloud Stream. If you can, please upgrade your code to use the latest functional model. See the programming model section from the docs for more details.

Springboot #retryable not retrying

The following code is not retrying. What am I missing?
#EnableRetry
#SpringBootApplication
public class App implements CommandLineRunner
{
.........
.........
#Retryable()
ResponseEntity<String> authenticate(RestTemplate restTemplate, HttpEntity<MultiValueMap<String, String>> entity) throws Exception
{
System.out.println("try!");
throw new Exception();
//return restTemplate.exchange(auth_endpoint, HttpMethod.POST, entity, String.class);
}
I have added the following to the pom.xml.
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
I also tried providing different combinations of arguments to #Retryable.
#Retryable(maxAttempts=10,value=Exception.class,backoff=#Backoff(delay = 2000,multiplier=2))
Thanks.
In spring boot 2.0.2 Release, I have observed that the #Retryable is not working if you have retryable and called method in same class. On debugging found that the pointcut is not getting built properly. For now, the workaround for this problem is that we need to write the method in a different class and call it.
Working Example could be found here.
For the #Retryable annotation on the method to be discovered it needs to be called correctly from an initialised context. Is the method invoked from a bean from the spring context or called by other means?
If testing this is your runner using the SpringJunit4ClassRunner?
Spring's #Retryable, #Cacheable, #Transaction, etc. are ALL implemented using Aspect Oriented Programming. Spring implements AOP via proxy-based weaving. Proxies intercept calls from one bean to another. Proxies cannot intercept calls from one object's methods to another. This is a general limitation of proxy based weaving.
The following solutions address this limitation: 1) as mentioned above, use #Autowired (or #Resource) to inject a bean with a self reference; calls to this reference transit the proxy. 2) Use AspectJ's ClassLoader instead of Spring's default proxy-based weaving. 3) As mentioned above, place the methods on separate beans. I've done each in various situations, each has pros and cons.
I solved it. I figured out that if return something from the method that you trying to retry, then #Retryable() is not working.
maven dependency in pom.xml
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
Spring boot Application.java
#SpringBootApplication
#EnableTransactionManagement
#EnableRetry
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
in controller.java
#RestController
public class JavaAllDataTypeController {
#Autowired
JavaAllDataTypeService JavaAllDataTypeService;
#RequestMapping(
value = "/springReTryTest",
method = RequestMethod.GET
)
public ResponseEntity<String> springReTryTest() {
System.out.println("springReTryTest controller");
try {
JavaAllDataTypeService.springReTryTest();
} catch (Exception e) {
e.printStackTrace();
}
return new ResponseEntity<String>("abcd", HttpStatus.OK);
}
}
in service.java
#Service
#Transactional
public class JavaAllDataTypeService {
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts=9,value=Exception.class,backoff=#Backoff(delay = 2000))
public void springReTryTest() throws Exception {
System.out.println("try!");
throw new Exception();
}
}
output: It' trying 9 times then throwing exception.
I had exactly the same issue as described in the original question.
In my case it turned out that the spring-boot-starter-aop dependency was accidentally not included. After adding it to my pom.xml, my #Retryable methods worked as expected.
Returning values from #Retryable methods works fine for me.
It work for return type as well
#Service
public class RetryService {
private int count = 0;
// try the method 9 times with 2 seconds delay.
#Retryable(maxAttempts = 9, value = Exception.class, backoff = #Backoff(delay = 2000))
public String springReTryTest() throws Exception {
count++;
System.out.println("try!");
if (count < 4)
throw new Exception();
else
return "bla";
}
}
For those who want to call #Retryable block in same class can to this way.
The key here is not to call the method directly and through self-injected bean
#Slf4j
#Service
public class RetryService {
#Resource(name = "retryService")
private RetryService self;
public String getValue(String appender) {
return self.getData(appender);
}
#Retryable(value = NumberFormatException.class, maxAttempts = 4, backoff = #Backoff(500))
public String getData(String appender) {
log.info("Calling getData");
Integer value = Integer.parseInt(appender);
value++;
return value.toString();
}
#Recover
public String recoverData(String appender) {
log.info("Calling recoverData");
return "DEFAULT";
}
}
Can read more about using Retry in detail here
An alternative could be RetryTemplate
#Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000l);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
and
retryTemplate.execute(new RetryCallback<Void, RuntimeException>() {
#Override
public Void doWithRetry(RetryContext arg0) {
myService.templateRetryService();
...
}
});
worked out for me
source
Pretty old thread, but I wanted to share that after changing my method visibility from private to public, Retryable was successfully retrying.
This is in addition to using the self resource mentioned above.
Even I faced the same issue, Later after some investigation and research came to know that along with #Retryable annotation above the method we also need to provide #EnableRetry above the class. This #EnableRetry annotation either can be provided above same class in to which you have provided method you want to retry or above your main spring boot application class. For example like this:
#RequiredArgsConstructor
**#EnableRetry**
#Service
public class SomeService {
**#Retryable(value = { HttpServerErrorException.class, BadRequestException.class},
maxAttempts = maxRetry, backoff = #Backoff(random = true, delay = 1000,
maxDelay = 8000, multiplier = 2))**
public <T> T get( ) throws HttpServerErrorException, BadRequestException {
//write code here which you want to retry
}
}
I hope this will help and resolve your issue.
I got this one solved by moving #Retryable directly in front of the method I wanted to retry.
From this:
public class MyClass {
public String toBeRetried() {
return delegateTo();
}
#Retryable
public String delegateTo() {
throw new Exception();
}
}
To this:
public class MyClass {
#Retryable
public String toBeRetried() {
throw new Exception();
}
}

how to do logging of method chaining using spring aop

I am using slf4j with Spring AOP for logging and Exception purpose.there are some methods in some classes which formed a method chaining. I am able to log at first method's entry and exit point but when this method called another method then AOP is logging only first method's entry and exit point.I want to log every method's entry and and exit point using #Around annotation here is Pseudo code to explain what i want
package com.sample;
public class Test implements T{
#Override
public void show() {
System.out.println("Test.show()");
test();
}
void Test(){
//Want to log entry and exit point of this method whenever this method called by any other method
//The method may belongs to same class or different package's different class
}
spring.xml is something like this
<bean id="exceptionAspect" class="com.sample.ExceptionAspect"/>
<bean id="test" class="com.sample.Test"/>
My Advise class look like
#Aspect
public class LoggingAspect {
#Around(value="execution (* com.sample.*.*(..))||"+
"execution(* some other package.*.*(..))")
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
final Logger logger = LoggerFactory.getLogger(joinPoint.getTarget().getClass());
logger.info("Execution of : " + joinPoint.getSignature() + " Started");
joinPoint.proceed();
logger.info("Execution of : " + joinPoint.getSignature() + " completed");
}
}
Client class
package com.test;
public class App {
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new ClassPathXmlApplicationContext(
"classpath:/META-INF/spring.xml");
T test=(T) appContext.getBean("test");
test.show();
}
Any Help is greatly appreciated..
What you are trying to do is not possible with Spring AOP (at least not without some refactoring of the advised methods). The reason for that is that Spring AOP is proxy based, which means for each bean it creates a proxy class and injects it instead of your implementation. A proxy has all the methods of the bean with added aspect functionality. So When you call a method of a bean (actually the proxy of the bean), the aspect code is executed and your method is then called by delegation. So when your method calls other methods the call are performed using the real beans, not the proxies of those - if any are present - hence you don't get the output you are expecting.
You can think of a proxy it looks somehow like that:
class MyBeanProxy implements MyBean {
MyBeanImpl theBean;
public void foo() {
// aspect code
theBean.foo();
}
public void bar() {
// aspect code
theBean.bar();
}
}
Where your bean is something like
interface MyBean {
foo();
bar();
}
#Component("my_bean")
class MyBeanImpl implements MyBean {
public void foo() {
System.out.println("foo");
bar();
}
public void bar() {
System.out.println("bar");
}
}
In the example above, when you call foo() via the proxy then the aspect code is executed, and the delegation to MyBeanImpl#foo() happens, where bar() is being called. Now it becomes obvious that the aspect code for bar() will not be executed.
Now how can you make it work?
1 - Refactor your code in such a way that for methods you want to have the aspect code executed for them the calls happen on the proxy object not on the bean itself. For that you can get the actual proxy and use it to call your methods.
public void foo() {
System.out.println("foo");
MyBean myBeanProxy = (MyBean) AopContext.currentProxy();
myBeanProxy.bar();
}
Note that this method is more of a hack than a clean way to do the job. For example it is obvious that myBeanProxy has no clue of the state of your current object.
2 - Refactor you code in such a way that bar() is in another bean which you can retrieve using your appContext.
3- Use AspectJ: Aspect code is injected into the target classes themselves (The real thing!)
Here is small example using AspectJ
Aspect
package com.aj;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class MyAspect {
#Around("execution( * com.app.services.*.* (..) )")
public Object callDurationAdvice(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
String argList = Arrays.toString(args);
System.out.println(signature.getDeclaringTypeName() +
"." + signature.getName() + "(" + argList + ") started");
long s = System.nanoTime();
Object proceed = pjp.proceed(args);
long e = System.nanoTime();
System.out.println(signature.getDeclaringTypeName() +
"." + signature.getName() + "(" + argList + ") ended after " +
((double)(e-s)/1000000) + " ms");
return proceed;
}
}
One class in some package witch should be target for the aspect
package com.app.services;
public class ServicesVersionInfo {
public static String getVersion() {
return getVersionNumber() + " " + getVersionStage();
}
public static String getVersionNumber() {
return "1.0.0";
}
public static String getVersionStage() {
return "ALPHA";
}
}
The App
package com.app;
import com.app.services.ServicesVersionInfo;
public class App {
public static void main(String[] args) {
System.out.println("App services version: " +
ServicesVersionInfo.getVersion());
}
}
Ran, this should output something lie that
com.app.services.ServicesVersionInfo.getVersion([]) started
com.app.services.ServicesVersionInfo.getVersionNumber([]) started
com.app.services.ServicesVersionInfo.getVersionNumber([]) ended after 0.004862 ms
com.app.services.ServicesVersionInfo.getVersionStage([]) started
com.app.services.ServicesVersionInfo.getVersionStage([]) ended after 0.005673 ms
com.app.services.ServicesVersionInfo.getVersion([]) ended after 0.378877 ms
App services version: 1.0.0 ALPHA
Finally here are some similar questions and further readings:
Spring AOP not working for method call inside another method
Get AOP proxy from the object itself
Spring AOP top problem #1 - aspects are not applied

Categories