spring-native icon indicating copy to clipboard operation
spring-native copied to clipboard

AOP improvement ideas

Open ttddyy opened this issue 2 years ago • 2 comments

I created a small project and played around AOP with Spring Native.

Sample Classes

Interface based service:

public interface HelloService {
    String hello();
}

public class HelloServiceImpl implements HelloService {
    public String hello() {
        return "Hello";
    }
}

Class based service:

public class HiService {
    public String hi() {
        return "Hi";
    }
}

Annotated class:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Greeter {
}

@Service
public class GreetingService {
    @Greeter
    public String greet() {
        return "Greeting";
    }
}

Aspect:

@Aspect
public class MyAspect {
    @Around(value = "execution(* com.example.demonativeaop.HelloService.*(..))")
    public String helloIntroduction(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed() + " (hello intercepted)";
    }
    @Around(value = "execution(* com.example.demonativeaop.HiService.*(..))")
    public String hiIntroduction(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed() + " (hi intercepted)";
    }
    @Around(value = "@annotation(com.example.demonativeaop.Greeter)")
    //    @Around(value = " within(com.example.demonativeaop..*) && @target(com.example.demonativeaop.Greeter)")
    public String greeterIntroduction(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed() + " (greeter intercepted)";
    }
}

Here are some of the findings and improvement ideas.

@Aspect class

MyAspect class is annotated with @Aspect.

Declared as @Bean

When MyAspect is declared as a @Bean:

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }

The reflect-config.json requires this entry.

  {
    "name": "com.example.demonativeaop.MyAspect",
    "allDeclaredMethods": true
  }

This is because methods in the @Aspect class define pointcuts, but usually they are not referenced by the application. Those methods need to be retained to create AOP proxies.

Improvement idea

Automatically add hint when a bean with @Aspect is found.

Annotated as @Component

When MyAspect class is annotated with @Componenet, the component scan finds the bean. In this case, no manual configuration is required.

@Aspect
@Component
public class MyAspect {
    ...
}

The generated reflect-config.json has this entry:

  {
    "name": "com.example.demonativeaop.MyAspect",
    "allDeclaredFields": true,
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true,
    "allDeclaredClasses": true,
    "methods": [
      {
        "name": "<init>",
        "parameterTypes": []
      }
    ]
  }

Reference in AOP expression

For a pointcut expression execution(* com.example.demonativeaop.HiService.*(..)), the reflect-config.json requires this entry:

  {
    "name": "com.example.demonativeaop.HiService",
    "allDeclaredMethods": true
  }

Otherwise, this exception is thrown at runtime:

...
Caused by: java.lang.IllegalArgumentException: warning no match for this type name: com.example.demonativeaop.HiService [Xlint:invalidAbsoluteTypeName]
	at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:319) ~[na:na]
	at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:227) ~[na:na]
...

For annotation based pointcut, @annotation(com.example.demonativeaop.Greeter), it requires the annotation to be in reflect-config.json as well.

  {
    "name": "com.example.demonativeaop.Greeter",
    "allDeclaredMethods": true
  }

Without the entry, this error is thrown:

...
Caused by: java.lang.IllegalArgumentException: error Type referred to is not an annotation type: com$example$demonativeaop$Greeter
	at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:319) ~[na:na]
	at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:227) ~[na:na]
...

Improvement idea

It would be nice if AOT parses the AOP expression and figures out any classes that has reference to, then auto generate hints for them.

AOP Proxies

In spring-native, there are two phases for AOP proxies.

The first phase is at AOT.
For class based proxies, ConfigurationContributor and ProxyGenerator generate proxy classes with ByteBuddy(e.g. com.example.demonativeaop.HiService$$SpringProxy$7f5de2cd) and write class files under target/generated-sources/spring-aot/src/main/resources/../*.class.
Also, it adds the generated proxies to the reflect-config.json in order for runtime to read them.

The next phase is at runtime.
The Target_DefaultAopProxyFactory substitutes the AOP proxy creation. For class based proxies, it uses BuildTimeAopProxy(ObjenesisBuildTimeAopProxy) which loads the ByteBuddy generated proxies and instantiate them.

To bridge these two phases, the ProxyConfiguration#getProxyClassName() method guarantees the same name is generated based on the advised interfaces.

Class based proxy:

The HiService is a class based proxy service. In order to generate a proxy for this class at build time, this hint is required.

@AotProxyHint(targetClass=com.example.demonativeaop.HiService.class, proxyFeatures = ProxyBits.IS_STATIC)

The hint is read by ConfigurationContributor#generateBuildTimeClassProxies and generates ByteBuddy proxy classes.(e.g. com.example.demonativeaop.HiService$$SpringProxy$7f5de2cd)

Improvement Idea

Currently, the user needs to add @AotProxyHint to trigger the ByteBuddy proxy generation. It would be nice if this is not required; so that, build time automatically detects class based proxies for AOP and generates/persists proxies.

Interface based proxy

The HelloService is interfaced based proxy service. (with HelloServiceImpl)

This uses JdkDynamicAopProxy to generate AOP proxy at runtime.

Since this is a regular dynamic proxy at runtime, proxy-config.json requires this entry.

  [
    "com.example.demonativeaop.HelloService",
    "org.springframework.aop.SpringProxy",
    "org.springframework.aop.framework.Advised",
    "org.springframework.core.DecoratingProxy"
  ]

Improvement idea

Since this proxy entry is infrastructure logic to generate dynamic proxy, it would be nice that AOT automatically adds this hint at build time.

ttddyy avatar Oct 09 '21 19:10 ttddyy

Any thoughts @aclement?

sdeleuze avatar Nov 03 '21 09:11 sdeleuze

Yep, step 1 was at least have some kind of system that would make anything in this space possible, and we have that now (and it seems to work pretty well). Then step 2 is build on that with various kinds of intelligence. I appreciate the detail/though in this writeup :)

Obviously there will be certain point cut patterns from which it will be tricky to determine the involved types - depending on how far you go with analysis. The AspectJ point cut parser is a standalone usable entity.

For this it is easy:

execution(* com.example.demonativeaop.HiService.*(..))

with simple analysis (string regex or preferably parsing the point cut with the AspectJ parser and walking the parsed structure).

But if there are more wildcards involved execution(* com.foo.bar..*.*(..)) it'll get trickier - having to drive point cut matching against potential component candidates. This should be doable however but we'd have to keep an eye on a potential explosion of proxy classes.

However, a first step of explicit type names would be a great step forward.

aclement avatar Nov 03 '21 15:11 aclement

Spring Native is now superseded by Spring Boot 3 official native support, see the related reference documentation for more details.

As a consequence, I am closing this issue, and recommend trying your use case with latest Spring Boot 3 version. If you still experience the issue reported here, please open an issue directly on the related Spring project (Spring Framework, Data, Security, Boot, Cloud, etc.) with a reproducer.

Thanks for your contribution on the experimental Spring Native project, we hope you will enjoy the official native support introduced by Spring Boot 3.

sdeleuze avatar Jan 02 '23 12:01 sdeleuze