Как воспользоваться оригинальным методом после замены Proxy
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Доступ к оригинальному методу после замены Proxy
Это глубокий вопрос о том, как работает Proxy паттерн в Java и Spring AOP. Я разберу разные подходы в зависимости от сценария.
Контекст: Proxy паттерн
Когда используется Proxy (например, в Spring AOP), оригинальный объект оборачивается в proxy:
Запрос → Proxy → Дополнительная логика (logging, caching) → Оригинальный объект
Основная проблема
@Service
public class UserService {
@Cacheable("users")
public User getUser(UUID id) {
// Оригинальный метод
return userRepository.findById(id);
}
}
// При вызове:
UserService service = applicationContext.getBean(UserService.class);
User user = service.getUser(id); // Это вызывает PROXY, не оригинальный метод
Сценарий 1: Spring AOP и Self-invocation (Internal call)
Это наиболее частая проблема.
Проблема
@Service
public class UserService {
@Cacheable("users")
public User getUser(UUID id) {
System.out.println("Fetching user " + id);
return userRepository.findById(id);
}
public void updateUser(User user) {
// ...
getUser(user.getId()); // ❌ Вызывает оригинальный метод!
// Кэш не используется!
}
}
Почему? Когда updateUser вызывает getUser через this, это прямой вызов оригинального объекта, минуя proxy.
Решение 1: Inject самого себя
@Service
public class UserService {
private final UserRepository userRepository;
private final UserService self; // Inject self
public UserService(UserRepository userRepository,
UserService self) {
this.userRepository = userRepository;
this.self = self; // Это будет PROXY версия
}
@Cacheable("users")
public User getUser(UUID id) {
System.out.println("Fetching user " + id);
return userRepository.findById(id);
}
public void updateUser(User user) {
// ...
self.getUser(user.getId()); // ✅ Используется proxy!
// Кэш работает!
}
}
Как это работает:
Spring:
1. Создает UserService объект (оригинальный)
2. Оборачивает его в Proxy (добавляет @Cacheable функционал)
3. При inject'е self, передает PROXY версию
4. updateUser → self.getUser() → Proxy обрабатывает → original method
Решение 2: ObjectFactory
@Service
public class UserService {
private final UserRepository userRepository;
private final ObjectFactory<UserService> userServiceFactory;
public UserService(UserRepository userRepository,
ObjectFactory<UserService> userServiceFactory) {
this.userRepository = userRepository;
this.userServiceFactory = userServiceFactory;
}
@Cacheable("users")
public User getUser(UUID id) {
System.out.println("Fetching user " + id);
return userRepository.findById(id);
}
public void updateUser(User user) {
UserService proxy = userServiceFactory.getObject();
proxy.getUser(user.getId()); // ✅ Кэш работает
}
}
Решение 3: ApplicationContext
@Service
public class UserService implements ApplicationContextAware {
private ApplicationContext context;
private final UserRepository userRepository;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Cacheable("users")
public User getUser(UUID id) {
return userRepository.findById(id);
}
public void updateUser(User user) {
UserService proxy = context.getBean(UserService.class);
proxy.getUser(user.getId()); // ✅ Работает
}
}
⚠️ Минус: Явное получение из контекста не рекомендуется (Service Locator Pattern).
Сценарий 2: Программное использование Proxy без Spring
Dynamic Proxy (Java Reflection)
public interface UserService {
User getUser(UUID id);
void updateUser(User user);
}
public class UserServiceImpl implements UserService {
private final UserRepository repository;
public UserServiceImpl(UserRepository repository) {
this.repository = repository;
}
@Override
public User getUser(UUID id) {
System.out.println("Getting user");
return repository.findById(id);
}
@Override
public void updateUser(User user) {
// ... update logic
this.getUser(user.getId()); // ❌ Вызывает original, не proxy
}
}
// Создание Proxy
UserService original = new UserServiceImpl(repository);
UserService proxy = (UserService) Proxy.newProxyInstance(
UserService.class.getClassLoader(),
new Class[]{UserService.class},
new InvocationHandler() {
private final Map<UUID, User> cache = new ConcurrentHashMap<>();
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// Кэширование для getUser
if (method.getName().equals("getUser")) {
UUID userId = (UUID) args[0];
return cache.computeIfAbsent(userId,
id -> {
try {
return (User) method.invoke(original, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
}
// Обычный вызов для остальных методов
return method.invoke(original, args);
}
}
);
// Использование proxy
User user = proxy.getUser(id); // ✅ Кэшируется
Проблема self-invocation:
proxy.updateUser(user); // Вызывает proxy
├─ InvocationHandler.invoke(updateUser)
├─ method.invoke(original, args) // Вызывает ORIGINAL updateUser
└─ original.getUser(id) // ❌ Это вызывает original getUser, не proxy!
Решение: Pass proxy to original
public class UserServiceImpl implements UserService {
private final UserRepository repository;
private UserService selfProxy; // Хранит proxy
public void setSelfProxy(UserService proxy) {
this.selfProxy = proxy;
}
@Override
public void updateUser(User user) {
// ...
selfProxy.getUser(user.getId()); // ✅ Теперь работает!
}
}
// При создании proxy:
UserServiceImpl original = new UserServiceImpl(repository);
UserService proxy = (UserService) Proxy.newProxyInstance(...);
original.setSelfProxy(proxy); // Передаём proxy оригиналу
Сценарий 3: CGLIB Proxy (Spring default для классов)
Spring использует CGLIB для создания subclass'а:
// Original
@Service
public class UserService {
@Cacheable
public User getUser(UUID id) { ... }
}
// Spring создает динамически:
public class UserService$$EnhancedBySpringCGLIB$$xyz
extends UserService {
private UserService original;
@Override
public User getUser(UUID id) {
// Proxy логика
if (cache.contains(id)) return cache.get(id);
User result = super.getUser(id); // Вызывает original
cache.put(id, result);
return result;
}
}
Self-invocation проблема:
UserService service = new UserService$$EnhancedBySpringCGLIB$$xyz();
service.updateUser(user);
├─ UserService$$EnhancedBySpringCGLIB$$xyz.updateUser (proxy version)
├─ super.updateUser (original)
└─ this.getUser(id) // ❌ Это вызывает original getUser через super
// Не использует proxy!
Сценарий 4: Явный доступ к оригинальному методу
Иногда нужно обойти proxy специально:
Через AopProxyUtils
import org.springframework.aop.support.AopUtils;
import org.springframework.aop.support.AopProxyUtils;
@Service
public class OrderService {
public void processOrder(Order order) {
// Получить оригинальный объект (без proxy)
OrderService original = AopProxyUtils
.ultimateTargetClass(this);
// Вызвать оригинальный метод
// (обход @Transactional, @Cacheable и т.д.)
}
}
⚠️ Редко используется, обычно значит что-то неправильно в дизайне.
Через Reflection
public class ProxyAccessor {
public static <T> T getOriginal(Object proxy) {
try {
// CGLIB
if (proxy.getClass().getName().contains("$$EnhancedBySpringCGLIB")) {
Field field = proxy.getClass()
.getDeclaredField("CGLIB$BOUND");
// ...
}
// JDK Dynamic Proxy
if (Proxy.isProxyClass(proxy.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
Field field = handler.getClass()
.getDeclaredField("original");
field.setAccessible(true);
return (T) field.get(handler);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return (T) proxy;
}
}
⚠️ Очень плохая практика — нарушает инкапсуляцию и зависит от реализации.
Best Practices
1. Используй Inject self для Spring
@Service
public class UserService {
private final UserService self;
public UserService(UserService self) {
this.self = self;
}
// Используй self.method() для вызовов с proxy
}
2. Разделяй методы по логике
Вместо self-invocation:
// ❌ Плохо
@Service
public class UserService {
@Cacheable
public User getUser(UUID id) { ... }
public void update(User user) {
getUser(user.getId()); // Self-invocation
}
}
// ✅ Хорошо
@Service
public class UserService {
private final UserRepository repository;
@Cacheable
public User getUser(UUID id) {
return repository.findById(id);
}
public void update(User user) {
User existing = getUser(user.getId()); // Вызывает proxy
// update logic
}
}
// Или используй repository напрямую внутри update
@Service
public class UserService {
private final UserRepository repository;
@Cacheable
public User getUser(UUID id) {
return repository.findById(id);
}
public void update(User user) {
// Используй repository напрямую, не getUser
User existing = repository.findById(user.getId());
// update logic
}
}
3. Будь осторожен с аннотациями
// ❌ Self-invocation проблема
@Service
public class TransactionService {
@Transactional
public void methodA() { ... }
@Transactional
public void methodB() {
methodA(); // ❌ Использует ORIGINAL, не proxy
// Новая транзакция не создается
}
}
// ✅ Решение: Inject self
@Service
public class TransactionService {
private final TransactionService self;
@Transactional
public void methodA() { ... }
@Transactional
public void methodB() {
self.methodA(); // ✅ Вызывает proxy, новая транзакция
}
}
Заключение
Доступ к оригинальному методу через proxy:
- Spring AOP: Используй inject self для вызовов с proxy
- Dynamic Proxy: Передай proxy оригиналу через setter
- Не обходи proxy без необходимости — это признак неправильного дизайна
- Лучший подход: Разделяй логику так, чтобы не было self-invocation проблем
Это advanced тема, показывающая глубокое понимание того, как работает Spring AOP и proxy паттерны.