EzyFox Bean: Bean Creation
Updated at 17290682060001. Sequence of Bean Creation
- Firstly, EzyFox Bean will load configuration classes that be annotated by
EzyConfigurationBefore
, you should create and register which singletons that other classes need to use. - Secondly, EzyFox Bean will load configuration classes that be annotated by
EzyConfiguration
, if there is a configuration class depend on a singleton class, it will create that singleton. - Thirdly, EzyFox will create beans that be annotated by
EzySingleton
,EzyPrototype
andEzyPropertiesBean
. If there is any missing dependent bean, EzyFox Bean will print warning logs (by default) or throw exception by your configuration. - Finally, EzyFox Bean will load configuration classes that be annotated by
EzyConfigurationAfter
, you should use created beans and should not create any beans here.
2. Example
Let's say we need to create a web service to get data from a book service via API Gateway and provide Rest API to client. Firstly, to call to the API Gateway we need create a HTTP client with ezyhttp-client. Becase HttpClient is not annotated by EzySingleton
annotation, so we need a singleton for it. Because it's not depending on any another beans, so we can use EzyConfigurationBefore
for it:
@Setter @EzyConfigurationBefore public class HttpClientConfiguration { @Property("http.connect.timeout") private int connectTimeout; @EzySingleton public HttpClient httpClient() { return HttpClient .builder() .connectTimeout(connectTimeout) .build(); } }
The HttpClientConfiguration
class will be loaded first and the singleton of HttpClient
will be created first. Now, let's say we don't want to use HttpClient
directly, we want to use an BookGatewayProxy
like this:
@AllArgsConstructor public class BookGatewayProxy { private final String gatewayUrl; private final HttpClient httpClient; public Book getBookById(long bookId) throws Exception { return httpClient.call( new GetRequest() .setURL(gatewayUrl + "/api/v1/books" + bookId) .setResponseType(Book.class) ); } }
We can create singleton for the BookGatewayProxy
with EzyConfiguration
like this:
@Setter @EzyConfiguration public class BookGatewayConfiguration { @Property("book.gateway.url") private String gatewayUrl; @EzyAutoBind private HttpClient httpClient; @EzySingleton public BookGatewayProxy bookGatewayProxy() { return new BookGatewayProxy( gatewayUrl, httpClient ); } }
The class BookGatewayConfiguration
will be created after the HttpClientConfiguration
class, and the instance of BookGatewayProxy
will be created after HttpClient
. Now, we will create a service class named BookDataService
call to BookGatewayProxy
to get book data like this:
@EzySingleton @AllArgsConstructor public class BookDataService { private final BookGatewayProxy bookGatewayProxy; public Book getBookById(long bookId) { try { return bookGatewayProxy.getBookById(bookId); } catch (Exception e) { throw new NotFoundException("book not found", e); } } }
The singleton of BookDataService
will be created after the BookGatewayConfiguration
configuration and BookGatewayProxy
. Next step, we can create a controller named BookDataController
to return Book
data in json format like this:
@EzySingleton @AllArgsConstructor public class BookDataController { private final BookDataService bookDataService; public String getBookData(long bookId) { return bookDataService .getBookById(bookId) .toJson(); } }
The singleton of this class will be created after BookDataController
. Now, let's say we want to cache some books to highlight them, we can create a configuration class named HighlightBookConfiguration
and annotated it with EzyConfigurationAfter
annotation like this:
@EzyConfigurationAfter public class HighlightBookConfiguration implements EzyBeanConfig { @Setter @EzyAutoBind private BookDataService bookDataService; @Getter private final List highlightBooks = new ArrayList<>(); @Override public void config() { highlightBooks.add( bookDataService.getBookById(1) ); } }
This class will load after the all above class and EzyFox Bean will call config
method automatically.
3. Circular dependency
Circular dependency happend when we have some classes depend on each other like above image. With circular dependency, EzyFox Bean will throw an exception. Example, if you update BookDataService like this:
@EzySingleton @AllArgsConstructor public class BookDataService { private final BookGatewayProxy bookGatewayProxy; private final BookDataController bookDataController; }
And then, you run the application, you will get an exception like this:
Exception in thread "main" java.lang.IllegalStateException: can not create singleton of class class com.tvd12.ezyfox.example.bean.service.BookDataService at com.tvd12.ezyfox.bean.impl.EzySimpleSingletonLoader.load(EzySimpleSingletonLoader.java:53) at com.tvd12.ezyfox.bean.impl.EzySimpleBeanContext$Builder.createAndLoadSingleton(EzySimpleBeanContext.java:959) at com.tvd12.ezyfox.bean.impl.EzySimpleBeanContext$Builder.createAndLoadSingleton(EzySimpleBeanContext.java:946) at com.tvd12.ezyfox.bean.impl.EzySimpleBeanContext$Builder.addScannedSingletonsToFactory(EzySimpleBeanContext.java:935) at com.tvd12.ezyfox.bean.impl.EzySimpleBeanContext$Builder.build(EzySimpleBeanContext.java:853) at com.tvd12.ezyfox.bean.impl.EzySimpleBeanContext$Builder.build(EzySimpleBeanContext.java:207) at com.tvd12.ezyfox.example.bean.EzyFoxBeanExample.main(EzyFoxBeanExample.java:13) Caused by: java.lang.IllegalStateException: circular dependency detected, class com.tvd12.ezyfox.example.bean.service.BookDataService => class com.tvd12.ezyfox.example.bean.http.BookGatewayProxy => class java.lang.String => class com.tvd12.ezyhttp.client.HttpClient => class com.tvd12.ezyhttp.client.HttpClient$Builder => class com.tvd12.ezyfox.example.bean.controller.BookDataController => class com.tvd12.ezyfox.example.bean.service.BookDataService at com.tvd12.ezyfox.bean.impl.EzySimpleSingletonLoader.detectCircularDependency(EzySimpleSingletonLoader.java:251)
So, to avoid circular dependencies that can break your application, please organize your source code into multiple layers and don't add dependencies from higher layers to lower layers.