Spring beans обычно создаются при старте контекста с использованием того или иного механизма инициализации. Но если надо, бин можно создать и после инициализации, когда контекcт уже запущен и работает.
Подготовка
Начнём с пустого maven проекта, в который добавим Junit, Hamcrest, EasyMock и Spring:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <javaee.version>7.0</javaee.version> <org.springframework.version>4.1.7.RELEASE</org.springframework.version> <junit.version>4.12</junit.version> <hamcrest.version>1.3</hamcrest.version> <easymock.version>3.3.1</easymock.version> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${org.springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>${hamcrest.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>${easymock.version}</version> </dependency> </dependencies> |
Нам понадобятся два бина. Один мы будем внедрять:
1 2 3 4 5 6 7 8 9 10 11 | public interface TargetService { String getTarget(); } @Service public class TargetServiceImpl implements TargetService { @Override public final String getTarget() { return "World"; } } |
Второй будем конструировать вручную:
1 2 3 4 5 6 7 8 | public class GreeterService { @Inject private TargetService target; public final String greet() { return "Hello " + target.getTarget() + "!"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class GreeterServiceTest { @Rule public EasyMockRule em = new EasyMockRule(this); @Mock private TargetService targetService; @TestSubject private GreeterService testedObject = new GreeterService(); @Test public void testGreet() { expect(targetService.getTarget()).andStubReturn("TEST"); replay(targetService); assertThat(testedObject.greet(), is("Hello TEST!")); } } |
Обратите внимание, что у второго бина нет никакой аннотации, заставившей бы Spring его сконструировать.
Создание бина
Чтобы создать бин, нужно попопросить фабрику бинов создать его:
1 2 3 4 | AutowireCapableBeanFactory bf = context.getAutowireCapableBeanFactory(); GreeterService greeterBean = bf.createBean(GreeterService.class); System.out.println(greeterBean.greet()); |
context это экземпляр ApplicationContext. В моём примере я его создаю по месту, но можно его получить через интерфейс ApplicationContextAware или объявить его зависимостью через @Inject.
Вызов createBean() создаёт экземпляр класса и проводит все необходимые инициализационные работы: связывает зависимости, вызывает методы *Aware интерфейсов, вызывает метод инициализации итд. Однако этот бин существует только в виде объекта, который вернула createBean() и он не может быть запрошен из контекста и его жизненный цикл управляется вами, а не Srping’ом.
Регистрация бина
Чтобы создать «настоящий» бин, которым будет управлять Spring и который можно запросить из контекста, необходимо не инстанциировать класс напрямую, а описать будущий бин и попросить Spring построить его по этому описанию:
1 2 3 4 5 6 7 8 9 10 | GenericBeanDefinition gbd = new GenericBeanDefinition(); gbd.setBeanClass(GreeterService.class); gbd.setAutowireCandidate(true); gbd.setScope("singleton"); BeanDefinitionRegistry registry = (BeanDefinitionRegistry) bf; registry.registerBeanDefinition("greeter", gbd); GreeterService greeter = (GreeterService) context.getBean("greeter"); System.out.println(greeter.greet()); |
В объекте BeanDefintion можно описать все аспекты создания бина, которые доступны при инициализации с использованием xml или groovy. Описание бина регистрируется в контексте и дальше Spring управляет его жизненным циклом и доступом к бину.
Код примера доступен на github.