Завершение бина жизненного цикла Spring Custom Scope - PullRequest
0 голосов
/ 23 мая 2018

Вопрос : Как я могу сказать Spring, что набор bean-компонентов с настраиваемой областью действия должен рассматриваться как мусор, чтобы следующий запрос в том же потоке не использовал их состояние?

Что я сделал : я реализовал пользовательскую область в Spring, чтобы имитировать жизненный цикл области запроса (HttpRequest), но для TcpRequests.Это очень похоже на то, что найдено здесь .

Множество примеров пользовательских областей видимости, которые я нахожу, представляют собой варианты прототипа или синглтона без явного завершения бинов или, в качестве альтернативы, они основаны навокруг локального потока или ThreadScope, но они не описывают сообщение Spring о том, что жизненный цикл закончился и все компоненты должны быть уничтожены.

То, что я пробовал (возможно, неправильно):

  • Событие + Прослушиватель для указания начала и конца области (это происходит при получении сообщения и непосредственно перед отправкой ответа);в слушателе область явно очищается, что очищает всю карту, используемую локальной реализацией потока (scope.clear ()).Очистка области действия приводит к следующему вызову context.getBean (), возвращающему новый экземпляр при обработке вручную в тестах, но мой bean-компонент, который автоматически подключается в одноэлементном классе, не получает новый bean-компонент - он использует один и тот же bean-компонент снова и снова.

  • Слушатель, который реализует: BeanFactoryPostProcessor, BeanPostProcessor, BeanFactoryAware, DisposableBean и пытается вызвать destroy () для всех экземпляров одноразового компонента;что-то вроде это , но только для моей пользовательской области.Похоже, что это не помогает, поскольку ничто явно не завершает жизненный цикл bean-компонентов, несмотря на то, что я вызываю customScope.clear (), когда получаю событие окончания области;завершение области, по-видимому, не переводится как «конец всех bean-компонентов, связанных с этой областью».

  • Я много читал документацию Spring и, похоже, ясно, что Spring неуправлять жизненным циклом этих пользовательских bean-компонентов, так как он не знает, когда и как они должны быть уничтожены, а это означает, что необходимо указать, когда и как их уничтожить;Я попытался прочитать и понять области Session и Request, предоставленные Spring, чтобы я мог имитировать это, но что-то упустил (опять же, они недоступны для меня, поскольку это не веб-приложение, и я неиспользуя HttpRequests, и это нетривиальное изменение в структуре нашего приложения)

Кто-нибудь может указать мне правильное направление?

У меня есть следующеепримеры кода:

Конфигурация Xml-контекста :

<int-ip:tcp-connection-factory id="serverConnectionFactory" type="server" port="19000" 
    serializer="javaSerializer" deserializer="javaDeserializer"/>

<int-ip:tcp-inbound-gateway id="inGateway" connection-factory="serverConnectionFactory"
    request-channel="incomingServerChannel" error-channel="errorChannel"/>

<int:channel id="incomingServerChannel" />

<int:chain input-channel="incomingServerChannel">
    <int:service-activator ref="transactionController"/>
</int:chain>

TransactionController (обрабатывает запрос) :

@Component("transactionController")
public class TransactionController {

    @Autowired
    private RequestWrapper requestWrapper;

    @ServiceActivator
    public String handle(final Message<?> requestMessage) {

        // object is passed around through various phases of application
        // object is changed, things are added, and finally, a response is generated based upon this data

        tcpRequestCompletePublisher.publishEvent(requestWrapper, "Request lifecycle complete.");

        return response;
    }
}

TcpRequestScope (определение области действия) :

@Component
public class TcpRequestScope implements Scope {

