Динамическое создание Spring Bean в рантайме

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Перевод подготовлен специально для будущих студентов курса "Разработчик на Spring Framework".


Эта статья о динамическом создании бинов за пять лет стала самой популярной в моем блоге (более 9300 просмотров). Пришло время ее обновить. Также я добавил пример на Github.

динамика Spring Bean на Github
динамика Spring Bean на Github

Однажды на тренинге меня спросили: "Можно ли создать Spring Bean динамически, чтобы можно было выбрать реализацию во время выполнения". Так как во время компиляции еще не известно, какой бин должен быть создан. Приложение должно решить это на основе properties-файла.

1. Создадим аннотацию для того, чтобы отметить метод, который должен создавать объект динамически:

package your.package;

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectDynamicObject {
}

2. Далее используем ее в методе, который должен создать объект:

@Service
public class CustomerServiceImpl {
    private Customer dynamicCustomerWithAspect;
    
    @InjectDynamicObject
    public Customer getDynamicCustomerWithAspect() {
        return this.dynamicCustomerWithAspect;
    }
}

3. Напишем аспект с Pointcut и Advise, который изменяет объект, возвращаемый методом на шаге 2:

@Component
@Aspect
public class DynamicObjectAspect {
    // This comes from the property file
    @Value("${dynamic.object.name}")
    private String object;
    @Autowired
    private ApplicationContext applicationContext;
    
    @Pointcut("execution(@com.lofi.springbean.dynamic.
        InjectDynamicObject * *(..))")
    public void beanAnnotatedWithInjectDynamicObject() {
    }
    @Around("beanAnnotatedWithInjectDynamicObject()")
    public Object adviceBeanAnnotatedWithInjectDynamicObject(
        ProceedingJoinPoint pjp) throws Throwable {   
        pjp.proceed();
        
        // Create the bean or object depends on the property file  
        Object createdObject = applicationContext.getBean(object);
        return createdObject;
    }
}

4. Пишем класс, который должен возвращаться из @InjectDynamicObject. Имя класса настраивается в properties-файле. В данном примере я написал две реализации Customer: CustomerOneImpl и CustomerTwoImpl:

@Component("customerOne")
public class CustomerOneImpl implements Customer {
    @Override
    public String getName() {
        return "Customer One";
    }
}

application.properties
dynamic.object.name=customerOne

5. Пишем тест:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomerServiceImplTest {
    @Autowired
    private CustomerServiceImpl customerService;
    @Test
    public void testGetDynamicCustomerWithAspect() {
        // Dynamic object creation
        logger.info("Dynamic Customer with Aspect: " +
            customerService.getDynamicCustomerWithAspect()
            .getName());
}

Но есть еще, более простой, способ сделать это. Без аспектов и AspectJ, только чистый Spring. Можно просто сохранить все реализации в Map и получить из нее необходимую реализацию. Так мы сделали в приложении eXTra Client. В качестве примера можно посмотреть на реализацию PluginsLocatorManager. Spring автомагически инжектит Map с именем бина (String) и самим бином.

"… Даже типизированные Map можно инжектить автоматически, если тип ключа String. В значениях Map будут все бины ожидаемого типа, а в ключах — соответствующие имена бинов".

Подробнее см. в документации Spring.

@Service
public class CustomerServiceImpl {
    
    // We inject the customer implementations into a Map
    @Autowired
    private Map<String, Customer> dynamicCustomerWithMap;
    
    // This comes from the property file as a key for the Map
    @Value("${dynamic.object.name}")
    private String object;
    public Customer getDynamicCustomerWithMap() {
        return this.dynamicCustomerWithMap.get(object);
    }
}

Подробнее о курсе "Разработчик на Spring Framework" можно узнать здесь.

Источник: https://habr.com/ru/company/otus/blog/528312/


Интересные статьи

Интересные статьи

Привет, Хабр! Мне давно хотелось добавлять в любые проекты Spring-анимации. Но делал я это только для React-проектов с помощью react-spring, так как не знал ничего другого. Но наконец я ре...
Всем привет! Меня зовут Владислав Родин. В настоящее время я преподаю на портале OTUS курсы, посвященные архитектуре ПО и архитектуре ПО, подверженного высокой нагрузке. Сейчас в OTUS'е открыт на...
В этой статье вы узнаете, как с помощью Speedment создать полный CRUD REST API для базы данных. С каждым годом становится все очевиднее, что Spring Framework является одним из наиболее шир...
В Unity 2018.3 появилась поддержка изометрических тайловых карт, очень напоминающая поддержку тайловых карт шестиугольников, которая была добавлена в версии 2018.2. Новые функции Tilemap позвол...
На встрече московского сообщества Java-разработчиков jug.msk.ru, состоявшейся 28 июня 2019 года традиционно в офисе компании КРОК, Кирилл Толкачёв и Максим Гореликов представили свой доклад о Spr...