Каков наилучший способ реализовать синглтон с областью действия компонента?

Допустим, есть приложение, которое создает экземпляр класса Task каждый раз, когда ему нужно обработать какие-то данные. В экземпляр задачи внедрены некоторые другие службы, но все эти службы и сам объект задачи уникальны в пределах одного экземпляра задачи. Конечно, некоторые глобальные сервисы также внедряются, но они являются настоящими синглтонами для всего приложения. Итак, мой вопрос: как лучше всего настроить внедрение этих локальных (или ограниченных) одноэлементных экземпляров? В первую очередь я думаю об использовании дочернего контекста, но как его правильно настроить, все еще остается для меня вопросом. Еще одна вещь, которую следует упомянуть, это то, что я использую аннотации и конфигурацию на основе Java.


person Vitaly Tsaplin    schedule 11.12.2013    source источник
comment
Покажите код, для которого требуется экземпляр Task. Будет ли этот код иметь доступ к ApplicationContext?   -  person Sotirios Delimanolis    schedule 11.12.2013
comment
Итак, вам нужно, чтобы какие-то сервисы были прототипами, а какие-то синглтонами? Не могли бы вы пояснить, что это значит The task instance have some other services injected into it but all this services and the task object itself are unique within a single task instance   -  person Taylor    schedule 11.12.2013
comment
Это действительно может вам помочь. Похоже, фабричный подход, который я описываю, вероятно, то, что вам нужно.   -  person Floegipoky    schedule 11.12.2013
comment
@SotiriosDelimanolis, конечно, можно ввести ApplicationContext. Класс Task — это управляемый bean-компонент Spring.   -  person Vitaly Tsaplin    schedule 11.12.2013
comment
Затем просто объявите один bean-компонент для класса Task и введите в него все, что хотите. Что мы упускаем?   -  person Sotirios Delimanolis    schedule 11.12.2013
comment
"creates an instance of the Task class every time when it needs to process some data" Вы спрашиваете, как заполнить bean-компонент с областью действия прототипа как singleton, так и bean-компонентами с областью прототипа? Какие данные он обрабатывает? Или все это содержится в бобах-прототипах?   -  person Floegipoky    schedule 11.12.2013
comment
@Taylor, идея здесь в том, что когда экземпляр класса Task загружается контейнером IoC, некоторые зависимости рассматриваются как синглтоны в иерархии объектов Tasks. Другой экземпляр Task будет иметь свои собственные экземпляры singleton. Таким образом, «одиночки» здесь как бы ограничены или изолированы внутри Задач. Вы можете думать о каждом экземпляре Task как о маленьком приложении со своими синглтонами и прототипами. Имеет ли это смысл?   -  person Vitaly Tsaplin    schedule 11.12.2013
comment
@ user3091735 Нет, для меня это не имеет смысла. Одиночки являются глобальными во всей области вашего загрузчика классов. Bean-объекты с областью действия Singleton являются глобальными для всего контейнера Spring. Вы имеете в виду, что хотите раскрутить новый контейнер для каждой задачи? Это действительно плохая идея.   -  person Floegipoky    schedule 11.12.2013
comment
@ user3091735 Вы уверены, что на самом деле вам не нужен prototype bean-компонент с областью действия, внедренный с другими bean-компонентами с областью действия прототипа?   -  person Floegipoky    schedule 11.12.2013
comment
Итак, если задача использует ServiceA и ServiceB, а ServiceA также использует ServiceB, вы хотите, чтобы ServiceA и Task имели один и тот же экземпляр ServiceB для каждого экземпляра Task? Я думаю? Что произойдет, если ServiceA использует ServiceB без какой-либо задачи в стеке вызовов? Вы имеете в виду, что вам нужна область действия для каждого потока?   -  person Taylor    schedule 11.12.2013
comment
@Taylor, ты очень близок к сути. Могу привести еще один пример. Допустим, у нас есть приложение с многодокументным интерфейсом (MDI). Каждый документ (такой же, как ранее) является компонентом графического интерфейса (окном). У каждого есть свой собственный экземпляр UndoManager. Вы можете внедрить UndoManager в объект приложения или куда-то еще (вне окон), но это не имеет смысла, по крайней мере, в этой ситуации это не важно. Я хочу сказать, что в настоящее время у нас есть две противоположные вещи: синглтон и прототип. Singleton с ограниченной областью действия — это что-то среднее. Он является общим в рамках заданной иерархии объектов.   -  person Vitaly Tsaplin    schedule 11.12.2013
comment
@Floegipoky, каждый экземпляр bean-компонента с областью действия прототипа уникален. Это не то, чего я хочу. Мне нужно разделить экземпляр между объектами в иерархии объектов. Вы можете спросить, зачем мне это вообще нужно и почему бы не использовать bean-компоненты только с областью действия прототипа. Я хочу сказать, что если иерархия объектов, для которых требуется синглтон с ограниченной областью действия, довольно велика, мне нужно будет вручную передать экземпляр каждому компоненту, которому он нужен в качестве зависимости. В случае высокомодульного приложения большинство инъекций будет сделано таким образом, что сделает IoC бесполезным.   -  person Vitaly Tsaplin    schedule 11.12.2013


