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

Что такое паттерн прокси?

2.0 Middle🔥 161 комментариев
#SOLID и паттерны проектирования

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

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

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

# Что такое паттерн Прокси (Proxy Pattern)

Паттерн Прокси (Proxy Pattern) — это структурный паттерн проектирования, который предоставляет промежуточный объект (прокси) для контроля доступа к другому объекту. Прокси выступает как заместитель или плацдарм для контроля доступа к реальному объекту.

Основная идея

Вместо прямого обращения к объекту, клиент обращается к прокси, который:

  • Может отложить создание реального объекта
  • Контролирует доступ
  • Логирует операции
  • Добавляет кэширование
  • Выполняет проверки безопасности

Простой пример

// Интерфейс, который реализуют и реальный объект, и прокси
public interface Document {
    void open();
    void close();
    String getContent();
}

// Реальный объект — тяжёлый, дорого создавать
public class RealDocument implements Document {
    private String filename;
    private String content;
    
    public RealDocument(String filename) {
        this.filename = filename;
        System.out.println("Загрузка документа: " + filename);  // Дорогая операция
        this.content = loadFromDisk(filename);
    }
    
    @Override
    public void open() {
        System.out.println("Открыт документ: " + filename);
    }
    
    @Override
    public void close() {
        System.out.println("Закрыт документ: " + filename);
    }
    
    @Override
    public String getContent() {
        return content;
    }
    
    private String loadFromDisk(String filename) {
        // Дорогостоящая операция загрузки
        return "Содержимое " + filename;
    }
}

// Прокси — ленивая инициализация
public class DocumentProxy implements Document {
    private String filename;
    private RealDocument realDocument;
    
    public DocumentProxy(String filename) {
        this.filename = filename;
        // Не создаём RealDocument, пока он не понадобится
    }
    
    // Инициализация при первом обращении
    private RealDocument getRealDocument() {
        if (realDocument == null) {
            realDocument = new RealDocument(filename);
        }
        return realDocument;
    }
    
    @Override
    public void open() {
        getRealDocument().open();
    }
    
    @Override
    public void close() {
        getRealDocument().close();
    }
    
    @Override
    public String getContent() {
        return getRealDocument().getContent();
    }
}

// Использование
Document doc = new DocumentProxy("report.pdf");
// Реальный документ ещё не загружен

doc.open();  // Теперь загружается
String content = doc.getContent();
doc.close();

Типы прокси

1. Ленивая инициализация (Lazy Initialization Proxy)

public class DatabaseProxy implements Database {
    private RealDatabase realDb;
    
    @Override
    public ResultSet query(String sql) {
        // Создание БД происходит только при первом запросе
        if (realDb == null) {
            realDb = new RealDatabase();
        }
        return realDb.query(sql);
    }
}

2. Контроль доступа (Protection Proxy)

public interface BankAccount {
    void withdraw(double amount);
    double getBalance();
}

public class BankAccountProxy implements BankAccount {
    private RealBankAccount realAccount;
    private User currentUser;
    
    public BankAccountProxy(RealBankAccount realAccount, User user) {
        this.realAccount = realAccount;
        this.currentUser = user;
    }
    
    @Override
    public void withdraw(double amount) {
        // Проверка прав доступа
        if (!currentUser.hasPermission("withdraw")) {
            throw new AccessDeniedException("Недостаточно прав");
        }
        
        // Проверка лимита
        if (amount > realAccount.getBalance()) {
            throw new InsufficientFundsException("Недостаточно средств");
        }
        
        realAccount.withdraw(amount);
    }
    
    @Override
    public double getBalance() {
        return realAccount.getBalance();
    }
}

3. Логирование (Logging Proxy)

public class UserServiceProxy implements UserService {
    private RealUserService realService;
    private Logger logger = LoggerFactory.getLogger(UserServiceProxy.class);
    
    @Override
    public User getUserById(int id) {
        logger.info("Запрос пользователя с ID: " + id);
        long startTime = System.currentTimeMillis();
        
        User user = realService.getUserById(id);
        
        long duration = System.currentTimeMillis() - startTime;
        logger.info("Пользователь найден за " + duration + "ms");
        
        return user;
    }
    
    @Override
    public void saveUser(User user) {
        logger.info("Сохранение пользователя: " + user.getId());
        realService.saveUser(user);
        logger.info("Пользователь сохранён");
    }
}

