---
title: 'Aspect-Oriented Programming'
source: 'https://academia.sh/en/courses/enterprise-application-framework/aspect-oriented-programming'
course: 'The Enterprise Application Framework'
language: en
updated: '2026-10-21T05:03:16+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Shows by running it that a wrap annotation adds behavior through a proxy without touching the call site'
    - 'Measures by comparing that a manually built object is not wrapped, while the same class built through the container is'
    - 'Proves that a wrap marker placed on a class with no interface stays ineffective without throwing any error'
---

# Aspect-Oriented Programming

A wrap marker placed on a managed component adds behavior without ever touching the call site: a cross-cutting concern (a counter, in this example) steps in on every call, and the mechanism is a proxy. But the marker existing does not mean it is applicable — the same marker placed on a class with no interface stays ineffective without throwing any error at all.

The previous five lessons measured how a managed component is constructed, which dependency it
is equipped with, how long it lives, where it gets its settings from, and which implementation is
chosen automatically — all of it concerned the component's **own** internal state. The previous
lesson's `Cache` and `Metric` examples worked without the call site ever being touched, but the
calling side still always talked to the object that **actually does the work**. This lesson asks
a different question: is it possible to add something to a component's **behavior**, from
outside, without ever changing its source code at all? The Server Fundamentals course had named
this kind of behavior — one scattered across more than one class, belonging to no single class —
a **cross-cutting concern**: behaviors like logging, keeping a counter, checking authorization,
which accompany the business logic itself without being related to it. This lesson measures how
we add that cross-cutting concern to a managed component, without ever touching the call site.

## Wrapping: Stepping In Without Touching the Call Site

The `InMemoryStore` class carries `@Container.Wrap`. If the container sees this annotation while
constructing a component and the class implements exactly one interface, it does not return the
constructed object directly — instead it returns a **proxy**. A proxy is an intermediate object
that implements the `Store` interface but does not do the real work; it routes every call through
itself first: the `Proxy.newProxyInstance` call wraps the `InMemoryStore` instance (`target`) and
builds a handler that does `wrapCalls++` on every method call and then forwards the call to the
real object. `Service` itself never knows about this proxy at all — when it calls `store.read()`,
it cannot tell whether the reference it holds is a real `InMemoryStore` or a proxy wrapping one,
because both carry the same `Store` interface.

This is the concrete form of a cross-cutting concern being added without ever touching the call
site: `Service.report()` still calls `store.read()` — not a single character
changed, but now every call passes through a counter increment. What adds the counter is neither
`Service`'s source code nor `InMemoryStore`'s `read()` body — what adds it is the container itself
seeing the `Wrap` marker. This is a mechanism from the same family as the previous lesson's
automatic configuration: there, the container decided which class would be constructed for an
interface; here, the container decides what gets wrapped **around** the constructed object. Both
are container decisions that leave no trace in the source.

The third argument given to `Proxy.newProxyInstance` is a handler —
`(p, m, a) -> { wrapCalls++; return m.invoke(target, a); }`. This three-letter parameter
abbreviation comes from a standard contract: `p` is the proxy object the call was made on itself,
`m` is a `Method` object representing the called method, `a` is the array of arguments given to
that call. **Every** call made to the proxy — whether it is `read()` or some other method added
later — passes through this single handler, because `Proxy.newProxyInstance` does not produce a
separate method per call, it builds a single interceptor and tells which method was called using
the `m` parameter. In this measurement the `Store` interface has a single method (`read()`), so
`m`'s value is always the same; but the mechanism stays the same no matter how many methods are
added to the interface.

- **CC26.** The proxy only increases the call count, it forwards the real call to the target with
  `m.invoke(target, a)`; no call is blocked or changed by the proxy.
- **CC27.** Wrapping is applied only to classes implementing exactly one interface
  (`type.getInterfaces().length == 1`); this is not `Proxy.newProxyInstance`'s own constraint, it
  is this measurement's own simplification. A class implementing more than one interface is not
  tried in this measurement.
- **CC28.** The counter only tracks the call **count**; which thread called it, how long it took,
  or with which arguments it was called are not in this measurement.
- **CC29.** A manually built object never enters the container's `get` method at all, so it never
  reaches the proxy logic either — wrapping applies only to objects the container constructs.
- **CC30.** Identity is read only with `Proxy.isProxyClass`; no proxy instance's identity hash is
  ever printed.

