In my SpringBoot app, I have Autowired an configObject in the class that implements EnvironmentPostProcessor.
The injected class reads data from a different source on startup as this is required for the app to work.
But upon starting the application, the configObject is coming off as Null.
#SpringBootApplication
#EnableEncryptableProperties
#EnableConfigurationProperties
#EnableCaching
#Slf4j
public class SBApplication {
public static void main(String[] args) {
SpringApplication.run(SBApplication.class, args);
}
}
And the AppEnvironmentPostProcessor class where the Autowired object is called. This class is configured as org.springframework.boot.env.EnvironmentPostProcessor in spring.factories. The class gets called on start up.
#Slf4j
public class AppEnvironmentPostProcessor implements
EnvironmentPostProcessor, Ordered {
#Autowired
KeysConfig keysConfig;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// keysConfig is null
String key = keysConfig.getSecretKeyMap().get("key12");
}
}
And in the KeysConfig class
#Component
public final class KeysConfig {
public Map getSecretKeyMap() {
//Returns key map
}
}
I am using Intellij Ultimate. How can I debug and resolve this?
EnvironmentPostProcessors are created before the application context has been created and, therefore, before dependency injection is possible. This means that #Autowired won’t work.
You’ll have to update your implementation to create an instance of KeysConfig itself, or to use a different approach that mimics whatever KeysConfig currently does.
Related
I am creating a REST service in Spring boot. I am creating a bean in config class and trying to use in service class by auto wiring, but I am always getting null, I have tried in constructor injection as well but not working. Below is the code,
Main app
#SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
REST controller
#RestController
#RequestMapping("/v1")
public class RestController {
#Autowired
private Service service;
Service
#Service
public class ServiceImpl implements Service {
//This is the bean
#Autowired
private Record record;
public ServiceImpl() { //-----------------> tried injecting in constructor as well
System.out.println(record); //-------------------> null
}
Config class
#Configuration
public class AppConfig {
#Bean
public Record record() {
return new Record("test");
}
}
I noted whenever I remove the record() from config class I get below error
required a bean of type 'com.ns.service.Record' that could not be found
And after adding the method the error is not reported but null is returned, which indirectly means record() is considered as returning the required bean.
I can't find what I am doing wrong please advise.
Project folder structure
I think you're doing everything right conceptually
Spring creates an object first and only after that injects the values (technically done in bean post processors):
So try this:
#Service
public class ServiceImpl implements Service {
//This is the bean
#Autowired
private Record record;
public ServiceImpl() {
// here the record is null - not injected yet
System.out.println(record);
}
#PostConstruct
public void checkThisOut() {
// here print the record
}
You say you've tried constructor injection as well - it should work because spring has to inject something into the constructor of a bean (ServiceImpl) or fail. Please show the code snippet
One this that might be wrong in some level (although it doesn't sound like this from your description) is that you have to put all the #Configuration/#Service annotated classes in the package that is the same or underneath the package where you've created the main class annotated with #SpringBootApplication annotation. It instructs spring boot where to look for the beans.
So make sure your classes obey this rule...
I'm learning SpringBoot and I built a simple "sample" app but I'm having trouble injecting the different components together.
I get Field b in ca.company.hello.A required a bean of type 'ca.company.hello.B' that could not be found. The injection point has the following annotations:
#org.springframework.beans.factory.annotation.Autowired(required=true).
Here is my configuration:
#Configuration
public class Config {
#Bean
public B b() {
return new B();
}
}
Here is how I use the class B:
#Component
public class A {
#Autowired
private B b;
#Value("Covid 19")
private String calamity;
public void speak() {
b.writeToScreen(this.calamity);
}
}
Can someone tell me please, how do I inject class B correctly into the field b in class A above?
P.S.
Here is the package structure of my project:
And here is my main:
#SpringBootApplication
public class Helloer {
public static void main(String[] args) {
// EDIT: I'm now using ctx to create the A object as per suggestions below. Same error though
ApplicationContext ctx = SpringApplication.run(A.class, args);
A a = ctx.getBean(A.class);
a.speak();
}
}
Thanks!
Spring Boot Must find the Beans (A and B) during the scanning. It has some component scanning rules that should be obeyed (see the explanation in "item 2" below) + the class marked with SpringBootApplication is wrong, these two issues cause the problem:
Solution:
Fix the Helloer class:
Note, that Helloer is passed to SpringApplication.run.
Another issue is that A should be managed By Spring itself, otherwise, if you do a new A() - the autowiring magic won't happen. In general, everything that has autowiring in one way or another must be managed by spring dependency injection container (read, under the responsibility of spring boot)
#SpringBootApplication
public class Helloer {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Helloer.class, args);
A a = ctx.getBean(A.class);
a.speak();
}
}
Move the config class so that its package will be the same or "inner" package related to the "Helloer". Spring boot scans everything in the package of SpringBootApplication and underneath by default during its startup. Only the classes under these packages can be processed by spring:
package ca.company.hello; // note the package - it can also be something "inside" this package, like ca.company.hello.config; otherwise spring won't find it and class B won't be "visible" for spring.
#Configuration
public class Config {
#Bean
public B b() {
return new B();
}
}
If you already have config for class B, why not also manage class A in the #Configuration (well, this one is optional, it will work even without this change):
package ca.company.hello; // note the package - it can also be something "inside" this package, like ca.company.hello.config;
#Configuration
public class Config {
#Bean
public A a() {
return new A();
}
#Bean
public B b() {
return new B();
}
}
With this change, you don't have to put #Component on A - these are kind of two styles of declaring beans, spring boot will work with both. I haven't seen the definition of the class B in the question, but if you manage it through the #Configuration then you shouldn't put a #Component on the class B itself
You defined A to be a component. So instance of A will be created by Spring. then you say new A().
This creates a new instance of A for which it looks for B and doesn't find.
Anywhere in your code when you use "new" keyword, it is just another instance you are creating which Spring is not aware of, hence your error.
So you need to autowire A into the Main class and call the method a.speak.
Plus, your bootstrap class is wrong, you are bootstraping class A, but the run method expects a class which is annotated with SpringBootConfiguration. That is how SpringBoot starts the application.
So your code should be :
#SpringBootApplication
public class Helloer {
#Autowired
private A a;
public static void main(String[] args) {
SpringApplication.run(Hellor.class, args);
a.speak();
}
}
If you do not want to autowire A, second thing you can do is, SpringApplication.run() method returns ApplicationContext. Hold it in a variable and then say context.getBean("a", A.class) -> this returns the instance of A from the Spring Context. Then you can call the a.speak()
#SpringBootApplication
public class Helloer {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Hellor.class, args);
A a = ctx.getBean("a", A.class);
a.speak();
}
}
In my Spring Boot application, i have a configuration, which reads entries from a Mongo database.
After this is done, my subclass of AbstractMongoEventListener is created, even though it operates on a different table and different scope (my own custom #CustomerScope).
Here is the listener:
#CustomerScoped
#Component
public class ProjectsRepositoryListener extends AbstractMongoEventListener<Project> {
#Override
public void onAfterSave(Project source, DBObject dbo) {
System.out.println("saved");
}
}
And here the configuration:
#Configuration
public class MyConfig {
#Autowired
private CustomersRepository customers;
#PostConstruct
public void initializeCustomers() {
for (Customer customer : customers.findAll()) {
System.out.println(customer.getName());
}
}
}
I find it surprising that the listener is instantiated at all. Especially since it is instantiated well after the call to the customers repository has finished.
Is there a way to prevent this? I was thinking of programmatically registering it per table/scope, without annotation magic.
To prevent auto-instantiation, the listener must not be annotated as #Component. The configuration needs to get ahold of the ApplicationContext, which can be autowired.
Thus, my configuration class looks like this:
#Autowired
private AbstractApplicationContext context;
private void registerListeners() {
ProjectsRepositoryListener firstListener = beanFactory.createBean(ProjectsRepositoryListener.class);
context.addApplicationListener(firstListener);
MySecondListener secondListener = beanFactory.createBean(MySecondListener.class);
context.addApplicationListener(secondListener);
}
Note that this works for any ApplicationListener, not just AbstractMongoEventListener.
I have 2 applications, one using Spring inside a web-app, and another local application using Spring Boot. These 2 share a configuration class between them.
I cannot figure out how to configure the local application correctly.
Here is the basic layout of classes I am using:
The main class
#EnableAutoConfiguration
class MainClass{
#Autowired
private static MyComponent component;
public static void main(String args[]){
// code
SpringApplication.run(MyConfiguration.class, args);
component.start();
}
}
The configuration
#Configuration
#EnableConfigurationProperties
#PropertySource("classpath:/path/to/queue.properties")
public class MyConfiguration {
#Autowired
public static Environment env;
// more beans
#Bean
#Qualifier("qualifier1")
public static String getName(){ //made String and simple to match the Component's Autowired
return env.getProperty("property.name");
}
}
The component
#Component
#EnableAutoConfiguration
public class MyComponent extends Thread {
#Autowired
#Qualifier("qualifier1")
private String template; // this isn't actually String, but a springAMQP class. Should have the same effect though.
#Override
public void run(){
//code
template.charAt(0); // just something that fails if it was not autowired
//code
}
}
If .run is given MyConfiguration.class i get a null pointer within the autowired Environment in MyConfiguration. If it is given MainClass.class the autowired MyComponent is still null.
As for some layout restrictions,
the Main Class and MyComponent only exist in the local application. The Configuration is in a shared package between the local application and the web application. This prevents me from simply creating the Bean with MyComponent in the Configuration due to dependencies.
If I remove the MyComponent from the MainClass and add the following configuration within the Local application:
#Configuration
public class MyLocalConfiguration extends MyConfiguration {
private MyComponent listener;
#Bean
public MyComponent getListener(){
if(listener == null){
listener = new MyComponent();
listener.start();
}
return listener;
}
}
I still have the issue of the Environment being null in MyConfiguration, preventing the other beans from being set up.
There are 2 problems with your configuration
#Autowired will only work for Spring Managed beans, your MainClass isn't spring managed so no autowiring is going to happen
#Autowired will not work on static fields
I've got two beans. Both implement the mailing function. One is only working when it is deployed to an application server. The other one is used for testing.
We have profile for each developer and environment. I want to wire the testing bean only when actually testing. The other bean should be used when not testing. How can I archive this?
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Solution approaches:
Using "default" I read this somewhere, but there seems to be no fall-back to "default" for a profile like "dev":
#Component
#Profile("default")
public class OnlineMail implements Mailing {}
-> Exception for no bean for wiring found.
Leaving the profile out:
#Component
public class OnlineMail implements Mailing {}
-> Throws a unique exception when running the "localtest" profile.
Adding all profiles:
#Component
#Profile("prod")
#Profile("integration")
#Profile("test")
#Profile("dev1")
#Profile("dev2")
#Profile("dev3")
...
public class OnlineMail implements Mailing {}
This is actually working, however our devs aren't numbered they use "dev<WindowsLogin>" and adding the profiles, may work for one bean, but one will get into trouble when using it for several beans as this definitely gets ugly.
Using something like #Profile("!localtest") doesn't seem to work as well.
Does anyone know a nicer way to get a "wire by default if no specific bean is found"?
I finally found an easy solution.
The online mail is just wired by default.
#Component
public class OnlineMail implements Mailing {}
Using the #Primary annotation the offline mail takes precedence over the OnlineMail and avoids the Unique exception.
#Component
#Profile("localtest")
#Primary
public class OfflineMail implements Mailing {}
Try this:
#Component
#Profile("production")
public class OnlineMail implements Mailing {}
#Component
#Profile("localtest")
public class OfflineMail implements Mailing {}
Then run tests using #ActiveProfiles("localtest") and run production enviroment using "production" as DEFAULT profile.
Also I hope in next version of Spring ActiveProfilesResolver will be introduced SPR-10338 - it may be helpfull for you (to avoid "dev1", "dev2" and so on).
Spring supports inject the Bean by #Profile very well:
interface Talkative {
String talk();
}
#Component
#Profile("dev")
class Cat implements Talkative {
public String talk() {
return "Meow.";
}
}
#Component
#Profile("prod")
class Dog implements Talkative {
public String talk() {
return "Woof!";
}
}
Works in unit test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:applicationContex-test.xml"})
#ActiveProfiles(value = "dev")
public class InjectByDevProfileTest
{
#Autowired
Talkative talkative;
#Test
public void TestTalkative() {
String result = talkative.talk();
Assert.assertEquals("Meow.", result);
}
}
Works in Main():
#Component
public class Main {
public static void main(String[] args) {
// Enable a "dev" profile
System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "dev");
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Main p = context.getBean(Main.class);
p.start(args);
}
#Autowired
private Talkative talkative;
private void start(String[] args) {
System.out.println(talkative.talk());
}
}
Check this for the Demo code: https://github.com/m2land/InjectByProfile