Примеры кода в статье будут намеренно упрощены в угоду компактности изложения идеи, сама демонстрация идеи не страдает. Более пригодный для промышленной эксплуатации код можно найти в конце статьи и в репозитории с практикой. Прокси объекты являются основой "магии" Spring Framework. В качестве демонстрации реализована @JmxExporter аннотация, которая позволяет превратить любой Spring Bean в JMX MBean.  Spring Framework позволяет сфокусироваться на бизнес-логике, а вся настройка инфраструктуры выполняется силами самого фреймворка. Так, например, разработчик вешает аннотацию @RestController на бин, и бин начинает обрабатывать REST запросы, при этом разработчик не трогает Servlet Context, не настраивает цепочку фильтров или веб сервер: все конфигурируется автоматически. Автоматическая настройка инфраструктуры выполняется благодаря постобработке бинов. Зачастую для реализации дополнительной функциональности применяется Spring AOP - аспектно-ориентированное программирование. Spring AOP удобен, когда необходимо выполнить код перед, после или вместо вызова метода бина. Управление транзакциями - классический пример использования Spring AOP: начать транзакцию перед вызовом метода и зафиксировать ее после завершения метода. Spring AOP бин, который накручивает дополнительную функциональность другим Spring бинам, помечается аннотацией @Aspect, но как потом этот бин используется для добавления дополнительной функциональности в поток исполнения? Все работает благодаря тому, что в жизненный цикл бина можно вклиниться при помощи BeanPostProcessor. Прокси объект полностью повторяет интерфейс проксируемого объекта, но в то же время может исполнять дополнительный код. Предельным случаем прокси объекта можно считать наследование: по согласно принципу подстановки Барбары Лисков объект наследник можно использовать везде, где можно использовать объект родитель. При этом в наследниках можно переопределять методы родителя, дополняя или полностью изменяя логику. Другим способом создать прокси объект является класс java.lang.reflect.Proxy, который появился в Java 1.3. При помощи Proxy можно динамически создавать объекты, которые реализуют некоторый интерфейс. Так, программист не обязан предоставлять реализацию интерфейса: она может появиться в рантайме. Для создания прокси-объекта необходимо: интерфейс, т.к. java.lang.reflect.Proxy не умеет работать с конкретными или абстрактными классами; обработчик, который реализует интерфейс java.lang.reflect.InvocationHandler; реализация метода java.lang.reflect.InvocationHandler#invoke для перехвата вызова методов оригинального интерфейса. В качестве первого примера предлагается рассмотреть прокси объект, которым является строка "Hello, World!". Особенности: строки в java являются объектами класса java.lang.String, что не подходит для Proxy, т.к. нужен интерфейс, поэтому прокси будет создаваться для интерфейса java.lang.CharSequence; при печати строки неявно вызывается метод toString, следовательно именно этот метод необходимо проксировать; вызов остальных методов будет делегирован строковой константе "Hello, World!". Реализация java.lang.reflect.InvocationHandler: Пояснения: метод invoke принимает на вход три параметра:  сам прокси объект, для которого вызван метод. Методы могут быть как из списка методов реализуемого интерфейса, так и методы java.lang.Object; объект java.lang.reflect.Method указывает на вызванный метод; список аргументов, которые были переданы в метод при вызове. метод invoke обрабатывает вызовы всех методов проксируемого объекта; активно используется Java Reflection API. Создание прокси объекта: Пояснения: Создание proxy объекта выполняется при помощи Proxy#newProxyInstance, аргументами которого являются:  загрузчик классов для загрузки создаваемого прокси объекта; список интерфейсов, которые реализует прокси объект; обработчик, в метод invoke которого будут направляться вызовы всех методов прокси объекта. Метод Proxy#newProxyInstance возвращает результат типа java.lang.Object, поэтому необходимо явно привести его тип к реализуемому интерфейсу. Реализовывать можно также и пользовательские интерфейсы. В качестве примера будет реализован интерфейс PasswordGenerator с одним методом getPassword: Определение интерфейса генератора паролей: Реализация java.lang.reflect.InvocationHandler для генератора паролей: Пояснения: PasswordGeneratorInvocationHandler может генерировать пароли указанной длины, которая задается в конструкторе; пароль генерируется во время создания объекта PasswordGeneratorInvocationHandler при помощи вспомогательного метода generatePassword; реализация generatePassword опущена в целях экономии места, код можно посмотреть в репозитории; метод PasswordGeneratorInvocationHandler#invoke проверяет какой метод был вызван:  если был вызван метод getPassword, то вернуть заготовленный пароль из поля password; в противном случае делегировать вызов метода заготовленному объекту password. метод getPassword, вызванный на одном объекте на базе PasswordGeneratorInvocationHandler, будет возвращать всегда одно и то же значение. Создание прокси объекта: Создание прокси объекта аналогично созданию прокси объекта из предыдущего раздела. Как было отмечено ранее, один прокси объект PasswordGenerator будет возвращать всегда одно и то же значение пароля вне зависимости от того, сколько раз был вызван generatePassword. В целях безопасности лучше показывать пароль только один раз. Как изменить PasswordGeneratorInvocationHandler, чтобы метод getPassword для одного прокси объекта возвращал сгенерированный пароль только один раз, а при последующих вызовах на том же самом прокси генерировал исключение IllegalStateException("Password has already been shown")? Прокси объекты могут выступать обёртками над реальными объектами. Такой способ может быть полезным в случаях, когда: нет возможности изменить реализацию метода, а наследование не годится (например, для final классов); необходимо отделить бизнес-логику от сервисного кода (логирование, метрики), что очень похоже на аспектно-ориентированное программирование. Для реализации обёртки необходимо: в конструктор обработчика прокси объектов передать готовый объект, в методе invoke делегировать все вызовы готовому объекту, выполнить сервисный код перед или после вызова метода. В качестве примера можно рассмотреть логирование методов произвольного объекта, тогда: реализация InvocationHandler для обертки: Пояснения: класс LoggerWrapperInvocationHandler параметризован типом обёртываемого объекта, метод invoke делегирует вызов каждого метода объекту delegate, перед вызовом любого метода фиксируется факт начала исполнения метода, а также запускается таймер, после завершения метода фиксируется информация о времени, затраченном на исполнение метода. создание прокси объекта: Код создания прокси объекта идентичен коду из предыдущих разделов, основные отличия: метод wrappedMap принимает в качестве аргумента произвольный объект map типа Map<K, V>; прокси объект реализует интерфейс Map; объект map передается в конструктор LoggerWrapperInvocationHandler. Spring полагается на проксирование объектов как на основной способ реализации служебного кода, в качестве примеров можно выделить: работа с базой данных через @Repository: программист описывает в своем коде интерфейс для работы с базой данных, а Spring генерирует типовую реализацию методов интерфейса; управление транзакциями через @Transactional: программист добавляет аннотацию @Transactional к своим методам, а Spring создает обёртки вокруг них, где стартует и фиксирует транзакции; обработка REST API через @RestController: программист добавляет аннотацию @RestController к своему классу, а также указывает какие методы класса какие запросы обрабатывают, а Spring поднимает ServletContext; чтение из Kafka через @KafkaListener, работа с потоками через @Async, работа с задачами по расписанию @Scheduled и многое другое - всё работает через прокси объекты. Замечание: Внутри Spring использует по умолчанию CGlib в качестве библиотеки для создания прокси объектов, что позволяет создавать прокси объекты даже для классов, а не только для интерфейсов как в java.lang.reflect.Proxy. Использование cglib избавило программистов от необходимости объявлять интерфейсы к Spring бинам. Рассмотрение возможностей библиотеки cglib выходит за пределы статьи, поэтому проксирование будет выполняться при помощи класса Proxy из стандартной поставки JDK, что в любом случае позволит продемонстрировать идею. Любой класс может стать Spring бином, если добавить к нему необходимые аннотации. По умолчанию Spring создает по одному экземпляру каждого бина на протяжении жизни приложения. Spring Framework стратегически расположил несколько точек расширения, в которых программист может выполнить свой код. Код может быть определен в самом классе бина, для этого необходимо на методы класса повесить аннотации: @PostConstruct - выполнить код после того, как бин создан; @PreDestroy - выполнить код перед тем, как приложение завершит свою работу, а бин будет уничтожен. Подход с выполнением кода напрямую в бине можно рассматривать как локальную точку расширения. Локальная точка расширения не годится, когда необходимо выполнить один и тот же код для всех бинов. В этом случае в дело вступает org.springframework.beans.factory.config.BeanPostProcessor - глобальная точка расширения. Бин BeanPostProcessor пропускает через себя все остальные Spring бины и позволяет выполнить один и тот же код для них. При этом можно фильтровать бины по метаданным, записанными в виде аннотаций. Например, можно собрать все @RestConroller бины и создать для них сервлеты, или собрать все @KafkaListener бины и зарегистрировать их как Kafka консьюмеры. Объектов типа BeanPostProcessor может быть много, каждый из которых выполняет свою работу, и в этом заключается "магия" Spring Framework. В качестве примера использования BeanPostProcessor предлагается рассмотреть автоматическую конфигурацию JMX MBean объектов для каждого бина, помеченного аннотацией @JmxExporter: Java Management Extensions (JMX) - это технология, которая позволяет управлять java приложением в реальном времени. Технология JMX вводит понятие MBean, что представляет собой объект, который может вызывать код приложения. Все MBean бины регистрируются на сервере, к которому можно подключиться извне и выполнить код приложения через один из зарегистрированных MBean бинов. Самым большим ограничением работы с MBean бинами является список доступных типов: примитивы (int, boolean, char и т.д.), обёртки над примитивами (Integer, Boolean, Character и т.д.), java.lang.String byte[]. Для обхода этого ограничения при работе с более сложными типами можно сериализовать объекты в json и обратно, что потребует дополнительной работы во время регистрации и вызова MBean объектов. Самый простой JMX клиент - это jconsole, который входит в стандартную поставку JDK. Любой Spring бин, который помечен аннотацией @JmxExporter должен выставить соответствующий ему MBean, т.е. необходимо выполнить одинаковый код для неизвестного числа бинов, а значит необходимо реализовать BeanPostProcessor: Пояснения: каждый Spring бин после создания пройдет по всем BeanPostProcessor объектам и будет передан в метод postProcessAfterInitialization; регистрация MBean объекта для бина выполняется в registerMBean и вынесена в отдельный метод, чтобы повысить уровень читаемости кода; Spring бины без аннотации @JmxExporter пропускаются; если имя не указано через аннотацию @JmxExporter, то имя Spring бина будет использоваться в качестве имени объекта MBean; метод ManagementFactory#getPlatformMBeanServer возвращает текущий объект MBeanServer, который является singleton объектом; объект MBean реализует интерфейс javax.management.DynamicMBean и создается в методе getDynamicMBean; Объект MBean реализует интерфейс DynamicMBean, а значит является кандидатом для создания прокси объекта. Создание объекта MBean выполняется в методе JmxExporterPostProcessor#getDynamicMBean: Создание MBean объекта аналогично созданию прокси объекта из предыдущих разделов, отличием является тот факт, что Spring бин передается в JmxWrapperInvocationHandler, где бин используется в качестве делегата для вызова методов на прокси объекте. Реализация InvocationHandler для MBean бинов: Пояснения: Реализация InvocationHandler для MBean бинов аналогична реализации обработчика для обёртки над готовым объектом; интерфейс javax.management.DynamicMBean содержит шесть методов, но только два из них имеют нетривиальную обработку:  getMBeanInfo возвращает объект MBeanInfo - метаданные MBean бина, в том числе и список доступных операций (каждая операция имеет метод в классе переданного Spring бина); invoke вызывает одну из доступных операций. метод invokeProxy обрабатывает вызов операции; поиск метода Spring бина для вызова выполняется по имени операции и сигнатуре; сигнатура метода Spring бина вычисляется по массиву классов переданных аргументов. Приведенная реализация JmxWrapperInvocationHandler намеренно упрощена в угоду компактности. Она не будет работать, если Spring бин выставляет методы, в сигнатуре которых есть типы, отличные от примитивных, String и byte[]. Более пригодную для промышленной эксплуатации версию можно найти в конце статьи или в репозитории с практикой. Демонстрация конфигурации MBean бинов по Spring бинам будет выполняться на примере REST контроллера: Класс является типовым REST контроллером, поэтому дополнительные пояснения излишни. Стоит отметить лишь, что в дополнение к аннотации @RestController появилась аннотация @JmxExporter. Customer - это record с тремя полями: Запустив приложение, можно подключиться к нему по JMX и увидеть, что CustomerController зарегистрировался в качестве MBean бина, который имеет все три операции: add, get, list. Добавить клиента через curl, получить через JMX: Добавить клиента через JMX, получить через curl:  Получить список всех клиентов В статье было продемонстрировано, как при помощи прокси BeanPostProcessor можно создать "магию" Spring своими руками. Полный проект можно найти в репозитории с практикой, где так же добавлены тесты и параметры запуска. В дополнение можно запустить проект на Gitpod, виртуальной машине в облаке (50 часов в месяц бесплатно).  Код в статье был намеренно упрощен в угоду компактности и фокусировке на основной теме, а поэтому если переносить его один-в-один, то не все может работать. Более полную версию можно получить в репозитории с практикой. Ниже в спойлерах приведен полный код для историчности или на случай, если репозиторий на GitHub пропадет или станет недоступен. В качестве задания для самостоятельной работы предлагается внедрить прокси объект типа org.slf4j.Logger во все поля Spring бинов, которые помечены аннотацией @WithLogger.