Ответы (4)


Я думаю, что настраиваемые области действия — это то, о чем вы просите. Тем не менее, некоторые слова предостережения: как правило, проблема, которую вы описываете, возникает из-за чрезмерно тесно связанной конструкции, а не из-за законной необходимости вставать по локоть во внутренности контейнера IOC. Вы можете быть одним из немногих людей, у которых действительно есть такая законная потребность, но гораздо более вероятно, что редизайн решит вашу проблему гораздо чище.

person Floegipoky    schedule 11.12.2013
comment
Единственный очевидный способ перепроектировать его — заменить зависимости в области модуля синглтонами и искать контекстные данные в этих синглтонах, используя уникальные идентификаторы задач в качестве ключей. - person Vitaly Tsaplin; 12.12.2013
comment
Оптимальный дизайн, к сожалению, редко бывает очевидным, и часто для его достижения требуется множество итераций. Тем не менее я готов поспорить на хорошие деньги, что лучшее, что вы можете сделать, это провести рефакторинг. Вероятно, есть некоторые из этих псевдо-синглетонов, которые вы можете сделать безгражданскими и/или разделить на части, чтобы упростить граф зависимостей. - person Floegipoky; 12.12.2013
comment
Абстрактная идея, стоящая за этой видимой сложностью, вращается вокруг довольно простой концепции повторно используемого компонента, который имеет общие зависимости внутри этого компонента. Мне интересно, как бы вы разделили автомобильный двигатель на части, чтобы отодвинуть его от автомобиля, а затем установить его как отдельное устройство, подключив его обратно к машине, используя шестерни или что-то подобное :) Что я действительно пытаюсь сделать? избежать — это сломать логическую абстракцию, ориентированную на компоненты, просто чтобы сделать IoC счастливым. Крайним средством было бы просто внедрить зависимости вручную. - person Vitaly Tsaplin; 12.12.2013
comment
Не все нужно знать обо всем двигателе. Выхлопу наплевать на коленвал. Двигатель — это всего лишь набор точек входа и путей, и большинство вещей, которые к нему подключаются, требуют знания лишь небольшой части. Сохраняйте свои компоненты, но не бойтесь критически взглянуть на части, из которых они состоят, или подумайте, что, возможно, границы между компонентами находятся не там, где должны быть. - person Floegipoky; 12.12.2013
comment
Как бы вы тогда спроектировали внедрение CarEngine в CarComputer и CarBody? - person Vitaly Tsaplin; 12.12.2013
comment
Двигатель преобразует топливо в линейную силу, а линейную силу — во вращательную силу. Тело озабочено потоком воздуха и пространством. Эти вещи связаны? Действительно ли тело заботится о двигателе или ему просто нужно место спереди? - person Floegipoky; 12.12.2013
comment
CarEngine — это интерфейс с методами start() и stop(). Топливо и силы - детали реализации :) - person Vitaly Tsaplin; 12.12.2013
comment
Вот вы говорите, но я бы пошел еще дальше и утверждал, что сама машина — это интерфейс, а двигатель — деталь реализации. - person Floegipoky; 12.12.2013
comment
@VitalyTsaplin или, может быть, транспорт - это то, что вас действительно волнует, а машина - деталь реализации. - person Floegipoky; 13.12.2013
comment
Все эти соображения дизайна хороши, и вы можете думать о топливе, силах и транспорте, но какой бы вопрос вы ни придумали, все тот же: что было бы лучшим решением для внедрения зависимостей на уровне компонентов с помощью Spring (или Guice)? Это не синглтоны и не прототипы. - person Vitaly Tsaplin; 13.12.2013

 private static final SingletonObject singleton = new SingletonObject();