```java
// Container.java — how a wrap marker's producing behavior via a proxy, without the source ever changing, comes from a call, an annotation, or the container's own default
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Container {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Component {}

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Wrap {}

    static int annotation = 0, byDefault = 0;
    static int ineffective = 0;

    static void observe(String behavior, String source) {
        if (source.equals("default")) byDefault++; else annotation++;
        System.out.printf("%-38s %s%n", behavior, source);
    }

    static void rule(String name, String result) {
        if (result.equals("ineffective")) ineffective++;
        System.out.printf("%-38s %s%n", name, result);
    }

    static void summary() {
        System.out.printf("behavior: %d annotation, %d default%n", annotation, byDefault);
        System.out.printf("defect  : %d ineffective%n", ineffective);
    }

    int wrapCalls = 0;
    final Map<Class<?>, Class<?>> bindings = new HashMap<>();

    Container bind(Class<?> iface, Class<?> implementation) { bindings.put(iface, implementation); return this; }

    Object get(Class<?> type) throws Exception {
        if (type.isInterface()) {
            Class<?> implementation = bindings.get(type);
            if (implementation == null)
                throw new IllegalStateException("no implementation bound to interface: " + type.getSimpleName());
            return get(implementation);
        }
        Component c = type.getAnnotation(Component.class);
        if (c == null) throw new IllegalStateException("no component annotation: " + type.getSimpleName());

        Constructor<?> constructor = type.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        Object[] arg = new Object[constructor.getParameterCount()];
        for (int i = 0; i < arg.length; i++) arg[i] = get(constructor.getParameterTypes()[i]);
        Object instance = constructor.newInstance(arg);

        if (type.isAnnotationPresent(Wrap.class) && type.getInterfaces().length == 1) {
            Object target = instance;
            instance = Proxy.newProxyInstance(type.getClassLoader(), type.getInterfaces(),
                    (p, m, a) -> { wrapCalls++; return m.invoke(target, a); });
        }
        return instance;
    }

    public static void main(String[] args) throws Exception {
        Container container = new Container().bind(Store.class, InMemoryStore.class);

        Service service = (Service) container.get(Service.class);
        int previousWraps = container.wrapCalls;
        service.report();
        Container.observe("call wrapped", container.wrapCalls > previousWraps ? "annotation" : "default");

        InMemoryStore manual = new InMemoryStore();
        Container.observe("wrapping on a manually built object", Proxy.isProxyClass(manual.getClass()) ? "annotation" : "default");

        Object noIface = container.get(InterfacelessComponent.class);
        Container.rule("wrap put on an interfaceless class", noIface instanceof Proxy ? "error" : "ineffective");

        Container.summary();
    }
}

interface Store { String read(); }

@Container.Component @Container.Wrap
class InMemoryStore implements Store {
    public String read() { return "data"; }
}

@Container.Component
class Service {
    final Store store;
    Service(Store store) { this.store = store; }
    String report() { return store.read(); }
}

@Container.Component @Container.Wrap
class InterfacelessComponent { String hello() { return "hello"; } }
```

```
call wrapped                           annotation
wrapping on a manually built object    default
wrap put on an interfaceless class     ineffective
behavior: 1 annotation, 1 default
defect  : 1 ineffective
```

This topic's first lesson measured this same question across twelve behaviors and four defects,
arriving at a total of `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1 ineffective`;
this lesson stays only in that core's wrapping slice.

## The Counter Rose Without the Call Site Ever Being Touched

The `call wrapped` line comes out `annotation`: the `previousWraps` variable holds the counter's
value right before `service.report()` is called, then `service.report()` is called, and
afterward `container.wrapCalls > previousWraps` comes out true — the counter really did rise. It
is worth noting that this line calls `service.report()`, not `store.read()` directly: `Service.
report()` internally goes to `store.read()`, and since the `store` field is actually a proxy,
this internal call passes through the proxy's `invoke` handler. There is not a single word in
`Service`'s source showing the proxy — the `Store store` field only carries the interface type,
it does not say which concrete class (a real `InMemoryStore` or a proxy wrapping one) stands
behind it.

`wrapCalls` is an instance field, not the `Container` class's own — since this measurement uses a
single `container` object, we see a single counter, but if two separate `Container` instances had
each wrapped the same `InMemoryStore` class separately, their two counters would stay independent
of each other too. The counter is part of the container's own state, not the wrapped class's —
this also explains why `Service` never keeps its own counter at all, the counter always sits in
the container.

The manually built `InMemoryStore` never receives this wrapping at all:
`Proxy.isProxyClass(manual.getClass())` returns `false`, so the `wrapping on a manually built
object` line comes out `default` — a situation the container never got involved in at all. The
first lesson had already noted this in advance; here the reason becomes fully visible: wrapping
is applied only inside the `get` method, right after the object is constructed. Since
`new InMemoryStore()` falls outside this method, the proxy never comes into play at all, and the
manually built object stays permanently unwrapped — it cannot be "converted" into a proxy
afterward, because in Java an object's identity does not change once it is constructed.

## Limiting Measurement: A Marker That Shows But Does Not Work

