Failed to Autowire beans in a Java thread in Spring Boot - java

I successfully autowired beans and sent emails in my previous code. The code is as follow:
public class HomeController{
#Autowired
private MailConstructor mailConstructor;
#Autowired
private JavaMailSender mailSender;
...
String token = UUID.randomUUID().toString();
String appUrl = "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
SimpleMailMessage newEmail = mailConstructor.constructResetTokenEmail(appUrl, request.getLocale(), token, user);
mailSender.send(newEmail);
...
}
Now I want to send the email in a new thread and I wrote my code referring to this link:Why does Autowiring not function in a thread?
My new code is as follow:
public class EmailUtility {
#Autowired
private JavaMailSender mailSender;
public static SimpleMailMessage constructNormalEmail(String text, String subject, String toAddress, String fromAddr) {
SimpleMailMessage email = new SimpleMailMessage();
email.setTo(toAddress);
email.setSubject(subject);
email.setText(text);
email.setFrom(fromAddr);
return email;
}
public class SendEmail implements Runnable{
private String subject;
private String text;
private String toAddr;
private String fromAddr;
public SendEmail(String subject, String text, String toAddr, String fromAddr){
this.subject = subject;
this.text = text;
this.toAddr = toAddr;
this.fromAddr = fromAddr;
}
#Override
public void run() {
SimpleMailMessage email = constructNormalEmail(text, subject, toAddr, fromAddr);
mailSender.send(email);
}
}
}
But I got the following error:
exception in thread "Thread-5" java.lang.NullPointerException
Both this error and the message presented by IntelliJ Could not autowire. No beans of 'JavaMailSender' type found indicates that the autowire fails.
What may be the cause of this error and how can I autowire a bean in a thread? Any suggestion will be appreciated.
=============================UPDATE=====================================
Solved this problem with the help of these guys. The problem is that I create the EmailUtility myself.
MY previous code is as follow:
//Wrong Version
Thread sendEmailThread = new Thread(new EmailUtility().new SendEmail(subject, text, toAddress, fromAddress));
If I let Spring autowires the EmailUtility, then the code works:
//Correct Version
#Component
public class EmailUtility {
......
}
public class HomeController{
#Autowired
private EmailUtility emailUtility;
...
Thread sendEmailThread = new Thread(emailUtility.new SendEmail(subject, text, toAddress));
...
}

EmailUtility most probably is not created by Spring, since it is not marked as #Service, #Component, #Controller, #Inject, etc. thats why Spring framework has no chance to inject (autowire) your implementation of JavaMailSender into EmailUtility instance.
Spring does some magic with classes named by a certain pattern, therefore your *Controller classes might be recognized by Spring as beans, but not *Utility class.

Most likely your EmailUtility is not a spring managed bean. Consider adding annotation #Component to EmailUtility or declare a corresponding bean in your configuration class.

Related

How can I get my Spring Boot application to recognize this service?

