Подтвердить что ты не робот

INTERFACES или TARGET_CLASS: Какой proxyMode я должен выбрать?

Я ищу способ сохранить свой объект, и кажется, что лучший подход - использовать прокси. Я нашел 2 аннотации в Интернете, которые я должен использовать:

@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)

или же

@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )

Более того, верно ли, что прокси- @Component @Scope("session") является лучшим способом использования, чем использование @Component @Scope("session") или использование @SessionAttributes?

4b9b3361

Ответ 1

Вам нужно будет понять, что каждая из этих аннотаций выбирает для себя. Смотрите Javadoc, здесь. Продолжайте для более подробного объяснения.

Первый

@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)

создает

динамический прокси JDK, реализующий все интерфейсы, предоставляемые классом целевого объекта

Другими словами, прокси будет подтипом интерфейсов, которые реализует класс целевого объекта, но не будет подклассом самого класса целевого объекта.

По сути, Spring делает следующее

public class Example {
    public static void main(String[] args) throws Exception {
        Foo target = new Foo();
        InvocationHandler proxyHandler = ... // some proxy specific logic, likely referencing the 'target'

        // works fine
        Printable proxy = (Printable) Proxy.newProxyInstance(Example.class.getClassLoader(),
                target.getClass().getInterfaces(), proxyHandler);

        // not possible, ClassCastException
        Foo foo = (Foo) proxy; 
    }

    public static class Foo implements Printable {
        @Override
        public void print() {
        }
    }

    public interface Printable {
        void print();
    }
}

Возвращаемый прокси не будет иметь тип Foo и поэтому вы не можете внедрить его в какие-либо цели этого типа. Например, Spring не сможет ввести его в поле, подобное

@Autowired
private Foo foo;

но успешно введет прокси в поле, подобное

@Autowired
private Printable printable;

Все вызовы прокси-сервера будут обрабатываться InvocationHandler (который обычно выполняет некоторую логику для конкретного случая использования, а затем делегирует целевому объекту).


Вторая аннотация

@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS )

создает

основанный на классе прокси (использует CGLIB).

Помимо интерфейсов, с помощью CGLIB Spring сможет создавать прокси, класс которого является подклассом целевого класса. По сути, это делает следующее

Foo target = new Foo();
net.sf.cglib.proxy.Enhancer enhancer = new net.sf.cglib.proxy.Enhancer();
enhancer.setInterfaces(target.getClass().getInterfaces());
enhancer.setSuperclass(target.getClass());
net.sf.cglib.proxy.MethodInterceptor interceptor = ... // some proxy specific logic, likely referencing the 'target'
enhancer.setCallback(interceptor);

// works fine
Foo proxy = (Foo) enhancer.create();

CGLIB создает новый класс, который является подклассом Foo и создает его экземпляр (вызывая конструктор Foo). Все вызовы прокси будут перехвачены предоставленным обратным вызовом (который обычно выполняет некоторую логику для конкретного случая использования, а затем делегирует объекту назначения).

Поскольку прокси-класс расширяет Foo, Spring может внедрить прокси в поле (или параметр конструктора/метода), например

@Autowired
private Foo injectMe;

Все это говорит о том, что если вы программируете для интерфейсов, то ScopedProxyMode.INTERFACES будет достаточно. Если нет, используйте ScopedProxyMode.TARGET_CLASS.


Что касается использования @SessionAttributes, это не альтернатива сессионным bean-объектам. Атрибуты сессий - это просто объекты, а не бины. Они не обладают полным жизненным циклом, возможностями инъекции, поведением прокси, которое может иметь бин.

Ответ 2

Если вы хотите сохранить весь bean в сеансе, используйте @Scope, иначе используйте @SessionAttributes. В случае использования @Scope, если класс реализует некоторые интерфейсы, то используйте режим прокси-сервера INTERFACES, если не использовать TARGET_CLASS.

Обычно ваша служба реализует интерфейс, который позволяет использовать прокси JDK (режим INTERFACES). Но если это не так, используйте TARGET_CLASS, который создает прокси CGLIB.

Использование INTERFACES следует использовать, если это возможно, и TARGET в качестве последнего средства, если bean не реализует интерфейсы.

Ответ 3

Просматривая сообщение в блоге, представленное в комментариях выше, я нашел комментарий с указанием прокси-интерфейсов на основе интерфейса.

На этом посту пользователь Flemming Jønsson опубликовал следующее:

Будьте осторожны с использованием прокси-серверов на основе интерфейса.

Если вы используете Spring Security или Spring Transactions, вы можете столкнуться с странностями при использовании прокси-серверов на основе интерфейса.

Например, если у вас есть фасоль T, и у этого компонента есть методы a() и b(), которые являются аннотированными транзакциями. Вызовы из других bean-компонентов непосредственно в() или b() будут вести себя правильно (как настроено). Однако, если вы вводите внутренний вызов - где a() вызывает b(), тогда b метаданные транзакций не будут иметь никакого эффекта. Причина в том, что при использовании прокси-серверов на основе интерфейса внутренний вызов не будет проходить через прокси-сервер, и, следовательно, у транзакционного перехватчика не будет возможности начать новую транзакцию.

То же самое касается безопасности. Если метод a() требует только USER-роли, но вызывает b(), для которого требуется ADMIN-роль, тогда внутренний вызов от a до b будет выполняться для любого пользователя USER без предупреждений. По той же причине, что и выше, внутренние вызовы не проходят через прокси-сервер, и, таким образом, перехватчик безопасности не имеет возможности действовать на вызов b() из a().

Чтобы решить такие проблемы, используйте targetClass.