The `InterfacelessComponent` class carries `@Container.Component @Container.Wrap` — exactly like
`InMemoryStore`. But with one difference: `InterfacelessComponent` implements no interface at all,
it is only a class on its own. The wrapping check in the `get` method looks for two conditions at
once: `type.isAnnotationPresent(Wrap.class) && type.getInterfaces().length == 1`. For
`InterfacelessComponent`, the first is true but the second is false —
`getInterfaces().length` is zero. The check reaches a `false` result and the wrapping block is
never entered at all; the object is returned in its bare form.

```
Object noIface = container.get(InterfacelessComponent.class);
Container.rule("wrap put on an interfaceless class", noIface instanceof Proxy ? "error" : "ineffective");
```

`noIface instanceof Proxy` comes out `false`, and the rule line falls into the `"ineffective"`
class. There is no exception at all — since `Proxy.newProxyInstance` is never called, there is
no error to throw either. Result: `InterfacelessComponent` is constructed, it is usable, it looks
in no way broken — but the `@Container.Wrap` marker standing on it is entirely **ineffective**.
This is the shared model's blind spot: an annotation standing on a class does not mean that
annotation is actually applied. Whoever reads the source might see `@Container.Wrap` and think
"every call on this class is being counted," but that is not true — the `Proxy` mechanism can
only work through an interface, because Java's dynamic proxy mechanism can only wrap interfaces,
not concrete classes.

The reason this defect stays in the `ineffective` class is exactly this technical boundary: the
container does not **refuse** to wrap `InterfacelessComponent`, it **cannot** wrap it at all, and
it never says so at all. Of the four defect families, this is the quietest one — `error` shouts,
`silent` produces a wrong value, but `ineffective` leaves no trace at all; the program appears to
work as expected, only the one expected behavior (the counter rising) never happens at all.

As CC27 points out, this measurement only runs the **zero**-interface edge case; it never tries
its symmetric counterpart — a class implementing **more than one** interface. But the check's own
code line (`type.getInterfaces().length == 1`) already tells us what would happen in that case
too: a class implementing two interfaces would also get `false` from the check, just like
`InterfacelessComponent`, and would return unwrapped — the reason is different (not a shortage of
interfaces, but an excess of them), but the result is the same, a silent ineffectiveness. This is
an example of a single condition line dropping two separate edge cases into the same defect
class: `Proxy.newProxyInstance` itself is capable of gathering more than one interface behind the
same proxy — its third argument accepts a `Class<?>[]` array — but this measurement's own check
never uses that capability at all, it only correctly handles the case of **exactly one**
interface. Whoever reads the source, on seeing `@Container.Wrap`, might think "this class will be
wrapped" without looking at how many interfaces the class carries; but the correct answer depends
only on whether `getInterfaces().length` is exactly 1.

## Six Lessons, the Same Question

This topic's six lessons asked the same question over and over — did a call in the source
produce this behavior, an annotation, or neither — and each time measured a different corner.
The first lesson answered this question with `Container.java`'s full core: it measured, in a
single run, an object's construction and staying a singleton, injection through the constructor,
scope and the init method, setting sources, and wrapping, arriving at a total of twelve behaviors
and four defects: `3 call, 6 annotation, 3 default` and `1 error, 2 silent, 1 ineffective`. The
second, third, and this lesson rebuilt and ran only the slice of that same core touching their
own question: injection through the constructor and an unbound interface's error, scope and the
init method and an unrecognized scope name's silence, and now wrapping and an ineffective marker
— all three are subsets of the first lesson's total, none of them claims to arrive at the same
twelve/four total on its own. The fourth and fifth lessons stepped outside this core and built
their own measurements — the precedence order of three configuration sources, conditional
automatic defaults — but both kept the same vocabulary: continuing to ask where a behavior is
born from in the source, and to distinguish whether it stays noisy or silent once it breaks.

## Summary

- A wrap annotation adds behavior by building a proxy that steps in during the call, without
  ever touching the call site; `Service`'s source code never changes at all.
- The wrapped behavior (a counter, in this measurement) appears nowhere in the source as a call —
  it can only be confirmed by running it, once the counter itself rises.
- A manually built object never passes through the container's `get` method, so it is not
  wrapped, and it stays permanently unwrapped.
- A wrap marker placed on a class with no interface stays ineffective without throwing any error
  at all — an annotation existing does not mean it is applicable.
- Of the twelve behaviors measured in the first lesson, three came from a call in the source, six
  from an annotation, three from the container's own default; the wrapping and the ineffective
  marker added in this lesson are two new observations layered on top of that total.

## Next Step

This topic's six lessons measured a single object container's own internal workings: an object's
construction, a dependency being supplied, scope and lifecycle, where settings come from,
conditional defaults, and now behavior added without ever touching the call site. This last
lesson also drew a boundary: a proxy can only add behavior as far as the interface can
**reach**, it cannot go beyond that. The next topic follows the same container outside its own
internal workings, **while it serves a request** — endpoints, validation, and data access are
also born from exactly the same three sources measured throughout this topic: a call, an
annotation, or the container's own default.
