Convert legacy code to Spring dependency injection - java

The legacy code snippet is shown as below. What I want to do is to convert this code to Spring. But the problem is Spring managed the dependency on it's own. My question is how to inject the serviceId provided by constructor?
public class MyService{
public Attribute getAttribute(){
int serviceId =1;
new ServiceDao(serviceId).getAttribute();
}
}
class ServiceDao{
private int serviceId;
ServiceDao(int serviceId){
this.serviceId = serviceId;
}
public Attribute getAttribute(){
//to get attribute
}
}

Basically you are trying to create new objects each time you call
new ServiceDao(serviceId).getAttribute();
This is purely against dependency injection. As your logic is based on the service ID you can create a service class as follows
#Service
class ServiceDao{
ServiceDao(){
}
public Attribute getAttribute(int serviceId){
//to get attribute
//return attribute based on service Id,
//if(serviceId==1)
//{ return new Attribute("Red");}
}
}
Your Myservice can be something like this
#Service
public class MyService{
#Autowired
ServiceDao dao;
public Attribute getAttribute(){
int serviceId =1;
return dao.getAttribute(1);
}
}

Related

Spring component returning list from previous run

I have a spring component which looks like this. It has other beans auto-wired to it. And these components are returned as a list by the method getConfigurators(String Id)
#Component
public class Configuration{
#Autowired
private Config Config1;
#Autowired
private Config Config2;
#Override
public List<Config> getConfigurators(String Id) {
return List.of(
Config1,
Config2);
}
}
When I run the application the first time, I get everything correct for getConfigurators(String Id). The subsequent runs, I get the values populated by the first run. I want that every time I run the application, the list is not pre populated with the config values from before.
The Config class looks like this
#Component
public class Config1 implements Config {
final List errors = new ArrayList<>();
#Override
public List<> validate() {
boolean isValid // some logic
if (!isValid) {
errors.add("Some error");
}
return errors;
}
}

How to provide converter service to POJO class without passing it as method argument

