This is my first Spring Application so please forgive my ignorance on the matter.
I'm getting a NullPoinerException on an #Autowired dependency.
14:08:48,415 SEVERE [com.vaadin.server.DefaultErrorHandler] (default task-4) : java.lang.NullPointerException
at com.letifer.ui.factory.BudgetTabbedPaneFactory$BudgetTabbedPane.init(BudgetTabbedPaneFactory.java:26)
at com.letifer.ui.factory.BudgetTabbedPaneFactory.createComponent(BudgetTabbedPaneFactory.java:44)
at com.letifer.ui.commons.BudgetMainUI.init(BudgetMainUI.java:44)
BudgetTabbedPaneFactory.java:
package com.letifer.ui.factory;
import org.springframework.beans.factory.annotation.Autowired;
import com.letifer.utils.constants.BudgetStringConstants;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.VerticalLayout;
#SpringComponent
public class BudgetTabbedPaneFactory implements BudgetComponent {
private class BudgetTabbedPane extends VerticalLayout {
private TabSheet tabSheet;
#Autowired
BudgetAccountsFactory accountsFactory;
Component accounts;
public BudgetTabbedPane init() {
tabSheet = new TabSheet();
accounts = accountsFactory.createComponent(); // <-- NullPoinerException
return this;
}
public BudgetTabbedPane layout() {
setSizeFull();
tabSheet.addTab(accounts, BudgetStringConstants.ACCOUNTS_TAB_NAME.getName());
tabSheet.addTab(new Label(BudgetStringConstants.BALANCE_TAB_NAME.getName()), BudgetStringConstants.BALANCE_TAB_NAME.getName());
tabSheet.addTab(new Label(BudgetStringConstants.STATISTICS_TAB_NAME.getName()), BudgetStringConstants.STATISTICS_TAB_NAME.getName());
addComponent(tabSheet);
return this;
}
}
public Component createComponent() {
return new BudgetTabbedPane().init().layout();
}
}
this class has a BudgetAccountsFactory dependency
BudgetAccountsFactory.java:
package com.letifer.ui.factory;
import org.springframework.beans.factory.annotation.Autowired;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
#SpringComponent
public class BudgetAccountsFactory implements BudgetComponent {
#Autowired
private BudgetAccountMenuFactory accountMenuFactory;
#Autowired
private BudgetInfoPaneFactory infoPaneFactory;
private class BudgetAccountsLayout extends HorizontalLayout {
Component menu;
Component infoPane;
public BudgetAccountsLayout init() {
menu = accountMenuFactory.createComponent();
infoPane = infoPaneFactory.createComponent();
return this;
}
public BudgetAccountsLayout layout() {
setMargin(true);
setSizeFull();
addComponent(menu);
setComponentAlignment(menu, Alignment.TOP_LEFT);
setExpandRatio(menu, 1);
addComponent(infoPane);
setComponentAlignment(infoPane, Alignment.TOP_LEFT);
setExpandRatio(infoPane, 2);
return this;
}
}
public Component createComponent() {
return new BudgetAccountsLayout().init().layout();
}
}
this class has another 2 dependecies, BudgetAccountMenuFactory and BudgetInfoPaneFactory
BudgetAccountMenuFactory.java:
package com.letifer.ui.factory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.letifer.utils.constants.BudgetStringConstants;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.ListSelect;
import com.vaadin.ui.VerticalLayout;
#SpringComponent
public class BudgetAccountMenuFactory implements BudgetComponent {
private class BudgetAccountMenuLayout extends VerticalLayout {
private ListSelect<String> options;
public BudgetAccountMenuLayout init() {
options = new ListSelect<String>(BudgetStringConstants.ACCOUNTS_MENU_OPTION_TITLE.getName());
List<String> optionsList = new ArrayList<String>(Arrays.asList(BudgetStringConstants.ACCOUNTS_MENU_OPTION_SHOW_ACCOUNTS.getName(),
BudgetStringConstants.ACCOUNTS_MENU_OPTION_ADD.getName(), BudgetStringConstants.ACCOUNTS_MENU_OPTION_REMOVE.getName()));
Set<String> optionsSet = new HashSet<String>(optionsList);
options.setValue(optionsSet);
return this;
}
public BudgetAccountMenuLayout layout() {
setMargin(true);
setSizeFull();
addComponent(options);
return this;
}
}
public Component createComponent() {
return new BudgetAccountMenuLayout().init().layout();
}
}
BudgetInfoPaneFactory.java:
package com.letifer.ui.factory;
import com.vaadin.spring.annotation.SpringComponent;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
#SpringComponent
public class BudgetInfoPaneFactory implements BudgetComponent {
private class BudgetInfoPaneLayout extends VerticalLayout {
public static final String VIEW_NAME = "info";
private Label label;
public BudgetInfoPaneLayout init() {
label = new Label("INFO HERE");
return this;
}
public BudgetInfoPaneLayout layout() {
setMargin(true);
setSizeFull();
addComponent(label);
return this;
}
}
public Component createComponent() {
return new BudgetInfoPaneLayout().init().layout();
}
}
My ignorance led me to believe that "nested" dependencies(an #Autowired component within an #Autowired component) will work just fine.
But obviously I get a NullPoinerException on the top component.
What am I missing here?
And what is the Intelligent way of "injecting a dependency within a dependency"?
Well, I think we need to clarify what is dependency injection in Spring.
When you mark class with annotation #SpringComponent, #Controller, #Repository etc spring automatically creates instance of this class. But this will be not BudgetTabbedPaneFactory but dynamically generated subclass of BudgetTabbedPaneFactory (so called Proxy).
After instantiating all annotated components are visible for Spring. They are in Spring context and become managed beans.
Then Spring checks all methods and fields marked with #Autowired and tries to initialize them with appropriate instance of automatically created object from previous stage.
But when you create object manually it will exist outside the Spring context and Spring will not care about it and its annotations. It actually don't know about your class at all. Annotations are just a sort of markers they doesn't perform any functionality by themselves.
Read section about Spring IoC container. Maybe it will help to find best solution for your task.
P.S. For your situation your should at least put #SpringComponent annotation over BudgetTabbedPane (not sure if it works for inner classes), because now it is not Spring bean and do not create it manually to let Spring inject dependencies for your.
Related
I am trying to create a java + spring library (a seperate, reusable, application independent jar file) and use it in the application.
I show you a non-working example, that is already suitable to demonstrate my problem.
My problem is, that i have difficulties to autowire a repository by name and not by type.
The jar file logic is implemented here in one file called Library
package com.example;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.stereotype.Service;
public class Library {
#MappedSuperclass
public static class ChildEntity {
public String childAttribute;
}
#MappedSuperclass
public static class RootEntity<T extends ChildEntity> {
public String rootAttribute;
#OneToOne
public T childEntity;
}
#Service
public static class RootEntityService<T extends ChildEntity> {
#Autowired
#Qualifier(Library.REPOSITORY_BEAN_NAME)
private RootRepository<T> repository;
public RootEntity<T> findMyEntity() {
return this.repository.findByChildEntity();
}
}
#NoRepositoryBean
public static interface RootRepository<T extends ChildEntity> extends Repository<T, Long> {
public RootEntity<T> findByChildEntity();
}
public final static String REPOSITORY_BEAN_NAME = "entityRepository";
}
As you can see i have a RootEntity and a ChildEntity with a OneToOne relation.
The RootRepository is defined as #NoRepositoryBean, since Repository cannot have generic parameters.
The RootService is referencing for the RootRepository and i am trying to autowire by name and not type - using the #Qualifier annotation.
Here comes the application itself:
package com.example;
import javax.persistence.Entity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.stereotype.Repository;
import com.example.Library.ChildEntity;
import com.example.Library.RootEntity;
import com.example.Library.RootEntityService;
import com.example.Library.RootRepository;
public class Application implements ApplicationRunner {
#Entity
public static class RealChildEntity extends ChildEntity {
public String realChildAttribute;
}
#Entity
public static class RealRootEntity extends RootEntity<RealChildEntity> {
}
#Repository(Library.REPOSITORY_BEAN_NAME)
public static interface RealRootRepository extends RootRepository<RealChildEntity> {
}
public static void main(final String[] args) throws Exception {
SpringApplication application;
application = new SpringApplication(ApplicationConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
application.run(args);
}
#Autowired
private RootEntityService<RealChildEntity> rootEntityService;
#Override
public void run(final ApplicationArguments args) throws Exception {
this.rootEntityService.findMyEntity();
}
}
The RealChildEntity has an application specific attribute.
I define the RealRootRepository to be a real repository, without generic parameters. I have also defined a bean name for this component to refer to.
I also have an ApplicationConfig class to define the service bean and the repo:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import com.example.Library.ChildEntity;
import com.example.Library.RootEntityService;
#Configuration
#EnableJpaRepositories(basePackageClasses = { Application.class })
public class ApplicationConfig {
#Bean(Library.REPOSITORY_BEAN_NAME)
public <T extends ChildEntity> RootEntityService<T> entityService() {
return new RootEntityService<T>();
}
}
If i execute this application, than spring gives me the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field repository in com.example.Library$RootEntityService required a bean of type 'com.example.Library$RootRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
- #org.springframework.beans.factory.annotation.Qualifier(value="entityRepository")
Spring tells me, that RootEntityService required a bean of type. Why by type?
I want a repository by name
What am i doing wrong?
How can i have a library service without extending it just because of the repository;
Thanks for your help in advance
I am trying to dynamically update the #value annotated fields in my application.
First of all, this application has a custom property source, with source being a Map<Object, String>.
A timer is enabled to update the values after a minute interval.
package com.test.dynamic.config;
import java.util.Date;
import java.util.Map;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.util.StringUtils;
public class CustomPropertySorce extends EnumerablePropertySource<Map<String, Object>> {
public CustomPropertySorce(String name, Map<String, Object> source) {
super(name, source);
new java.util.Timer().schedule(new java.util.TimerTask() {
#Override
public void run() {
source.put("prop1", "yoyo-modified");
source.put("prop2", new Date().getTime());
System.out.println("Updated Source :" + source);
}
}, 60000);
}
#Override
public String[] getPropertyNames() {
// TODO Auto-generated method stub
return StringUtils.toStringArray(this.source.keySet());
}
#Override
public Object getProperty(String name) {
// TODO Auto-generated method stub
return this.source.get(name);
}
}
Initial values of source Map<String, Object> is supplied from the PropertySourceLocator. (This is not the real scenario, but I am trying to recreate the logic used here)
package com.test.dynamic.config;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
public class CustomPropertySourceLocator implements PropertySourceLocator {
#Override
public PropertySource<?> locate(Environment environment) {
Map<String, Object> source=new HashMap<String,Object>(){{put("prop1","yoyo");put("prop2",new Date().getTime());}};
return new CustomPropertySorce("custom_source",source);
}
}
RestController class where I inject these properties using #Value is given below.
environment.getProperty("prop1"); is supplying updated value, but not the #value annotated fields.
I also tried to inject a new property source updatedMap using the addFirst method of environment.propertySources() assuming that it will take precedence over the others. But that effort also went futile. any clue is much appreciated.
package com.test.dynamic.config.controller;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class DataController {
#Value("${prop1}")
private String propertyOne;
#Value("${prop2}")
private Long propertyTwo;
#Autowired
private ConfigurableEnvironment environment;
#GetMapping("/p1")
private String getProp1() {
System.out.println("~~~~>"+environment.getPropertySources());
environment.getPropertySources().forEach(ps -> {
if(ps.containsProperty("prop1") || ps.containsProperty("prop2")) {
System.out.println("*******************************************************");
System.out.println(ps.getName());
System.out.println(ps.getProperty("prop1"));
System.out.println(ps.getProperty("prop2"));
System.out.println("*******************************************************");
}
});
// env.get
return propertyOne;
// return environment.getProperty("prop1");
}
#GetMapping("/p2")
private Long getProp2() {
System.out.println("~~~~>"+environment.getPropertySources());
// env.get
return propertyTwo;
// return environment.getProperty("prop1");
}
#GetMapping("/update")
public String updateProperty() {
Map<String, Object> updatedProperties = new HashMap<>();
updatedProperties.put("prop1", "Property one modified");
MapPropertySource mapPropSource = new MapPropertySource("updatedMap", updatedProperties);
environment.getPropertySources().addFirst(mapPropSource);
return environment.getPropertySources().toString();
}
}
If you think this is not the right way of injecting values to a RestController, please let me know. All possible alternate suggestions/best practices are accepted.
Thank you #flaxel. I used #RefreshScope to resolve this issue.
Posting the solution here if it helps someone with the same query.
In this particular case, I applied #RefreshScope on my Controller to refresh the bean with new values.
You can refer to this link before applying #RefreshScope to your bean.
It is the spring boot actuator that facilitates this refresh mechanism. So in order for this to work, you must have actuator in your classpath.
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: "${springboot_version}"
Then as discussed earlier, add RefreshScope to the bean that needs to be refreshed.
Finally, invoke the actuator/refresh endpoint to trigger the refresh.
If you want to programmatically do it, Autowire an instance of RefreshEndpoint class to your bean and invoke the refresh() method in it.
[Note: You don’t have to strictly follow this approach, but I am giving a clue that it can be Autowired]
#RefreshScope
#RestController
public class DataController {
#Value("${prop1}")
private String prop1;
#Autowired
private RefreshEndpoint refreshEndpoint;
#GetMapping("/p1")
public String getProp1(){
return prop1;
}
#getMappig("/refresh")
public void refresh(){
refreshEndpoint.refresh();
}
}
**************** MORE (if you are developing a library) ********************
What if you are developing a library and you have to get the RefreshEndpoint instance from the current ApplicationContext?
Simply Autowiring RefreshEndpoint may give you a null reference. Instead, you can get hold of the current ApplicationContext by the method given below. And use the ApplicationContext to get the RefreshEndpoint instance to invoke the refresh() method on it.
public class LocalApplicationContextFetcher implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static ApplicationContext ctx;
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void refresh(){
ctx.getBean(RefreshEndpoint.class).refresh();
}
}
Finally, add this class to the spring.factories to get invoked by spring.
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.x.y.z.LocalApplicationContextFetcher
In our service, we are initializing a bean (say "A") and that internally constructing a CacheableService Object by using - new CacheableService(). And as I know spring's #Cacheable annotations won't work on class method if the class is initialized using "new" Keyword.
Then what is an alternative or a way to cache method response?
Scenario :
<bean class="com.package.src.A"/>
public class A {
Map<String, CacheableService> map;
public CacheableService2() {
map = new HashedMap();
map.put("a", new CacheableService());
}
}
import org.springframework.cache.annotation.Cacheable;
public class CacheableService {
#Cacheable(value = "entityCount", key = "#criteria.toString()")
public int someEntityCount(final String criteria) {
System.out.println("Inside function : " + criteria);
return 5;
}
}
Here is a minimum example which demonstrates caching using Spring Boot. The code for the examples below can be found here.
Go to https://start.spring.io/ and create a new Spring Boot project. Make sure to include "Spring cache abstraction" which results in this entry being added to your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
Add the #EnableCaching annotation to your application:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
#EnableCaching
#SpringBootApplication
public class CacheableApplication {
public static void main(String[] args) {
SpringApplication.run(CacheableApplication.class, args);
}
}
Your service:
package com.example;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
#Service
public class CacheableService {
#Cacheable(value = "entityCount")
public int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class A:
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class A {
private CacheableService cacheableService;
public A(#Autowired CacheableService cacheableService) {
this.cacheableService = cacheableService;
}
public int getEntityCount(String criteria) {
return cacheableService.someEntityCount(criteria);
}
}
And then here is a test that demonstrates that the caching is working. As you can see in the test a.getEntityCount("foo") is being called twice, but in standard out we only see "Inside function: foo" being printed once. Therefore we have verified that the second call resulted in the cache being used to produce the result.
package com.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest
class CacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
#Autowired
private A a;
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
a.getEntityCount("foo");
a.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
EDIT:
If you want to move the cache outside of the Spring lifecycle and manually manage it then I would recommend using Caffeine. Here is the same example but now without any Spring involved.
Your service:
package com.example.withoutspring;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineCachingService {
private LoadingCache<String, Integer> entityCountCache = Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.build(key -> someEntityCount(key));
public int cachedEntityCount(final String criteria) {
return entityCountCache.get(criteria);
}
private int someEntityCount(final String criteria) {
System.out.print(String.format("Inside function: %s", criteria));
return 5;
}
}
Class B:
package com.example.withoutspring;
public class B {
private CaffeineCachingService cacheableService;
public B() {
cacheableService = new CaffeineCachingService();
}
public int getEntityCount(String criteria) {
return cacheableService.cachedEntityCount(criteria);
}
}
And the same test but without Spring:
package com.example.withoutspring;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CaffeineCacheableTest {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private B b = new B();
#BeforeEach
public void init() {
System.setOut(new PrintStream(outContent));
}
#Test
public void testCaching() {
b.getEntityCount("foo");
b.getEntityCount("foo");
assertEquals("Inside function: foo", outContent.toString());
}
}
Obviously you need to tune the cache to perform how you want it so probably evicting the cached values after 5 minutes is not what you want but if you visit the Caffeine Github page you will see a lot of detailed examples how to configure the cache to meet your use-case.
Hope this helps!
I have a random class in a random package that is loaded through reflection after the app launches, is there a way for it to be registered as a component under springboot and have annotations such as #Autowired and #Value etc work for that class.
It works when it is in the same package at launch time, but if introduce it thorough another jar at runtime (same package or not) it doesn't work.
Below are samples that don't work even if it is in the same jar. I can't change the app's configuration - it would defeat the "random package/random class" objective.
Code in Spring boot application package
package sample.app
#SpringBootApplication
public class Application {
public static void main(String[] args) {
// Code that starts app
//
//
try {
Thread.sleep(7000);
Class test = Class.forName("test.Test", true, Application.class.getClassLoader());
System.out.println(test.getMethod("getName").invoke(null)); //NPE
System.out.println(test.getMethod("getProfiles").invoke(null)); //NPE
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Test.java
package test;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DependsOn;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
#DependsOn("blaaaaaaaah")
#ComponentScan
public class Test {
#DependsOn("blaaaaaaaah")
public static String getName() {
return SpringGetter.instance.getApplicationName();
}
#DependsOn("blaaaaaaaah")
public static String[] getProfiles() {
String[] profiles = SpringGetter.instance.getEnv().getActiveProfiles();
if (profiles == null || profiles.length == 0) {
profiles = SpringGetter.instance.getEnv().getDefaultProfiles();
}
return profiles;
}
}
SpringGetter.java
package test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
#Component("blaaaaaaaah")
public class SpringGetter implements InitializingBean {
public static SpringGetter instance;
#Value("${spring.application.name}")
private String applicationName;
#Autowired
private Environment env;
public SpringGetter() {
System.out.println("consASFJEFWEFJWDNFWJVNJSBVJWNCJWBVJNVJNVJSNJSNCSDJVNSVJtruct");
}
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public Environment getEnv() {
return env;
}
public void setEnv(Environment env) {
this.env = env;
}
#PostConstruct
public void setInstance() {
instance = this;
}
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
}
EDIT:
I managed to dynamically create the SpringGetter class as part of the same package as the Application class(the one with the #SpringBootApplication). I got Test.java to point to that dynamic class and yet no luck.
To simply inject fields into a POJO as if it were a Spring-managed bean, you can use something like the following:
#Component
public class BeanInitializer implements ApplicationContextAware {
private AutowireCapableBeanFactory beanFactory;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
public void initializeObject(Object pojo) {
beanFactory.autowireBean(pojo);
}
}
Note, however, that this only injects fields marked as #Autowired or #Injected. It does not create proxies that honor method interception strategies based on e.g. #Transactional, #Async, etc.
If you're using Spring 5, have a look at the registerBean() method from GenericApplicationContext. You can find an example here: https://www.baeldung.com/spring-5-functional-beans
The issue in your Test class may also be that you're not loading the Spring Boot context from the main class. You can use the SpringBootTest annotation for this.
I have these classes. when I debug I see that the spring creates the service object in constructor and called the constructors in both classes but when I want to use the fields, they are null. What's the problem?! (type1Processor, type2Processor and type3Processor are null)
import com.vali.ReactiveSocketServer.service.ReceivedDataService;
import org.springframework.stereotype.Component;
public abstract class Processor {
public ReceivedDataService receivedDataService;
public Processor(ReceivedDataService receivedDataService) {
this.receivedDataService = receivedDataService;
}
public abstract void readStream(String stream);
}
and this is its subclass
import com.vali.ReactiveSocketServer.model.ReceivedData;
import com.vali.ReactiveSocketServer.service.ReceivedDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Type1Processor extends Processor {
#Autowired
public Type1Processor(ReceivedDataService receivedDataService) {
super(receivedDataService);
}
#Override
public void readStream(String stream) {
System.out.println("readStream "+ getClass().getSimpleName() + "-" + stream);
receivedDataService.add(new ReceivedData(stream.getBytes()));
}
}
and this is its usage:
import com.vali.ReactiveSocketServer.processor.Processor;
import com.vali.ReactiveSocketServer.processor.Type1Processor;
import com.vali.ReactiveSocketServer.processor.Type2Processor;
import com.vali.ReactiveSocketServer.processor.Type3Processor;
import com.vali.ReactiveSocketServer.receivers.AppServer;
import com.vali.ReactiveSocketServer.socket.ClientHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.HashMap;
import java.util.Map;
#SpringBootApplication
public class ReactiveSocketServerApplication {
private AppServer appServer;
#Autowired
Type1Processor type1Processor;
#Autowired
Type2Processor type2Processor;
#Autowired
Type3Processor type3Processor;
public static void main(String[] args) {
SpringApplication.run(ReactiveSocketServerApplication.class, args);
ReactiveSocketServerApplication reactiveSocketServerApplication = new ReactiveSocketServerApplication();
reactiveSocketServerApplication.Start();
}
public void Start(){
appServer = AppServer.getInstance();
Map<Integer, Processor> processorMap = new HashMap<>();
processorMap.put(7001, type1Processor);
processorMap.put(7002, type2Processor);
processorMap.put(7003, type3Processor);
appServer.initialize(processorMap);
new ClientHandler(7001, 1000);
new ClientHandler(7002, 5000);
}
}
You are instantiating ReactiveSocketServerApplication yourself.
So spring can't inject the #Autowired annotated beans, because the instance was created outside of it's life cycle.
Remove this completly:
ReactiveSocketServerApplication reactiveSocketServerApplication = new ReactiveSocketServerApplication();
reactiveSocketServerApplication.Start();
And annotate your Start() with #PostConstruct:
#PostConstruct
public void Start() { ... }
You are missing #Component in the abstract class ReceivedDataService.
This mean when you create the subclases using :
#Autowired
public Type1Processor(ReceivedDataService receivedDataService) {
super(receivedDataService);
}
Can't inject ReceivedDataService and get the default value(null)