-
Notifications
You must be signed in to change notification settings - Fork 0
Dependency Injection
Methods:
- getBean(String, Class)
- Gets bean by it's name and class or throws BeanNotFoundException if bean doesn't exists
- getBean(Class)
- Gets bean of given class.
- Throws BeanNotFoundException if no beans with given class are exist
- Throws BeanConflictException if there more than one bean of given class exists
- searchBean(String, Class)
- Searches for bean by it's name and class and returns Optional
- searchBean(Class)
- Searches for bean of given class and returns Optional
- loadConfiguration(Class)
- Loads configuration class
- Configuration class must be annotated as
@Configuration - Configuration class may contain annotations
@ComponentScan(String[])to provide packages with GameBean classes,@Components(Class[])to provide specific GameBean classes and methods annotated as GameBean
- loadBean(Class)
- Loads class annotated as
@GameBeaninto DI container
- Loads class annotated as
Start using DI container with creating configuration class:
//Configuration class must be annotated as @Configuration
@Configuration
public class BasicConfiguration {
}To load this configuration into DI container use loadConfiguration
BasicComponentManager.getDiContainer().loadConfiguration(BasicConfiguration.class);Sometimes DI container can't scan packages for classes you point to with @ComponentScan. Then you can use @Components:
@Configuration
@Components({MyBeanA.class, MyBeanB.class})
public class BasicConfiguration {
}You can create beans from classes and methods with @GameBean. This annotation has some properties you need to know about:
-
name - specifies bean name. If not specified, then bean will get method or class name in lower case
-
policy - bean creation policy. Default singleton - create once and always reuse it, object - create bean only on getBean call
-
order - specifies order in which annotated methods in configuration will be executed
-
variant - specifies variant this bean will be used in. Only beans with variant, which equals to variant specified in BasicDIContainer will be loaded. You can create beans for different use cases, build platforms, etc.
You can specify variant in BasicDIContainer constructor. @GameBean has two constants:
- DEFAULT_VARIANT - default variant used in empty
BasicDIContainerconstructor - GLOBAL_VARIANT - default variant used in
@GameBean. Beans with GLOBAL_VARIANT will be loaded anyway
//Create DI container with default variant
BasicDIContainer defaultContainer = new BasicDIContainer();
//Create DI container with Android variant
BasicDIContainer diContainer = new BasicDIContainer("Android");To specify variant in game bean use variant parameter
public interface ItemVariant {
String getVariant();
}
//Will be load if DI container variant is set to Android
@GameBean(variant = "Android")
public class AndroidVariant implements ItemVariant {
@Override
public String getVariant() {
return "Android";
}
}
//Will be loaded anyway, cause default variant is GameBean.GLOBAL_VARIANT
@GameBean
public class WindowsVariant implements ItemVariant {
@Override
public String getVariant() {
return "Windows";
}
}Sort methods in class by parameters count, then sorts method by order() in `@GameBean` then tries to search existed beans to inject in parameters. Configuration classes are bean providers. DIContainer loads them unordered, so methods in configuration class shouldn't rely on methods from other configuration classes or game bean classes.
Tries to create new instance of class. If class has a constructor annotated with `@Autofill` has parameters then tries to search for existing beans
//Configuration class must be annotated as @Configuration
@Configuration
//Will scan ru.danilarassokhin.example.component package for @GameBean classes and @Configuration classes
@ComponentScan("ru.danilarassokhin.example.component")
@Components({GameItem.class})
public class BasicConfiguration {
private long globalIdGenerator = -1;
//Will create bean of type BasicObjectCaster and name "objCaster"
@GameBean(name = "objCaster")
public BasicObjectCaster objCaster() {
return new BasicObjectCaster();
}
//Will add bean information of type Long and name "globalIdGenerator".
//Will create new object every time when you call getBean
//Will give new Long value every time you call getBean("globalIdGenerator", Long.class)
@GameBean(name = "globalIdGenerator", policy = GameBeanCreationPolicy.OBJECT, order = 1)
public Long generateId() {
return ++globalIdGenerator;
}
//Will create bean with type GameItem and name "createitem"
//Before method call will inject random bean of type Long as parameter if such bean exists
@GameBean(policy = GameBeanCreationPolicy.OBJECT, order = 2)
public GameItem createItem(@Qualifier("globalIdGenerator") Long id) {
GameItem item = new GameItem();
item.setId(id);
return item;
}
//Use @Qualifier to specify bean name to inject
@GameBean(name = "generate", order = 3)
public Long generate(@Qualifier("globalIdGenerator") Long id) {
return ++globalIdGenerator;
}
//Will create bean with type GameItem and name "gameItem"
//Will inject bean with name "globalIdGenerator" as Long arg
@GameBean(name = "gameItem", policy = GameBeanCreationPolicy.OBJECT, order = 4)
public GameItem createItemWithIdGenerator(@Qualifier("generate") Long id) {
return createItem(id);
}
}To create bean from class:
//Will create bean with name objCaster
@GameBean(name = "objCaster")
public final class BasicObjectCaster implements GameObjectCaster {
}After beans loading you can get them:
//Get bean by it's name and class
BasicObjectCaster basicObjectCaster = diContainer.getBean("objCaster", BasicObjectCaster.class);
//There is only one bean of type BasicObjectCaster, so bean name is not required
basicObjectCaster = diContainer.getBean(BasicObjectCaster.class);
//Get bean as interface
GameComponent simpleGameItem = diContainer.getBean("simpleGameItem", GameComponent.class);If bean doesn't exists then getBean will throw BeanNotFoundException.
Beans from DI container can be injected in class fields, constructor and method parameters. There two ways to inject beans:
- implicit, while other bean creation
- explicit, through
BasicComponentCreator
Both ways have a common part. Element must be annotated with @Autofill
public interface MyService {}
@GameBean(name = "MyServiceA")
public class MyServiceA implements MyService {}
@GameBean(name = "MyServiceB")
public class MyServiceB implements MyService {}
public class MyClass {
//Will inject bean of type MyService with name myservice
@Autofill
private MyService myService;
//Will inject beans of type MyService, MyServiceB into constructor parameters
@Autofill
public MyClass(MyService myServiceA, MyServiceB myServiceB) {}
}In example above beans will be injected into class field and constructor parameters. But in example above BeanConflictException will be thrown. That is because field and constructor of class MyClass requires parameter of type MyService, but there are more than one bean of type MyService exist: MyServiceA and MyServiceB. In such a situation you need to specify name of bean you want to inject with @Qualifier annotation. So, MyClass above should be changed to:
public class MyClass {
//Will inject bean of type MyService with name MyServiceA
@Autofill
@Qualifier("MyServiceA")
private MyService myService;
//Will inject beans of type MyService with name MyServiceA, MyServiceB into constructor parameters
@Autofill
public MyClass(@Qualifier("MyServiceA") MyService myServiceA, MyServiceB myServiceB) {}
}Now you have two ways to create object and inject beans into it
Make MyClass game bean and put it into DI container:
@GameBean
public class MyClass {
//Will inject bean of type MyService with name MyServiceA
@Autofill
@Qualifier("MyServiceA")
private MyService myService;
//Will inject beans of type MyService with name MyServiceA, MyServiceB into constructor parameters
@Autofill
public MyClass(@Qualifier("MyServiceA") MyService myServiceA, MyServiceB myServiceB) {}
}
//Load bean into container
diContainer.loadBean(MyClass.class);Use BasicComponentCreator to create object of any class:
MyClass myClassObj = BasicComponentCreator.create(MyClass.class);If you want to invoke some method after constructor, create methods with no parameters and annotate it with @Autofill
public class MyClass {
@Autofill
public void afterCreation() {
System.out.println("I've been created!")
}
}DIContainer uses different implementations of BeanScanner to determine if class should be recognized as a bean.
Create new implementation of BeanScanner and define predicate to determine if class should be recognized as a bean.
Then add it to container with diContainer.addBeanScanner(beanScanner)
This is example of RepositoryScanner class:
public class RepositoryScanner implements BeanScanner {
@Override
public boolean shouldBeLoaded(Class<?> c) {
return c.isAnnotationPresent(Repository.class);
}
}
basicDIContainer.addBeanScanner(new RepositoryScanner());DI container uses different implementations of BeanFactory to:
- Determine if BeanDefinition should be created
- Determine if bean instance should be created
- Create BeanDefinition from given class
You can create your own bean factory to process custom annotations and classes.
This is example from RepositoryBeanFactory:
public class RepositoryBeanFactory implements BeanFactory {
@Override
public BeanDefinition createBeanMetaInformationFromClass(Class<?> beanClass) {
Repository annotation = ComponentAnnotationProcessor.findAnnotation(
beanClass, Repository.class);
if (annotation == null) {
throw new RuntimeException("No @Repository annotation specified!");
}
String beanName = annotation.value();
if (beanName.isEmpty()) {
beanName = beanClass.getSimpleName().toLowerCase();
}
BeanDefinition beanDefinitionData = new BeanDefinition();
beanDefinitionData.setName(beanName);
beanDefinitionData.setVariant(GameBean.GLOBAL_VARIANT);
beanDefinitionData.setCreationPolicy(GameBeanCreationPolicy.SINGLETON);
beanDefinitionData.setRealType(beanClass);
beanDefinitionData.setBean(BasicStorage.createRepository(cast(beanClass)));
beanDefinitionData.setCreated(true);
beanDefinitionData.setReady(true);
return beanDefinitionData;
}
// We want to create BeanDefinition from beanClass if it is a repository
@Override
public boolean isShouldBeProcessed(Class<?> beanClass) {
return isStorageRepository(beanClass);
}
// We don't need to create bean instance, cause our repository is an interface
// and we already created it's BeanDefinition in createBeanMetaInformationFromClass()
@Override
public boolean isShouldBeCreated(Class<?> beanClass) {
return false;
}
private Class<? extends StorageRepository> cast(Class<?> beanClass) {
return (Class<? extends StorageRepository>) beanClass;
}
private boolean isStorageRepository(Class<?> beanClass) {
try {
cast(beanClass);
return true;
} catch (ClassCastException e) {
return false;
}
}
}
diContainer.addBeanFactory(new RepositoryBeanFactory());