    private final ThreadLocal<ConcurrentHashMap<String, Object>> scopedObjects =
        new InheritableThreadLocal<ConcurrentHashMap<String, Object>>({

            @Override
            protected ConcurrentHashMap<String, Object> initialValue(){

                return new ConcurrentHashMap<>();
            }
        };

    private final Map<String, Runnable> destructionCallbacks =
        Collections.synchronizedMap(new HashMap<String, Runnable>());

    @Override
    public Object get(final String name, final ObjectFactory<?> objectFactory) {

        final Map<String, Object> scope = this.scopedObjects.get();
        Object object = scope.get(name);
        if (object == null) {
            object = objectFactory.getObject();
            scope.put(name, object);
        }
        return object;
    }

    @Override
    public Object remove(final String name) {

        final Map<String, Object> scope = this.scopedObjects.get();

        return scope.remove(name);
    }

    @Override
    public void registerDestructionCallback(final String name, final Runnable callback) {

        destructionCallbacks.put(name, callback);
    }

    @Override
    public Object resolveContextualObject(final String key) {

        return null;
    }

    @Override
    public String getConversationId() {

        return String.valueOf(Thread.currentThread().getId());
    }

    public void clear() {

        final Map<String, Object> scope = this.scopedObjects.get();

        scope.clear();

    }

}

TcpRequestCompleteListener :

@Component
public class TcpRequestCompleteListener implements ApplicationListener<TcpRequestCompleteEvent> {

    @Autowired
    private TcpRequestScope tcpRequestScope;

    @Override
    public void onApplicationEvent(final TcpRequestCompleteEvent event) {

        // do some processing

        // clear all scope related data (so next thread gets clean slate)
        tcpRequestScope.clear();
    }

}

RequestWrapper (объект, который мы используем на протяжении жизненного цикла запроса) :

@Component
@Scope(scopeName = "tcpRequestScope", proxyMode = 
ScopedProxyMode.TARGET_CLASS)
public class RequestWrapper implements Serializable, DisposableBean {


    // we have many fields here which we add to and build up during processing of request
    // actual request message contents will be placed into this class and used throughout processing

    @Override
    public void destroy() throws Exception {

        System.out.print("Destroying RequestWrapper bean");
    }
}

1 Ответ

0 голосов
/ 08 февраля 2019

После многих месяцев и нескольких попыток я, наконец, наткнулся на некоторые статьи, которые указали мне правильное направление.В частности, ссылки в блоге Дэвида Винтерфельда помогли мне понять SimpleThreadScope , который я ранее прочитал, и прекрасно осознавали тот факт, что Spring не пытается очистить область после завершения своего жизненного цикла, однако егоВ статье демонстрируется отсутствующая ссылка для всех предыдущих реализаций, которые я видел.

В частности, отсутствующие ссылки были статическими ссылками на ThreadScopeContextHolder в классе ThreadScope в его реализации (в предложенной выше реализации я назвал мой TcpRequestScope; остальная часть этогов ответе используются термины Дэвида Винтерфельда, поскольку его справочная документация окажется наиболее полезной, и он написал ее.)ThreadLocal, который содержит объект ThreadScopeAttributes, который содержит объекты в области действия.

Некоторые незначительные различия между им ДэвидомПлементация и мой последний были после того, как Spring Integration отправляет свой ответ, я использую ChannelInterceptor для очистки области потока, так как я использую Spring Integration.В своих примерах он расширил потоки, которые включали вызов держателя контекста как часть блока finally.

Как я очищаю атрибуты области действия / компоненты:

public class ThreadScopeInterceptor extends ChannelInterceptorAdapter {

@Override
public void afterSendCompletion(final Message<?> message, final MessageChannel channel, final boolean sent,
        @Nullable final Exception exception) {

    // explicitly clear scope variables
    ThreadScopeContextHolder.clearThreadScopeState();
}

Дополнительно,Я добавил метод в ThreadScopeContextHolder, который очищает ThreadLocal:

public class ThreadScopeContextHolder {

    // see: reference document for complete ThreadScopeContextHolder class

    /**
     * Clears all tcpRequest scoped beans which are stored on the current thread's ThreadLocal instance by calling
     * {@link ThreadLocal#remove()}.
     */
    public static void clearThreadScopeState() {

        threadScopeAttributesHolder.remove();
    }

}

Хотя я не совсем уверен, что не будет утечек памяти из-за использования ThreadLocal, я считаю, что это будет работать так, как я ожидаля вызываю ThreadLocal.remove (), который удалит единственную ссылку на объект ThreadScopeAttributes и, следовательно, откроет его для сборки мусора.

Любые улучшения приветствуются, особенно с точки зрения использования ThreadLocal и того, как это можетсоздавать проблемы в будущем.

Источники:

...