I'm working on a Spring Boot 2.3 based application. I have a controller that currently has one autowired dependency:
public class ValidationController {
private ValidationService validationService;
#Autowired
public ValidationController(ValidationService validationService) {
this.validationService = validationService;
}
}
I have tests that run on this service and pass. I am attempting to add a second service to this controller:
public class ValidationController {
private ValidationService validationService;
private TokenService tokenService;
#Autowired
public ValidationController(ValidationService validationService, TokenService tokenService) {
this.validationService = validationService;
this.tokenService = tokenService;
}
}
When I do this, all of my ValidationControllerTest cases start failing. I found this message in the stack trace:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.auth.service.service.TokenService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
My TokenService interface is as follows:
public interface TokenService {
JsonWebToken getToken(User user, Account account);
}
The implementation is annotated with the #Service annotation:
#Service
public class TokenServiceImpl implements TokenService {
private final String url;
private final String username;
private final String password;
public TokenServiceImpl(
#Value("${service.url}") String url,
#Value("${service.username}") String username,
#Value("${service.password}") String password
) {
this.url = url;
this.username = username;
this.password = password;
}
#Override
public JsonWebToken getToken(User user, Account account) {
...
return jsonWebToken;
}
}
My ValidationService looks very similar to my TokenService as far as annotations, but for whatever reason, Spring Boot can find my ValidationService just fine but my TokenService, which lives in the same place, is nowhere to be found.
How do I make sure that Spring Boot can find my TokenService?
Thanks very much for the comments!
Can you double check that you bring up a spring application context in your tests?
It is my understanding that the #WebMvcTest annotation will load my application context, is that accurate? I have the following annotations on my ValidationControllerTest class:
#ExtendWith(SpringExtension.class)
#WebMvcTest(controllers = ValidationController.class)
Is this sufficient to load the application context in my tests?
Can you double check that this context does a component scan to the package where the TokenServiceImpl resides?
I am new to Spring Boot and still trying to grok component scanning. I have a #SpringBootApplication annotation on my main AuthServiceApplication class, which is in the com.example.auth.service package. I read about component scanning here:
If your other packages hierarchies are below your main app with the #SpringBootApplication annotation, you’re covered by implicit components scan.
My TokenServiceImpl class resides at com.example.auth.service.service.impl, which I think means that it is within the hierarchy of the main app, so it should be covered by the implicit component scan. Is that accurate?
Can you double check that ValidationService is not mocked (via #Mock annotation or something) somewhere in your test suite?
I do have the following in my ValidationServiceImplTest:
#MockBean
private TokenService tokenService;
It looks like I am not using it at the moment. I will remove this #MockBean and see if that does the trick.

How can I use #RabbitListener in multiple classes?

I have a Spring application using RabbitMQ (spring-boot-starter-amqp).
I wanted to know if it's possible to use the #RabbitListener annotation across different classes.
I currently have two classes: Receiver and DeadLetterQueue
Receiver.java:
#Component
#Slf4j
public class Receiver {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#RabbitListener(queues = "queueName")
public void receiveMessage(String message) throws Exception {
logger.info("Received <{}>" + message.toString());
throw new Exception("Error with the message");
}
DeadLetterQueue.java:
#Component
#Slf4j
public class DeadLetterQueue {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
#RabbitListener(queues = "otherQueueName")
public void processFailedMessages(String message) {
logger.info("Received failed message<{}>:" + message.toString());
}
}
RabbitMqConfig.java:
#Data
#Configuration
#ConfigurationProperties(prefix = "rabbitmq")
public class RabbitMqConfig {
private String host;
private int port;
private String username;
private String password;
private String queue;
private String exchange;
private String dlq;
private String dlx;
private String routingKey;
#Bean
Queue incomingQueue() {
return QueueBuilder.durable(queue)
.withArgument("x-dead-letter-exchange", dlx)
.build();
}
#Bean
FanoutExchange deadLetterExchange() {
return new FanoutExchange(dlx);
}
#Bean
Queue deadLetterQueue() {
return QueueBuilder.durable(dlq).build();
}
#Bean
Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange());
}
#Bean
DirectExchange incomingQueueExchange() {
return new DirectExchange(exchange);
}
#Bean
Binding incomingQueueBinding() {
return BindingBuilder.bind(incomingQueue()).to(incomingQueueExchange()).with(queue);
}
When I use the RabbitMQ management tool to post a message to the FanoutExchange, the processFailedMessages inside the DeadLetterQueue class doesn't log anything to the console. However if this method is inside of the Receiver class, everything works fine. Which led me to the assumption that #RabbitListener couldn't work across multiple classes unless there is some configuration which I am missing.
Other information:
I'm using Docker to run the RabbitMQ Server
Strangely, if I put the receiveMessage() method inside the DeadLetterQueue class, the expectations fail.
So: Is it possible to define queues in multiple classes with #RabbitListener?
Yes, you can have as many #RabbitListeners as you want, either in the same class or in multiple classes, as long as those classes are used in Spring beans.
Since you have #Component on both, it should work just fine, unless the DeadLetterQueue is in a package that is not scanned by Spring Boot.
Boot only looks at the packages and subpackages where the main #SpringBootApplication is located.
You can enable DEBUG logging for org.springframework to log all the bean creation during application initialization.
I'm using Docker to run the RabbitMQ Server
The location of the broker is irrelevant.

Benefit of dependency injection for dynamically created objects

In the context of an IoC container such as Spring, I am looking for a way to inject some dependencies/properties into a class' instantiation. Not all properties of the object can be set using dependency injection, and the object is created dynamically in response to an application event. If all dependencies can be injected via the container, then a Spring managed bean would be ideal.
For instance, the class defined below must be annotated as a #Component (or more specialized annotation) for component scanning and dependency injection to work. But it has a couple of properties (name and attempts) that can only set be dynamically, by the application code and not the container. But if I have to use an endpoint and a restTemplate (which are already managed by the IoC container), providing them to this object via constructor or setter methods is not convenient.
public class SomeClass {
#Autowired
private RestTemplate restTemplate;
#Autowired
private String endpoint;
private String name;
private int attempts;
public SomeClass(String name, int attempts) {
this.name = name;
this.attempts = attempts;
}
// public getter and setter methods
}
Since there are some dynamically set properties, I cannot use the new keyword to instantiate the class and still reap the benefits of DI and IoC. Or can I?
You could use a factory. Something like the following:
public class SomeClass {
private RestTemplate restTemplate;
private String endpoint;
private String name;
private int attempts;
public SomeClass(String name, int attempts, RestTemplate restTemplate,
String endpoint) {
this.name = name;
this.attempts = attempts;
this.restTemplate = restTemplate;
this.endpoint = endpoint;
}
}
#Component
public class SomeClassFactory {
#Autowired
private RestTemplate restTemplate;
#Autowired
private String endpoint;
public SomeClass create(String name, int attempts) {
return new SomeClass(name, attempts, restTemplate, endpoint);
}
}
SomeClass instance = someClassFactory.create("beep", 0);
If I don't misunderstood you, you need to set the values in the constructor.
You can do it creating the bean from the context and setting the values:
context.getBean("beanname", arg1, arg2....);

Session scoped bean in Aspect

I have problem autowiring session scoped bean into an Aspect.
My aspect looks like this:
#Aspect
public class AuditAspect {
Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private AuditService auditService;
#Autowired
private SessionData sessionData;
#AfterReturning(value = "#annotation(fasthiAudit) && execution(* *(..))")
public void audit(JoinPoint joinPoint, FasthiAudit fasthiAudit) {
final String className = joinPoint.getTarget().getClass().getName();
final String methodName = joinPoint.getSignature().getName();
try {
UserId userId = sessionData.getUserId();
TenantId tenantId = sessionData.getTenantId();
} catch (Exception e) {
logger.error("Could not log audit entry for method name: " + methodName + " in class " + className, e);
}
}
}
My SessionData bean is session scoped and looks like this:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionData {
private UserId userId;
private TenantId tenantId;
public UserId getUserId() {
return userId;
}
public void setUserId(UserId userId) {
this.userId = userId;
}
public TenantId getTenantId() {
return tenantId;
}
public void setTenantId(TenantId tenantId) {
this.tenantId = tenantId;
}
}
In the aspect, the AuditService is autowired in okay and the SessionData is not null but it throws an Exception like
Method threw 'org.springframework.beans.factory.BeanCreationException' exception. Cannot evaluate se.reforce.fasthi.core.infrastructure.SessionData$$EnhancerBySpringCGLIB$$26c0d5bb.toString()
I have added a ContextLoaderListener to expose the request like this:
event.getServletContext().addListener(new RequestContextListener());
It works fine to autowire in the SessionData bean as a proxy in other singelton beans but the problem occurs in the aspect
What am I missing?
Thank you
/Johan
I found the problem after a few days of headache. The problem was my Vaadin integration (that I forgot to mention in my question). The #Push annotation on my vaadin UI did something confusing with the servlet so that spring didn't recognize the session scoped beans. I solved this my changing the annotation to:
#Push(transport= Transport.WEBSOCKET_XHR)
That was it, now the session scoped beans work perfectly together with the singelton beans

autowired component is null

I'm developing a web application with spring. I've had no problem autowiring and using database #Service classes. Now I'm trying to read a global property file and provide the values to all classes that need them. The solution I've come up with so far seem to be overly complicated (too many classes - AppConfig, ServerConfig iface, ElasticServerConfig) for such a trivial task but I could live with it if it worked.
my applicationContext.xml contains
<context:component-scan base-package="my.package" />
AppConfig.java:
package my.package.configuration;
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
}
ServerConfig.java:
public interface ServerConfig {
String getUrl();
String getUser();
String getPassword();
}
ElasticSearchConfig.java:
package my.package.configuration;
#Component(value = "elasticServerConfig")
public class ElasticServerConfig implements ServerConfig {
private static final Logger LOGGER = LogManager.getLogger(ElasticServerConfig.class);
private String url;
private String user;
private String password;
#Autowired
public ElasticServerConfig(final Environment env) {
this.url = env.getProperty("elastic_server.url");
this.user = env.getProperty("elastic_server.user");
this.password = env.getProperty("elastic_server.password");
LOGGER.debug("url=" + url + "; user=" + user + "; password=" + password); // this works!
}
#Override
public final String getUrl() {
return url;
}
#Override
public final String getUser() {
return user;
}
#Override
public final String getPassword() {
return password;
}
}
When the web application boots, the ElasticServerConfig constructor prints out the correct url/user/pwd as read from application.properties. However an instance of ElasticServerConfig is not injected into a Search object:
package my.package.util;
public class Search {
#Autowired
#Qualifier("elasticServerConfig")
private ServerConfig elasticServerConfig;
public final List<Foobar> findByPatternAndLocation() {
if (elasticServerConfig == null) {
LOGGER.error("elasticServerConfig is null!");
}
// and i get a NullPointerException further on
// snip
}
}
You have to register the Search class as a Spring Bean and take it from the Spring context when you want to use it. It's important to get the bean from the spring context. If you create an object of that class with new, Spring has no way to know about that class and mange it's dependencies.
You can get get a bean from the Spring context by #Autowire it somewhere or by accessing an instance of the context and use the getBean method:
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(AppConfig.class, args);
ctx.getBean...
}
}
Either use #Component annotation on the class and make sure that the class is in package thats under my.package
or register it in the configuration class
#Configuration
#PropertySource("classpath:application.properties")
public class AppConfig {
#Bean
public Search search(){
return new Search();
}
}

Categories

Resources