← Назад к вопросам

Как происходит инструментация в Spring

1.8 Middle🔥 181 комментариев
#Spring Boot и Spring Data

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Инструментация в Spring

Инструментация (instrumentation) в Spring - это механизм динамической модификации классов во время их загрузки в память, позволяющий добавлять дополнительное поведение без изменения исходного кода.

Основные способы инструментации в Spring

1. Load-Time Weaving (LTW) через Java Agent

Это процесс добавления кода к классам во время их загрузки классоloader'ом:

// Пример с аспектом
@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(public * com.example.service..*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Method: " + joinPoint.getSignature().getName());
    }
}

Для включения LTW нужна конфигурация:

<!-- META-INF/aop.xml -->
<aspectj>
    <weaver>
        <include within="com.example.service..*" />
    </weaver>
    <aspects>
        <aspect name="com.example.LoggingAspect" />
    </aspects>
</aspectj>

2. Runtime Weaving через Spring AOP

Spring AOP использует динамические прокси объекты:

@EnableAspectJAutoProxy // Включить AOP
@Configuration
public class AopConfig {
}

@Service
public class UserService {
    @Transactional // Это тоже инструментация!
    public void createUser(User user) {
        // Во время выполнения Spring обернет этот метод
        // для управления транзакциями
    }
}

Прокси будет выглядеть примерно так:

// Spring создает что-то вроде этого во время выполнения
public class UserServiceProxy extends UserService {
    public void createUser(User user) {
        // Инструментированный код
        Transaction tx = createTransaction();
        try {
            super.createUser(user);
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
            throw e;
        }
    }
}

3. Bytecode Generation через CGLIB и JDK Dynamic Proxy

Spring использует два способа создания прокси:

// JDK Dynamic Proxy - только для интерфейсов
public interface UserService {
    void createUser(User user);
}

// CGLIB - работает с классами
public class UserServiceImpl implements UserService {
    public void createUser(User user) {
        // ...
    }
}

// Spring выберет CGLIB, если класс не реализует интерфейс

Можно явно указать CGLIB:

@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration
public class AopConfig {
}

4. Процесс инструментации

1. Bean Definition - Spring читает конфигурацию
2. Bean Creation - создает экземпляр бина
3. Post-Processing - BeanPostProcessor'ы модифицируют бин
4. Proxy Creation - создается прокси объект
5. Runtime Execution - методы выполняются через прокси

5. Практические примеры инструментации

@Transactional:

@Service
public class OrderService {
    @Transactional
    public Order createOrder(Order order) {
        // Spring оборачивает в управление транзакцией
        orderRepository.save(order);
        return order;
    }
}

@Cached:

@Service
public class UserService {
    @Cacheable("users")
    public User findById(Long id) {
        // Spring добавляет кэширование перед выполнением
        return userRepository.findById(id).orElse(null);
    }
}

Custom аспект:

@Aspect
@Component
public class PerformanceAspect {
    
    @Around("@annotation(Monitored)")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.println("Method took: " + duration + "ms");
        }
    }
}

6. Проблемы и ограничения

  • Самовызовы не работают - аспекты не перехватывают вызовы внутри класса:
@Service
public class UserService {
    
    @Transactional
    public void create(User user) {
        userRepository.save(user);
        sendEmail(); // Аспект НЕ будет применен!
    }
    
    @Transactional
    private void sendEmail() {
        // Это не будет инструментировано при вызове из create()
    }
}
  • Финальные методы и классы не могут быть переопределены в CGLIB
  • Приватные методы не инструментируются

Сравнение подходов

СпособКогдаПроизводительностьГибкость
Runtime AOPБольшинство случаевХорошаяСредняя
LTWСложные требованияЛучшеВысокая
JDK ProxyС интерфейсамиСредняяНизкая
CGLIBБез интерфейсовСредняяСредняя

Вывод: инструментация в Spring - это мощный механизм добавления cross-cutting concerns (логирование, безопасность, транзакции, кэширование) без изменения исходного кода через использование прокси объектов и аспектов.

Как происходит инструментация в Spring | PrepBro