4. Кэширование (Caching Proxy)

public class CachedUserService implements UserService {
    private RealUserService realService;
    private Map<Integer, User> cache = new HashMap<>();
    
    @Override
    public User getUserById(int id) {
        // Проверяем кэш
        if (cache.containsKey(id)) {
            System.out.println("Из кэша: " + id);
            return cache.get(id);
        }
        
        // Загружаем из реального сервиса
        System.out.println("Загрузка из БД: " + id);
        User user = realService.getUserById(id);
        cache.put(id, user);
        return user;
    }
    
    @Override
    public void clearCache() {
        cache.clear();
    }
}

5. Удалённый прокси (Remote Proxy)

public interface ImageService {
    Image loadImage(String url);
}

public class RemoteImageProxy implements ImageService {
    private HttpClient httpClient;
    
    @Override
    public Image loadImage(String url) {
        // Загрузка через сеть
        ResponseBody body = httpClient.newCall(
            new Request.Builder().url(url).build()
        ).execute().body();
        
        return new Image(body.bytes());
    }
}

Реальный пример: Spring AOP прокси

public interface UserService {
    User getUserById(int id);
    void saveUser(User user);
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(int id) {
        return new User(id, "John");
    }
    
    @Override
    @Transactional  // Spring создаст прокси для управления транзакциями
    public void saveUser(User user) {
        // Сохранение в БД
    }
}

// Spring создаёт прокси, который:
// - Открывает транзакцию
// - Вызывает реальный метод
// - Коммитит или откатывает транзакцию

public class ProxyExample {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        UserService service = context.getBean(UserService.class);
        
        // service на самом деле прокси, а не UserServiceImpl
        System.out.println(service.getClass());  // Proxy class
        
        service.saveUser(new User(1, "Alice"));
    }
}

Пример с аннотациями

@Service
public class OrderService {
    @Cached(ttl = 3600)  // Custom аннотация
    public Order getOrder(int id) {
        // Прокси будет кэшировать результат
        return orderRepository.findById(id);
    }
    
    @RateLimited(maxRequests = 100)
    public List<Order> getAllOrders() {
        // Прокси ограничит количество запросов
        return orderRepository.findAll();
    }
    
    @LogExecutionTime
    public void createOrder(Order order) {
        // Прокси залогирует время выполнения
        orderRepository.save(order);
    }
}

@Aspect
@Component
public class CachingAspect {
    private Map<String, Object> cache = new HashMap<>();
    
    @Around("@annotation(cached)")
    public Object cache(ProceedingJoinPoint joinPoint, Cached cached) throws Throwable {
        String key = joinPoint.getSignature() + Arrays.toString(joinPoint.getArgs());
        
        if (cache.containsKey(key)) {
            return cache.get(key);
        }
        
        Object result = joinPoint.proceed();
        cache.put(key, result);
        return result;
    }
}

Плюсы и минусы

Плюсы

✅ Ленивая инициализация — создание объектов только при необходимости ✅ Контроль доступа — безопасность ✅ Логирование и мониторинг — отслеживание операций ✅ Кэширование — производительность ✅ Разделение ответственности — прокси занимается служебными операциями

Минусы

❌ Усложнение кода — дополнительный уровень абстракции ❌ Небольшое снижение производительности — дополнительный вызов ❌ Усложнение отладки — трудно отследить ошибки

Когда использовать

✅ Используй Proxy для:

  • Ленивой инициализации дорогостоящих объектов
  • Контроля доступа и безопасности
  • Логирования и мониторинга
  • Кэширования результатов
  • Удалённого доступа к объектам

❌ Избегай, если:

  • Нужна максимальная производительность
  • Простое логирование можно сделать иначе

Сравнение с другими паттернами

ПаттернНазначениеКогда использовать
ProxyКонтроль доступа к объектуБезопасность, логирование, кэш
DecoratorДобавление функциональностиРасширение возможностей
FacadeУпрощение интерфейсаУпрощение API
AdapterСовместимость интерфейсовИнтеграция различных систем

Заключение

Паттерн Proxy — это мощный инструмент для:

  • Контроля доступа к объектам
  • Логирования и мониторинга
  • Оптимизации производительности через кэширование
  • Ленивой инициализации

В Java паттерн широко используется в Spring Framework (через AOP) и в других фреймворках для создания прокси объектов.