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

Можно ли сделать Proxy для обычного класса без использования Spring?

2.2 Middle🔥 201 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

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

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

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

Создание Proxy для обычного класса без Spring

Отличный вопрос о паттерне Proxy и reflection в Java! Короткий ответ: ДА, абсолютно можно — это делается с использованием java.lang.reflect.Proxy или через byte-code generation библиотеки вроде CGLib. Spring просто упрощает этот процесс.

Паттерн Proxy

Proxy — это структурный паттерн проектирования, который позволяет подставить объект-заместитель вместо настоящего объекта. Используется для:

  • Логирования методов
  • Кэширования результатов
  • Проверки прав доступа
  • Отложенной инициализации
  • Трансформации данных

Способ 1: Dynamic Proxy (java.lang.reflect.Proxy)

Это встроенный механизм Java для создания proxy на основе интерфейсов:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// Интерфейс
public interface UserService {
    String getName(int id);
    void updateUser(int id, String name);
}

// Реализация
public class UserServiceImpl implements UserService {
    public String getName(int id) {
        System.out.println("Fetching user from DB: " + id);
        return "User_" + id;
    }
    
    public void updateUser(int id, String name) {
        System.out.println("Updating user: " + id + " -> " + name);
    }
}

// InvocationHandler - перехватывает все вызовы методов
public class LoggingHandler implements InvocationHandler {
    private Object target;  // Оригинальный объект
    
    public LoggingHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // До вызова метода
        long startTime = System.currentTimeMillis();
        System.out.println("[LOG] Вызов метода: " + method.getName());
        if (args != null) {
            System.out.println("[LOG] Аргументы: " + Arrays.toString(args));
        }
        
        // Вызываем оригинальный метод
        Object result = method.invoke(target, args);
        
        // После вызова метода
        long duration = System.currentTimeMillis() - startTime;
        System.out.println("[LOG] Результат: " + result);
        System.out.println("[LOG] Время выполнения: " + duration + "ms");
        
        return result;
    }
}

// Создание proxy
public class ProxyDemo {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        
        // Создаем proxy
        UserService proxyService = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),
            new Class[]{UserService.class},
            new LoggingHandler(realService)
        );
        
        // Используем proxy вместо реального сервиса
        String name = proxyService.getName(1);
        proxyService.updateUser(1, "John");
    }
}

Вывод:

[LOG] Вызов метода: getName
[LOG] Аргументы: [1]
Fetching user from DB: 1
[LOG] Результат: User_1
[LOG] Время выполнения: 5ms

[LOG] Вызов метода: updateUser
[LOG] Аргументы: [1, John]
Updating user: 1 -> John
[LOG] Результат: null
[LOG] Время выполнения: 2ms

Способ 2: Proxy с кэшированием

public class CachingHandler implements InvocationHandler {
    private Object target;
    private Map<String, Object> cache = new HashMap<>();
    
    public CachingHandler(Object target) {
        this.target = target;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Создаем ключ кэша
        String cacheKey = method.getName() + Arrays.toString(args);
        
        // Проверяем кэш
        if (cache.containsKey(cacheKey)) {
            System.out.println("[CACHE] Возврат из кэша: " + cacheKey);
            return cache.get(cacheKey);
        }
        
        // Вызываем оригинальный метод
        Object result = method.invoke(target, args);
        
        // Сохраняем в кэш
        cache.put(cacheKey, result);
        System.out.println("[CACHE] Сохранено в кэш: " + cacheKey);
        
        return result;
    }
}

// Использование
UserService realService = new UserServiceImpl();
UserService cachedService = (UserService) Proxy.newProxyInstance(
    UserService.class.getClassLoader(),
    new Class[]{UserService.class},
    new CachingHandler(realService)
);

cachedService.getName(1);  // Будет запрос в БД
cachedService.getName(1);  // Из кэша

Способ 3: Proxy для обычного класса (без интерфейса) - CGLib