Частный, чтобы сделать доступным только локально. Статический сделать только один. Final, чтобы остановить его изменение.

Если бы вам нужно было запретить другим людям создавать больше объектов SingletonObject, мне потребовалась бы дополнительная информация о контексте, и это может быть невозможно, но в большинстве случаев отсутствие блокировки на самом деле не является проблемой.

person Tim B    schedule 11.12.2013
comment
Я думаю, что цель состоит в том, чтобы получить экземпляр из контейнера Spring. - person Sotirios Delimanolis; 11.12.2013
comment
В этом случае это не локальный экземпляр, а локальная ссылка на глобальный экземпляр. Попытка использовать Spring для этого кажется огромным объемом работы для чего-то, что может быть одной строкой. Единственный возможный выигрыш - это внедрение зависимостей, вам потребуется немного больше работы, чтобы поддержать это, но это всего лишь пара строк. - person Tim B; 11.12.2013
comment
@TimB, на самом деле это не пара строк. У вас может быть довольно сложный граф зависимостей, который может отличаться в зависимости от выбранного профиля. - person Vitaly Tsaplin; 11.12.2013

Если я не ошибаюсь, вы хотели иметь
две переменные экземпляра класса «Задача», поскольку
«Подзадача» должна быть только одним экземпляром для каждого экземпляра задачи
«Глобальная задача» должна быть только одним экземпляром для каждого приложения / Spring. Контекст IOC
И каждый раз, когда вы создаете экземпляр «Задачи», вы хотите создать независимую «Подзадачу» и использовать один и тот же экземпляр GlobalTask.

если это правда, я не понимаю, в чем проблема
вы можете достичь, объявив "Task" и "SubTask" в качестве прототипа и "GlobalTask" по умолчанию как SingleTon, как показано ниже

@Component("Task")

@Scope("прототип") задача открытого класса {

public Task(){
    System.out.println("Task Created");
}

@Autowired
SubTask subTask;

@Autowired
GlobalTask globalTask;

public GlobalTask getGlobalTask() {
    return globalTask;
}

public void setGlobalTask(GlobalTask globalTask) {
    this.globalTask = globalTask;
}

public SubTask getSubTask() {
    return subTask;
}

public void setSubTask(SubTask subTask) {
    this.subTask = subTask;
}

}

@Component("SubTask")

@Scope("prototype") public class SubTask { public SubTask() { System.out.println("SubTask Created"); } public void PerformTask(){ System.out.println("Выполнить задачу"); } }

@Component("GlobalTask")

открытый класс GlobalTask ​​{

public GlobalTask(){
    System.out.println("Global task created");
}

public void performTask(){
    System.out.println("Perform Global Task");
}

}

