
# AOP proxies

A proxy is a generated class that stands in for an advised bean and runs the advice around its calls.

## Why you need to know about it

You never write a proxy, but its rules explain what AOP can and cannot do. Advice runs only for calls that go through the proxy. The proxy must be able to stand in for the bean, which depends on how the bean class is declared.

## How proxies are made

Dart cannot create classes at runtime, so `spring_core_dart_generator` writes a proxy class for each bean at build time. When the context starts, the `AspectAutoProxyCreator` (a bean post-processor the context registers itself) collects the advice of every aspect and advisor bean. After each bean is initialized, it checks the bean against the pointcuts. A matched bean is replaced by its proxy; `getBean` and other beans receive the proxy. A bean that no pointcut matches is not wrapped and costs nothing.

Aspects, advisors and post-processors are never proxied themselves. Aspects and advisors of parent contexts apply too.

## Proxy kinds

Which kind a bean gets depends on its class:

- **Implementing proxy.** The bean class has no private members and is not `final`, `base` or `sealed`. The proxy implements the class and its interfaces, so the bean can be injected by either.
- **Subclass proxy.** A component class that cannot be implemented (private members, operators, `base`) is created as a generated subclass instead. The subclass runs its public methods, getters and setters through the advice, like Spring's CGLIB proxy. The bean is its own proxy. A member the subclass cannot override (a private type, a default value that is not a public constant, `@nonVirtual`) is inherited and not advised, with a build warning.
- **Interface proxy only.** A component class that can be neither implemented nor extended (`final`, `sealed`, `interface`, a factory constructor, generic) is proxied through the interfaces it implements. Inject it by one of them.
- **No proxy.** Without such an interface, the bean keeps its signatures. Advice that matches it fails the context at startup with the reason and what to change. Add an interface with the advised methods and inject the bean by it.

```dart
// final: cannot be implemented or extended. Advice reaches it only
// through the Books interface, so inject Books, not BookService.
abstract interface class Books {
  Book find(String isbn);
}

@Service()
final class BookService implements Books {
  @Timed()
  @override
  Book find(String isbn) => Book(isbn);
}
```

Public fields are advised like getters and setters: a field `int count` is an implicit getter and, when not `final`, a setter. Their `MemberKind` is `getter` or `setter`. They are reached through `within`, `bean`, `target`, `@within`, `@annotation(X)` and custom advisors, never through `execution`.

## Calls a bean makes to itself

Only calls from outside go through the proxy. A call through `this` inside the bean (`findAll` calling `find`) skips the advice, as in Spring. This also holds for a subclass proxy: while one of its advised members runs, including code after an `await` in a `Future` member, calls on the bean go straight to its code.

There are two ways to get the advice on a self-call.

Inject the bean into itself with `@Lazy()`:

```dart
@Service()
class BookService implements Books {
  BookService(@Lazy() this._self);
  final Books _self;

  @Timed()
  @override
  Book find(String isbn) => Book(isbn);

  // _self.find goes through the proxy, so @Timed applies; this.find
  // would skip it.
  List<Book> findAll(List<String> isbns) =>
      [for (final isbn in isbns) _self.find(isbn)];
}
```

Or call through `AopContext.currentProxy()`:

```dart
List<Book> findAll(List<String> isbns) => [
  for (final isbn in isbns)
    (AopContext.currentProxy() as Books).find(isbn), // advised
];
```

`AopContext.currentProxy()` returns the proxy whose call is in progress. It throws a `StateError` when no call through a proxy is in progress. On a subclass proxy it advises only the one call made right after it, so call it again for each call. Unlike Spring, the proxy is always exposed (there is no `exposeProxy` flag), also in the asynchronous code that an advised `Future` method starts.

The body of an `async*` or `sync*` member runs only when its result is listened to or iterated, after the member has returned, so the calls it makes on the bean are advised.

## Advice without annotations

A `PointcutAdvisor` bean is applied like an aspect. This suits libraries that decide in code what to advise.

```dart
class UpperCase implements MethodInterceptor {
  @override
  Object? invoke(MethodInvocation invocation) =>
      (invocation.proceed()! as String).toUpperCase();
}

@Configuration()
class AopConfig {
  @Bean()
  PointcutAdvisor upperCase() =>
      DefaultPointcutAdvisor(ExpressionPointcut('bean(slogans)'), UpperCase());
}
```