Для обычных классов без интерфейса используем CGLib (Code Generation Library):

<!-- pom.xml -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

// Обычный класс БЕЗ интерфейса
public class PlainUserService {
    public String getName(int id) {
        return "User_" + id;
    }
    
    public void updateUser(int id, String name) {
        System.out.println("Updating: " + id);
    }
}

// MethodInterceptor для CGLib
public class LoggingInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB] Перед: " + method.getName());
        
        // Вызываем оригинальный метод
        Object result = proxy.invokeSuper(obj, args);
        
        System.out.println("[CGLIB] После: " + method.getName());
        return result;
    }
}

// Создание proxy
public class CGLibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PlainUserService.class);
        enhancer.setCallback(new LoggingInterceptor());
        
        PlainUserService proxyService = (PlainUserService) enhancer.create();
        
        proxyService.getName(1);
        proxyService.updateUser(1, "John");
    }
}

Способ 4: Вручную (Decorator паттерн)

public interface UserService {
    String getName(int id);
    void updateUser(int id, String name);
}

public class UserServiceImpl implements UserService {
    public String getName(int id) {
        return "User_" + id;
    }
    
    public void updateUser(int id, String name) {
        System.out.println("Updating: " + id);
    }
}

// Proxy через Decorator
public class LoggingUserServiceDecorator implements UserService {
    private UserService delegate;
    
    public LoggingUserServiceDecorator(UserService delegate) {
        this.delegate = delegate;
    }
    
    public String getName(int id) {
        System.out.println("[LOG] Вызов getName(" + id + ")");
        String result = delegate.getName(id);
        System.out.println("[LOG] Результат: " + result);
        return result;
    }
    
    public void updateUser(int id, String name) {
        System.out.println("[LOG] Вызов updateUser(" + id + ", " + name + ")");
        delegate.updateUser(id, name);
        System.out.println("[LOG] Завершено");
    }
}

// Использование
UserService service = new UserServiceImpl();
UserService decorated = new LoggingUserServiceDecorator(service);
decorataed.getName(1);

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

ПодходИнтерфейсПростотаПроизводительность
java.lang.reflect.ProxyОбязателенПростойНизкая (reflection)
CGLibНе требуетсяСреднееВыше (byte-code gen)
DecoratorОбязателенПростойВысокая
Spring AOPОпционаленСамый простойКомбинированная

Практический пример: Аннотация + Proxy

// Аннотация
public @interface Cacheable {
    int ttl() default 3600;
}

// Класс с аннотацией
public interface DataService {
    @Cacheable(ttl = 1800)
    String fetchData(String key);
}

public class CacheableInvocationHandler implements InvocationHandler {
    private Object target;
    private Map<String, CacheEntry> cache = new HashMap<>();
    
    static class CacheEntry {
        Object value;
        long expiresAt;
        
        CacheEntry(Object value, long ttl) {
            this.value = value;
            this.expiresAt = System.currentTimeMillis() + ttl * 1000;
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() > expiresAt;
        }
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        
        if (cacheable == null) {
            return method.invoke(target, args);
        }
        
        String cacheKey = method.getName() + Arrays.toString(args);
        
        CacheEntry entry = cache.get(cacheKey);
        if (entry != null && !entry.isExpired()) {
            return entry.value;
        }
        
        Object result = method.invoke(target, args);
        cache.put(cacheKey, new CacheEntry(result, cacheable.ttl()));
        
        return result;
    }
}

Выводы

  1. Dynamic Proxy (java.lang.reflect.Proxy) — встроенное решение, просто настраивается
  2. CGLib — для классов без интерфейсов, лучше производительность
  3. Decorator — самый простой вариант, полный контроль
  4. Spring AOP — самый удобный для больших приложений
  5. Proxy полезен для кроссквартирного функционала: логирование, кэширование, валидация, трансформация
Можно ли сделать Proxy для обычного класса без использования Spring? | PrepBro