I have ShoppingList service which is responsible for generating shopping list and a IngredientConverter service which is a helping tool for converting objects. My current implementation looks like this
#Service
#AllArgsConstructor
public class ShoppingListService {
private final RecipeService recipeService;
private final IngredientConverter ingredientConverter;
public ShoppingList generateShoppingList(List<UUID> uuidsOfRecipes) {
List<Recipe> recipes = recipeService.getAllByIDIn(uuidsOfRecipes);
ShoppingList shoppingList = ShoppingList.empty();
for (Recipe recipe : recipes) {
shoppingList.addIngredients(recipe.getIngredients());
}
shoppingList.finishAddition(ingredientConverter);
return shoppingList;
}
}
#RequiredArgsConstructor
public class ShoppingList {
#Getter
private final List<IngredientQuantity> optimizedList;
private final Map<Ingredient, Integer> ingredientAmountMap;
public static ShoppingList empty() {
return new ShoppingList(new ArrayList<>(), new HashMap<>());
}
public void addIngredients(List<IngredientQuantity> ingredients) { ... }
public void addIngredient(IngredientQuantity ingredientQuantity) { ... }
public void finishAddition(IngredientConverter ingredientConverter) {
for (Ingredient ingredient : ingredientAmountMap.keySet()) {
IngredientQuantity ingredientQuantity = ingredientConverter.convertWithAmount(
ingredient.getName(),
ingredientAmountMap.get(ingredient),
ingredient.getUnit());
optimizedList.add(ingredientQuantity);
}
}
}
#Service
public class IngredientConverter {
public IngredientQuantity convertWithAmount(String name, int amount, Unit unit) { ... }
}
Is there a better strategy for providing IngredientConverter service to this class? Could I Autowire it somehow despite ShoppingList being POJO class? Should ShoppingList be marked as Component maybe? Not sure what is the best approach.
You cannot autowire service class into POJO. Autowire can be done only within spring managed classes. I can see that ShoppingList is not a spring managed class. Adding #Component will also not be ideal solution. AFAIK, The best solution to use here would be mapStruct. mapStruct can be used to map fields between entity and POJO. And in cases where any field has to be calculated separately, you can write your custom logic and autowire services. Below are steps
Add mapStruct library to pom.xml
Add below mapper class to your project. componentModel="spring" tells the system that this mapper is managed by spring.
All the fields that have same name will be automapped.
For fields which require conversions, you can write #BeforeMapping
Mapper(componentModel="spring")
public abstract class ShoppingListMapper
{
#Autowired
IngredientConverter ingredientConverter; //autowire method you use.
public abstract shoppingListToShoppingListDTO(ShoppingList shoppingList) throws Exception;
public abstract List<ShoppingList> mapShoppingListsToDTOs(List<ShoppingList> shoppingLists) throws Exception;
#BeforeMapping
public void convertLogic(ShoppingList la, #MappingTarget ShoppingListDTO slDto) throws Exception
{
//your logic to set required shoppinglist field using converter
}
}
If this example is not clear, you can refer to web for various mapstruct examples. Let me know if you need further help

Best way to dynamically resolve dependencies in Java Spring?

Let's say I have this code structure:
public class NotificationService {
public void send(Notification notification) {
// call other services and send the notification
}
}
public class OrderNotification implements Notification {
#Autowired
public TranslationService translationService;
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
So, my goal is to use the NotificationService in this way:
notificationService.send(new OrderNotification(orderNumber));
But I know that code above won't work, because of the translationService won't be resolved.
My goal is to pass custom parameters to my Notification classes and being able to use services inside that class. What is the best way to do it in the Spring?
I know that below is not the correct answer to your question. It is however a bad design pattern to combine Entities and Services. An Entity should only contain information about the object and not business logic. A Service contains all the business logic.
You need to separate your Service from your Entity.
OrderNotification looks like a regular entity. The entity should not contain business logic. You need a specific service for the business logic.
public class OrderNotification implements Notification {
private String orderNumber;
public OrderNotification(String orderNumber) {
this.orderNumber = orderNumber;
}
public String getMessage() {
return "Order number: " + orderNumber;
}
//Getter & Setters
...
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public void send(Notification notification) {
//I do not know what trans accepts, so I assume it can accept Notification
translationService.trans(notification.getMessage());
}
}
If you really need to combine the entity and service - Then I recommend this approach:
#Service
public class Master{
#Autowired
NotificationService notificationService
public void testMethod(){
Notification notification = notificationService.createOrder("order1");
notificationService.send(notification);
}
}
#Service
public class NotificationService {
#Autowired
public TranslationService translationService;
public Notification createOrder(String orderNumber){
return new OrderNotification(orderNumber, translationService);
}
public void send(Notification notification) {
// call other services and send the notification
notification.getMessage();
}
}
public class OrderNotification implements Notification {
private TranslationService translationService;
private String orderNumber;
//I have changed this constructor to accept TranslationService.
public OrderNotification(String orderNumber, TranslationService translationService) {
this.orderNumber = orderNumber;
this.translationService = translationService;
}
public String getMessage() {
return translationService.trans('notification.order', new Object[]{orderNumber});
}
}
You have few options available:
Configure AOP and load time weaving to process Spring annotations on objects created with new keyword. This is explained in the docs 5.8.1. Using AspectJ to dependency inject domain objects with Spring.
Declare OrderNotification as a prototype scoped bean and obtain each instance from the context using BeanFactory.getBean(Class<T> requiredType, Object... args) method.
String orderNumber = "123";
OrderNotificaton = factory.getBean(OrderNotificaton.class, orderNumber);
Drop the #Autowired and use plain constructor injection.
public OrderNotification(TranslationService translationService, String orderNumber) {
this.translationService = Objects.requireNonNull(translationService);
this.orderNumber = Objects.requireNonNull(orderNumber);
}
If you only require simple #Autowired I'd go with option 3. It's the simplest approach and makes writing unit tests easier as you don't have to depend on Spring.

DeltaSpike custom ConfigSource with CDI

I am trying to define a custom DeltaSpike ConfigSource. The custom config source will have the highest priority and check the database for the config parameter.
I have a ConfigParameter entity, that simply has a key and a value.
#Entity
#Cacheable
public class ConfigParameter ... {
private String key;
private String value;
}
I have a #Dependent DAO that finds all config parameters.
What I am trying to do now, is define a custom ConfigSource, that is able to get the config parameter from the database. Therefore, I want to inject my DAO in the ConfigSource. So basically something like
#ApplicationScoped
public class DatabaseConfigSource implements ConfigSource {
#Inject
private ConfigParameterDao configParameterDao;
....
}
However, when registering the ConfigSource via META-INF/services/org.apache.deltaspike.core.spi.config.ConfigSource, the class will be instantiated and CDI will not work.
Is there any way to get CDI working in this case?
Thanks in advance, if you need any further information, please let me know.
The main problem is, that the ConfigSource gets instantiated very early on when the BeanManager is not available yet. Even the JNDI lookup does not work at that point in time. Thus, I need to delay the injection/lookup.
What I did now, is add a static boolean to my config source, that I set manually. We have a InitializerService that makes sure that the system is setup properly. At the end of the initialization process, I call allowInitialization() in order to tell the config source, that the bean is injectable now. Next time the ConfigSource is asked, it will be able to inject the bean using BeanProvider.injectFields.
public class DatabaseConfigSource implements ConfigSource {
private static boolean allowInit;
#Inject
private ConfigParameterProvider configParameterProvider;
#Override
public int getOrdinal() {
return 500;
}
#Override
public String getPropertyValue(String key) {
initIfNecessary();
if (configParameterProvider == null) {
return null;
}
return configParameterProvider.getProperty(key);
}
public static void allowInitialization() {
allowInit = true;
}
private void initIfNecessary() {
if (allowInit) {
BeanProvider.injectFields(this);
}
}
}
I have a request-scoped bean that holds all my config variables for type-safe access.
#RequestScoped
public class Configuration {
#Inject
#ConfigProperty(name = "myProperty")
private String myProperty;
#Inject
#ConfigProperty(name = "myProperty2")
private String myProperty2;
....
}
When injecting the Configuration class in a different bean, each ConfigProperty will be resolved. Since my custom DatabaseConfigSource has the highest ordinal (500), it will be used for property resolution first. If the property is not found, it will delegate the resolution to the next ConfigSource.
For each ConfigProperty the getPropertyValue function from the DatabaseConfigSource is called. Since I do not want to retreive the parameters from the database for each config property, I moved the config property resolution to a request-scoped bean.
#RequestScoped
public class ConfigParameterProvider {
#Inject
private ConfigParameterDao configParameterDao;
private Map<String, String> configParameters = new HashMap<>();
#PostConstruct
public void init() {
List<ConfigParameter> configParams = configParameterDao.findAll();
configParameters = configParams.stream()
.collect(toMap(ConfigParameter::getId, ConfigParameter::getValue));
}
public String getProperty(String key) {
return configParameters.get(key);
}
}
I could sure change the request-scoped ConfigParameterProvider to ApplicationScoped. However, we have a multi-tenant setup and the parameters need to be resolved per request.
As you can see, this is a bit hacky, because we need to explicitly tell the ConfigSource, when it is allowed to be instantiated properly (inject the bean).
I would prefer a standarized solution from DeltaSpike for using CDI in a ConfigSource. If you have any idea on how to properly realise this, please let me know.
Even though this post has been answered already I'd like to suggest another possible solution for this problem.
I managed to load properties from my db service by creating an #Signleton #Startup EJB which extends the org.apache.deltaspike.core.impl.config.BaseConfigSource and injects my DAO as delegate which I then registered into the org.apache.deltaspike.core.api.config.ConfigResolver.
#Startup
#Singleton
public class DatabaseConfigSourceBean extends BaseConfigSource {
private static final Logger logger = LoggerFactory.getLogger(DatabaseConfigSourceBean.class);
private #Inject PropertyService delegateService;
#PostConstruct
public void onStartup() {
ConfigResolver.addConfigSources(Collections.singletonList(this));
logger.info("Registered the DatabaseConfigSourceBean in the ConfigSourceProvider ...");
}
#Override
public Map<String, String> getProperties() {
return delegateService.getProperties();
}
#Override
public String getPropertyValue(String key) {
return delegateService.getPropertyValue(key);
}
#Override
public String getConfigName() {
return DatabaseConfigSourceBean.class.getSimpleName();
}
#Override
public boolean isScannable() {
return true;
}
}
I know that creating an EJB for this purpose basically produces a way too big overhead, but I think it's a bit of a cleaner solution instead of handling this problem by some marker booleans with static accessors ...
DS is using the java se spi mechanism for this which is not CD'Injectable'. One solution would be to use the BeanProvider to get hold of your DatabaseConfigSource and delegate operations to it.

Spring : how to get values from properties file in Model class

I am using Spring MVC for project. I have some constant values which is stored in properties file and I want to fetch from properties file. Question I am unable to fetch values in Model Classes from properties file. It is getting null.
I have set property file location in servlet-context.xml
<context:property-placeholder location="classpath:myproperties.properties" />
Now by using #Value annotation I inject value from properties file.
#Component
class ModelTest {
#Value("${fname}")
private String fname;
// Default Constructor
public ModelTest(){
Sysout(fname); // getting null here
}
#PostConstruct
public void initMembers(){
Sysout(fname) // Prints fname value properly
}
public void setFname(String fname){
this.fname=fname;
}
public String getFname(){
return fname;
}
#Override
public String toString() {
Sysout(fname);
return "ModelTest [variableFirst=" + variableFirst + "]";
}
}
Here is ServiceTest class.
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
public String printTest(){
sysout(fname); // Prints fname value
return new ModelTest().toString() // Prints null
}
}
Here is ControllerHome Class :
#Controller
public class ControllerHome {
#Value("${fname}")
private String fname;
#Autowired
private ServiceTest service;
#RequestMapping("/")
public #ResponseBody String printData(){
sysout(fname); // Prints fname value
return service.printTest(); // Print null
}
}
In model class fname is getting null while In controller and service class value is coming properly.
is anyone face such issue?
When you say model class, do you mean the value passed to a controller method indicated by #ModelAttribute?
If so, that class is created by ordinary constructor invocation through reflection. It is not a spring bean, and thus #Value does nothing.
Addressing your edit, I think there is some fundamental misunderstanding about how Spring works.
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
public String printTest(){
sysout(fname); // Prints fname value
// Calling new here means Spring does nothing
// ModelTest is not a Spring bean
// `#Component`, `#PostConstruct` and `#Value` in ModelTest mean nothing.
return new ModelTest().toString() // Prints null
}
}
Instead, you have to do something like this:
#Service
class ServiceTest(){
#Value("${fname}")
private String fname;
#Autowired
private ModelTest modelTest;
public String printTest(){
sysout(fname); // Prints fname value
// modelTest is now a Spring bean
return modelTest.toString() // Should not print null
}
}
Now, Spring will create ModelTest, and #Component, #PostConstruct and #Value will be honored by Spring.
However, #Component by itself has a default singleton scope. So, you will have the same modelTest always.
So, you have to do something like this:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
class ModelTest {
// ...
}
Now, while the modelTest reference in ServiceTest will remain constant, the use of a proxy will divert the method calls to a new instance of ModelTest, created by Spring, per request.
This is how I do it :
#Component
#PropertySource("classpath:myproperties.properties") // <-Add this.
class ModelTest {
#Autowired
private Environment env;
public void test(){
String name = env.getProperty("name"); //Assuming you have a 'name' key in your myproperties.property
}
}

Categories