How do aspects work with components for AOP?

← Classes and OOP · Ref: Q114

EK9 supports Aspect-Oriented Programming through the Aspect base class and application registration. Aspects wrap component calls with before and after advice.

ASPECT CLASS

Create an aspect by extending the built-in Aspect class:

  LoggingAspect extends Aspect
    override beforeAdvice()
      -> joinPoint as JoinPoint
      <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
    override afterAdvice()
      -> preparedMetaData as PreparedMetaData

BEFORE AND AFTER ADVICE

beforeAdvice() runs before each method call. afterAdvice() runs after. The JoinPoint provides the component name and method name.

JOINPOINT

The JoinPoint parameter gives execution context:

  joinPoint.componentName()   the component being called
  joinPoint.methodName()      the method being called

REGISTERING WITH ASPECTS

Use 'with aspect of' in application registration:

  register Solution1() as Config with aspect of LoggingAspect(), TimerAspect()

Multiple aspects chain in declaration order.

CHAINING MULTIPLE ASPECTS

Multiple aspects wrap in order: the first listed runs outermost. Each aspect's beforeAdvice runs before the next, and afterAdvice runs in reverse order.

See Q111 for components. See Q112 for services. See Q102 for 'as open'. See Q119 for constructs overview. See Q227 for compile-time DI validation.

See Q337 for transaction aspects.

Example

defines module qa.oop.aspects

  defines component

    Config as abstract
      getValue() as abstract
        <- rtn as String?
      default operator ?

    ProductionConfig extends Config
      override getValue()
        <- rtn as String: "production-database"
      default operator ?

  defines class

    SimpleAspect extends Aspect
      label <- String()

      SimpleAspect()
        -> label as String
        this.label: label

      override beforeAdvice()
        -> joinPoint as JoinPoint
        <- rtn as PreparedMetaData: PreparedMetaData(joinPoint)
        Stdout().println(`${label} BEFORE: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      override afterAdvice()
        -> preparedMetaData as PreparedMetaData
        joinPoint <- preparedMetaData.joinPoint()
        Stdout().println(`${label} AFTER: ${joinPoint.componentName()}.${joinPoint.methodName()}`)

      default operator ?

  defines application

    AspectApp
      register ProductionConfig() as Config with aspect of SimpleAspect("LOG")

  defines program

    AspectsDemo() with application of AspectApp
      stdout <- Stdout()

      // === INJECTION with aspects applied ===

      config as Config!
      stdout.println(`Value: ${config.getValue()}`)

Common mistakes

E05120 — When implementing Aspect's abstract methods beforeAdvice() and afterAdvice(), the 'override' keyword is mandatory. Omitting it triggers E05120. See ek9 -h E05120 for details.

Incorrect:

beforeAdvice()

Correct:

override beforeAdvice()

E05120 — ProductionConfig must use 'override' when implementing Config's abstract method getValue(). See ek9 -h E05120 for details.

Incorrect:

getValue()

Correct:

override getValue()

E50001 — The aspect type used in 'with aspect of' must be a defined type. If the type does not exist, E50001 is triggered — not resolved. Check spelling and ensure the aspect class is defined. See ek9 -h E50001 for details.

Incorrect:

register ProductionConfig() as Config with aspect of NonExistentAspect()

Correct:

register ProductionConfig() as Config with aspect of SimpleAspect("LOG")

E50020 — Aspect is a built-in class, not a component. Defining an aspect subclass inside 'defines component' triggers E50020 because components cannot extend classes — incompatible genus. Aspect subclasses belong in 'defines class'. See ek9 -h E50020 for details.

Incorrect:

defines component

Correct:

defines class

E50080 — Config is declared 'as abstract' and cannot be instantiated directly. The register statement must use a concrete implementation (ProductionConfig), not the abstract base type. See ek9 -h E50080 for details.

Incorrect:

register Config() as Config

Correct:

register ProductionConfig() as Config
Other ways to ask this
  • How does aspect-oriented programming work in EK9?
  • How do I add logging or timing to components in EK9?
  • What is the Aspect class in EK9?

Coming from another language?

Java: Spring AOP with @Before/@After annotations and proxy-based weaving, AspectJ for compile-time weaving. Python: decorators for simple AOP, no JoinPoint concept built-in. Rust: no built-in AOP, procedural macros for compile-time code generation. Go: no built-in AOP, middleware pattern for HTTP, manual wrapping. Kotlin: Spring AOP same as Java, no language-level AOP. EK9: language-level Aspect base class, beforeAdvice/afterAdvice methods, JoinPoint for context, 'register with aspect of' syntax in application definitions.

Keywords: after, aspect, cross-cutting, component, register, weave, logging, aop, advice, before, joinpoint, timing, object-oriented