person Community    schedule 11.12.2013
comment
Проблема с этим решением заключается в том, что когда SubTask дважды внедряется в Task или куда-то в иерархию объектов Task, мы будем и выше с 2 экземплярами SubTask, что нежелательно. Хорошим примером является автомобильный симулятор. В игре у вас может быть много экземпляров Car. У каждого автомобиля есть экземпляр Engine. Engine уникален в иерархии объектов Car. Вы можете внедрить Engine в CarComputer или CarBody, но это будет один и тот же экземпляр Engine. - person Vitaly Tsaplin; 11.12.2013
comment
Ох, хорошо. теперь я понимаю ваш вопрос. Вы можете попытаться реализовать CustomScope, внедрив Scope и зарегистрировав свою реализацию Scope вместо того, чтобы иметь отдельный IOC для каждой задачи. который может действовать на основе идентификатора задачи (некоторое свойство Unquie в задаче) - person Mani; 11.12.2013
comment
Любая идея о том, как будет выглядеть аннотация Scope, определяемая в классе SubTask? Я имею в виду, как бы вы привязали bean-компонент с областью задачи к области задачи, которая связана с экземпляром Task, который находится где-то ниже этого bean-компонента с областью задачи в иерархии объектов, принимая во внимание, что создание экземпляра является динамическим? - person Vitaly Tsaplin; 11.12.2013
comment
Я бы рекомендовал посмотреть, как реализуется SessionScope.java. ‹br› допустим, ваш экземпляр Task не виден в нескольких потоках в данный момент времени. Когда вы запускаете свою задачу, вы можете зарегистрировать свой идентификатор задачи в ThreadLocal, а в реализации CustomScope вы можете получить TaskId из того же ThreadLocal и сопоставить его с экземпляром SubTask. чтобы поток был локальным, когда когда-либо создавалась задача). Затем, где бы вы ни делали инъекцию в потоке для задачи, будет доставлен один и тот же экземпляр. какова ваша область ЗАДАЧИ - person Mani; 12.12.2013
comment
Какова ваша область действия экземпляра задачи? Будет ли он охватывать несколько потоков? В приведенном выше подходе вы можете иметь n экземпляров задач с одним потоком, но если ваша единственная задача порождает несколько потоков в данный момент времени, тогда описанный выше подход не будет работать, нам может потребоваться найти другой контекст - person Mani; 12.12.2013
comment
В случае bean-компонентов с потоками все было бы намного проще. Информация о потоке доступна в любой момент для любого объекта. Область действия здесь — это, ну, модуль задачи, сам экземпляр задачи и некоторые связанные зависимости. В примере с игрой Car это может быть модуль Car с CarEngine, CarComputer и CarBody в качестве bean-компонентов с модульной областью. С учетом всего сказанного решение дочернего контекста может быть самым простым. Все, что нам нужно, — это отдельная конфигурация spring для модуля и TaskFactory, которая создаст экземпляр дочернего контекста с конфигурацией модуля Task. - person Vitaly Tsaplin; 12.12.2013
comment
Даже когда вы реализуете дочерний контекст, вы должны предоставить кому-либо следующую область действия для вашего класса задач, верно? ‹br›1.singleton – возвращает один экземпляр компонента для каждого контейнера Spring IoC 2.prototype – возвращает новый экземпляр компонента каждый раз при запросе ‹br› 3.request – возвращает один экземпляр компонента для каждого HTTP-запроса. * ‹br› 4.session – возвращает один экземпляр компонента за HTTP-сессию. * ‹br› 5.globalSession — возвращает один экземпляр компонента на глобальный сеанс HTTP. * ‹br› вот что я спрашиваю, каков объем вашей задачи. Ваша подзадача может быть любой области - person Mani; 12.12.2013
comment
Задача — это bean-компонент с областью видимости прототипа. - person Vitaly Tsaplin; 12.12.2013

Решение, которое я наконец придумал, требует создания дочернего контекста. Ключевым моментом является указание другой дочерней конфигурации, чтобы родительский контекст не знал о зависимостях дочерних компонентов. Самое простое решение — создать отдельный java-конфиг с включенным сканированием компонентов и поместить его в отдельный пакет.

@Configuration
@ComponentScan
public class TaskConfig {}

public interface TaskFactory {
    Task createTask();  
}

@Component
public class TaskFactoryImpl implements TaskFactory {

    private ApplicationContext parentContext;

    @Autowired
    public void setParentContext(ApplicationContext parentContext) {
        this.parentContext = parentContext;
    }

    @Override
    public Task createTask() {
        try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
            context.register(TaskConfig.class);
            context.setParent(parentContext);
            context.refresh();
            return context.getBean(Task.class);
        }
    }
}
person Vitaly Tsaplin    schedule 20.01.2014