Any downside of addAspect(sameAspect) millions of times?

64 Views Asked by At

The code I'm writing needs to intercept calls to object #2 (part of an external library) that is instantiated/retrieved on the fly by object #1 (also in the external library).

Therefore, I'm intercepting object #1 and then adding an aspect to object #2; this way I can get the number of calls, exceptions thrown, response times, etc.

// My aspect intercepts object #1 that retrieves object #2 and adds the aspect
Object obj2 = joinPoint.proceed();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(obj2);
proxyFactory.addAspect(this);

Now, since obj2 will be retrieved millions of times will proxyFactory.addAspect(this); degrade the app over time when called (adding the aspect) over and over again, or is this call idempotent and has little side effects?

Note: I'm guessing obj2 will be instantiated once of just a few times, and then it's going to be the same object over and over again, but I can't be positive about this, since it's an external, closed source library.

1

There are 1 best solutions below

2
On BEST ANSWER

Like I said in my comment, simply use native AspectJ, ideally via load-time weaving (LTW), because then you can also intercept third-party classes without tedious post-compile binary weaving and repackaging of existing JARs. That should be quite easy and super efficient.

FWIW, your Spring AOP question was interesting enough to think about it. If you are for some reason unhappy about the performance when repeatedly and redundantly creating proxy factories, proxies and linking them to aspects, simply cache the target instances in a weak hash map, using the original instances as keys and the proxies as values. It would basically look like this:

package de.scrum_master.spring.q77417629;

public class MyPojo {
  public void doPojoStuff() {}
}
package de.scrum_master.spring.q77417629;

import org.springframework.stereotype.Component;

@Component
public class MyComponent {
  private final MyPojo myPojo;

  public MyComponent() {
    myPojo = new MyPojo();
  }

  public MyPojo doComponentStuff() {
    return myPojo;
  }
}

As you can see, the component always returns the same POJO instance, i.e. it should be cacheable.

package de.scrum_master.spring.q77417629;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import org.springframework.stereotype.Component;

import java.util.WeakHashMap;

@Aspect
@Component
public class MyAspect {
  private final WeakHashMap<Object, Object> proxyTargets = new WeakHashMap<>();

  @Around("execution(* MyComponent.doComponentStuff())")
  public Object interceptMyComponent(ProceedingJoinPoint joinPoint) throws Throwable {
    Object result = joinPoint.proceed();
    if (proxyTargets.containsKey(result)) {
      System.out.println("Existing proxy target " + result);
      return proxyTargets.get(result);
    }
    System.out.println("New proxy target " + result);
    AspectJProxyFactory proxyFactory = new AspectJProxyFactory(result);
    proxyFactory.addAspect(this);
    Object proxy = proxyFactory.getProxy();
    proxyTargets.put(result, proxy);
    return proxy;
  }

  @Before("within(de.scrum_master.spring.q77417629..*)")
  public void log(JoinPoint joinPoint) {
    System.out.println(joinPoint);
  }
}
package de.scrum_master.spring.q77417629;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@Configuration
@EnableAspectJAutoProxy
public class DemoApplication {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args)) {
      // The POJO is not proxied yet, i.e. the aspect should not fire
      new MyPojo().doPojoStuff();
      new MyPojo().doPojoStuff();
      new MyPojo().doPojoStuff();

      MyComponent component = context.getBean(MyComponent.class);

      // The first time doComponentStuff() is intercepted by the aspect, the
      // proxy factory should bind an aspect to its return value, a MyPojo
      // instance. The subsequent doPojoStuff() call should immediately be
      // intercepted by the second aspect advice.
      component.doComponentStuff().doPojoStuff();
      // All future doComponentStuff() interceptions should use the cached
      // MyPojo instance.
      component.doComponentStuff().doPojoStuff();
      component.doComponentStuff().doPojoStuff();
    }
  }
}

The console log shows how it works:

execution(MyPojo de.scrum_master.spring.q77417629.MyComponent.doComponentStuff())
New proxy target de.scrum_master.spring.q77417629.MyPojo@43e7f104
execution(void de.scrum_master.spring.q77417629.MyPojo.doPojoStuff())
execution(MyPojo de.scrum_master.spring.q77417629.MyComponent.doComponentStuff())
Existing proxy target de.scrum_master.spring.q77417629.MyPojo@43e7f104
execution(void de.scrum_master.spring.q77417629.MyPojo.doPojoStuff())
execution(MyPojo de.scrum_master.spring.q77417629.MyComponent.doComponentStuff())
Existing proxy target de.scrum_master.spring.q77417629.MyPojo@43e7f104
execution(void de.scrum_master.spring.q77417629.MyPojo.doPojoStuff())