An advisor bean that is an `Advisor` but not a `PointcutAdvisor` applies to every member of every proxied bean.

## Inspecting and changing a live proxy

Every generated proxy implements `Advised`.

| Member | Meaning |
|---|---|
| `aopTarget` | The bean behind the proxy; the proxy itself for a subclass proxy. |
| `aopAdvisors` | The advisors that apply, outermost first. |
| `addAdvisor(advisor)` | Appends an advisor as the innermost one; the next calls use it. |
| `removeAdvisor(advisor)` | Removes it; returns `false` when it was not there. |
| `addAdvice(interceptor)` | Appends an interceptor for every member (wrapped with a `TruePointcut`). |
| `removeAdvice(interceptor)` | Removes the first advisor whose advice is that object. |

`addAdvisor` and `addAdvice` throw an `AopConfigException` when the build generated the proxy class for only some members (see pruning below).

`AopUtils` answers the common questions:

```dart
final calculator = context.getBean<Calculator>();
AopUtils.isAopProxy(calculator); // true when advised
AopUtils.getTarget(calculator); // the SimpleCalculator behind it
```

## More proxies in code

`ProxyFactory` makes extra proxies of a bean with advice chosen by the caller, for example in a test. The bean in the context does not change. It reuses the proxy class the generator wrote for the bean.

```dart
// A logging view of the calculator bean, for this caller only.
final factory = ProxyFactory.forBean(context.beanFactory, 'calculator')
  ..addAdvice(LoggingInterceptor());
final calculator = factory.getProxy() as Calculator;
```

`AspectJProxyFactory` adds `addAspect(aspect, metadata)` to apply one aspect to one proxy only. `getProxy()` throws an `AopConfigException` for a bean without a proxy class, a pruned proxy class, or a subclass proxy (change its advice with `Advised.addAdvisor` instead).

## Build options

Set in your package's `build.yaml`:

```yaml
targets:
  $default:
    builders:
      spring_core_dart_generator:spring_core_dart:
        options:
          aop_proxies: all   # all (default) | interfaces | none
          aop_prune: true    # skip proxies of beans no pointcut matches
```

| Option | Values | Default | Meaning |
|---|---|---|---|
| `aop_proxies` | `all`, `interfaces`, `none` | `all` | `all`: a proxy for every bean that can have one, subclass proxies included. `interfaces`: proxies only through a bean's abstract supertypes. `none`: no proxies, so AOP cannot advise the module's beans. |
| `aop_prune` | `true`, `false` | `true` | With `aop_proxies: all`, skip the proxy class of a bean that no pointcut of the application can match, and route only the matched members through a kept proxy. `false` generates every proxy with every member. |

A pruned bean keeps its signatures. An advisor that matches it anyway (one the build did not see) fails the startup with the reason, rather than being skipped silently.

## Limits

- Advice runs only through the proxy. Self-calls skip it unless made through `@Lazy()` self-injection or `AopContext.currentProxy()`.
- Private members are never advised.
- A class that is `final`, `sealed` or `interface` (or has only factory or private constructors, or is generic) can be advised only through an interface it implements.
- `execution` never matches getters or setters.
- The generator must run after every change to aspects or advised beans.

## In Spring (Java)

```java
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
class AppConfig {}

Calculator calculator = context.getBean(Calculator.class);
AopUtils.isAopProxy(calculator);
((Advised) calculator).addAdvice(new LoggingInterceptor());
((Books) AopContext.currentProxy()).find(isbn);
```

Differences that matter:

- Spring makes JDK dynamic proxies (interfaces) or CGLIB subclasses at runtime. Here the generator writes the proxy classes at build time. No reflection is used at runtime, and it works in AOT, Flutter and web builds.
- No `@EnableAspectJAutoProxy` and no `exposeProxy` flag: both are always on.
- Dart class modifiers (`final`, `sealed`, `interface`, `base`) decide the proxy kind, much as `final` classes and methods limit CGLIB.
- `Advised` members are `aopTarget` and `aopAdvisors`, not `getTargetSource()` and `getAdvisors()`.

## See also

- [AOP overview](index.md)
- [@Aspect](aspect.md)
- [Pointcuts](pointcut.md)
- [Beans](../beans/index.md)
- [ApplicationContext](../beans/application-context.md)
