← Назад к вопросам
Можно ли сделать 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;
}
}
Выводы
- Dynamic Proxy (java.lang.reflect.Proxy) — встроенное решение, просто настраивается
- CGLib — для классов без интерфейсов, лучше производительность
- Decorator — самый простой вариант, полный контроль
- Spring AOP — самый удобный для больших приложений
- Proxy полезен для кроссквартирного функционала: логирование, кэширование, валидация, трансформация