Problema na injeção de dependência Java

Olá. Estou desenvolvendo uma API com Spring em Java.

Nesse momento, estou desenvolvendo mais especificamente um serviço de envio automático de e-mails.

Para isso, criei suas classes de serviço chamadas MockEmailService(apenas para testes) e SmtpEmailService(para produção).

Na classe que implementa o profile de desenvolvimento ( DevConfig ), defini um @Bean para fazer a injeção de dependência da classe SmtpEmailService, na classe de serviço PedidoService .

Porém, está sendo apresentado o seguinte erro:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
[2m2021-09-27 13:36:50.290[0;39m [31mERROR[0;39m [35m5236[0;39m [2m---[0;39m [2m[  restartedMain][0;39m [36mo.s.b.d.LoggingFailureAnalysisReporter  [0;39m [2m:[0;39m 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field emailService in com.mateuussilvapb.cursomc.services.PedidoService required a bean of type 'com.mateuussilvapb.cursomc.services.EmailService' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.mateuussilvapb.cursomc.services.EmailService' in your configuration.

Classe SmtpEmailService:

package com.mateuussilvapb.cursomc.services;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class SmtpEmailService extends AbstractEmailService {

    @Autowired
    private MailSender mailSender;
    
    // Objeto que será usado para mostrar o e-mail no LOG do servidor
    private static final Logger LOG = LoggerFactory.getLogger(SmtpEmailService.class);

    @Override
    public void sendEmail(SimpleMailMessage msg) {
        LOG.info("Simulando envio de e-mail...");
        mailSender.send(msg);
        LOG.info("E-mail enviado!");
    }
}

Classe PedidoService

package com.mateuussilvapb.cursomc.services;

import java.util.Date;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mateuussilvapb.cursomc.domain.ItemPedido;
import com.mateuussilvapb.cursomc.domain.PagamentoComBoleto;
import com.mateuussilvapb.cursomc.domain.Pedido;
import com.mateuussilvapb.cursomc.domain.enums.EstadoPagamento;
import com.mateuussilvapb.cursomc.repositories.ItemPedidoRepository;
import com.mateuussilvapb.cursomc.repositories.PagamentoRepository;
import com.mateuussilvapb.cursomc.repositories.PedidoRepository;
import com.mateuussilvapb.cursomc.services.exceptions.ObjectNotFoundException;

@Service
public class PedidoService {

    /*
     * Quando se declara uma dependência e é acrescentado a anotação 'Autowired', a
     * dependência é instanciada automaticamente pelo Spring.
     */
    @Autowired
    private PedidoRepository repo;
    @Autowired
    private BoletoService boletoService;
    @Autowired
    private PagamentoRepository pagamentoRepository;
    @Autowired
    private ProdutoService produtoService;
    @Autowired
    private ItemPedidoRepository itemPedidoRepository;
    @Autowired
    private ClienteService clienteService;
    @Autowired
    private EmailService emailService;
    
    public Pedido find(Integer id) {
        Optional<Pedido> obj = repo.findById(id);
        return obj.orElseThrow(() -> new ObjectNotFoundException(
                "Objeto não encontrado! ID: " + id + ". " + "Tipo: " + Pedido.class.getName()));
    }

    @Transactional
    public Pedido insert(Pedido obj) {
        obj.setId(null);
        obj.setInstante(new Date());
        obj.setCliente(clienteService.find(obj.getCliente().getId()));
        obj.getPagamento().setEstado(EstadoPagamento.PENDENTE);
        obj.getPagamento().setPedido(obj);
        if (obj.getPagamento() instanceof PagamentoComBoleto) {
            PagamentoComBoleto pagto = (PagamentoComBoleto) obj.getPagamento();
            boletoService.preencherPagamentoComBoleto(pagto, obj.getInstante());
        }
        obj = repo.save(obj);
        pagamentoRepository.save(obj.getPagamento());
        for (ItemPedido ip : obj.getItens()) {
            ip.setDesconto(0.0);
            ip.setProduto(produtoService.find(ip.getProduto().getId()));
            ip.setPreco(ip.getProduto().getPreco());
            ip.setPedido(obj);
        }
        itemPedidoRepository.saveAll(obj.getItens());
        emailService.sendOrderConfirmationEmail(obj);
        return obj;
    }
}

Classe de implementação do profile de configuração dev ( DevConfig )

package com.mateuussilvapb.cursomc.config;

import java.text.ParseException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import com.mateuussilvapb.cursomc.services.DBService;
import com.mateuussilvapb.cursomc.services.EmailService;
import com.mateuussilvapb.cursomc.services.SmtpEmailService;

@Configuration
@Profile("dev")
public class DevConfig {

    @Autowired
    private DBService dbService;

    @Value("${spring.jpa.hibernate.ddl-auto}")
    private String strategy;

    @Bean
    public boolean instantiateDatabase() throws ParseException {
        if (!"create".equals(strategy)) {
            return false;
        }
        dbService.instantiateTestDatabase();
        return true;
    }

    @Bean
    public EmailService emailService() {
        return new SmtpEmailService();
    }
}

Classe EmailService

package com.mateuussilvapb.cursomc.services;

import org.springframework.mail.SimpleMailMessage;

import com.mateuussilvapb.cursomc.domain.Pedido;

public interface EmailService {

    void sendOrderConfirmationEmail(Pedido obj);
    
    void sendEmail(SimpleMailMessage msg);
}

A classe anotada com @SpringBootApplication está no pacote raiz: com.mateuussilvapb.cursomc :

package com.mateuussilvapb.cursomc;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CursomcApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(CursomcApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
    }
}

O profile de dev está ativo:

spring.profiles.active=dev

default-sender=mateuussilvapb@gmail.com
default-recipient=mateuussilvapb@gmail.com

# No JDBC URL: jdbc:h2:file:~/test

Só para deixar mais claro: A aplicação está informando que não está localizando um @Bean que implemente o serviço EmailService , porém o @Bean está definido na classe de configuração DevConfig

Tentei, apesar do meu problema não estar no pacote de repositórios, utilizar o @EnableJpaRepositories , mas não funcionou. Andei pesquisando a respeito, mas os problemas envolvem principalmente o pacote de repositórios.

Quem puder ajudar, fico grato.

Bom dia !

A classe abstrata AbstractEmailService está implementando a interface EmailService?

Está!

package com.mateuussilvapb.cursomc.services;

import java.util.Date;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;

import com.mateuussilvapb.cursomc.domain.Pedido;

public abstract class AbstractEmailService implements EmailService{

	@Value("${default-sender}")
	private String sender;
	
	@Override
	public void sendOrderConfirmationEmail(Pedido obj) {
		SimpleMailMessage sm = prepareSimpleMailMessageFromPedido(obj);
		sendEmail(sm);
	}

	protected SimpleMailMessage prepareSimpleMailMessageFromPedido(Pedido obj) {
		SimpleMailMessage sm = new SimpleMailMessage();
		//Destinatário do e-mail
		sm.setTo(obj.getCliente().getEmail());
		//Remetente do e-mail
		sm.setFrom(sender);
		//Assunto do e-mail
		sm.setSubject("Pedido confirmado! Código: " + obj.getId());
		//Data do e-mail
		sm.setSentDate(new Date(System.currentTimeMillis()));
		//Corpo do e-mail
		sm.setText(obj.toString());
		return sm;
	}
}

Interessante é que quando mudo o encode dos properties, os erros mudam.

Voltei um commit, refiz todo o processo e o projeto simplesmente funcionou.
Se eu disser que aumentei ou diminui até mesmo uma linha em branco, estarei mentindo.
Vai entender ¯_(ツ)_/¯.
De qualquer forma, obrigado!

@DiasMateus Provavelmente o erro era pq a classe AbstractEmailService estava como abstract. Assim o container do spring não iria conseguir criar a instancia.

1 curtida

@Lucas_Camara, o estranho é que AbstractEmailService continua marcada como uma classe abstract, mas dessa vez funcionou normalmente.

Curioso. Tem alguma classe extendendo ela?

Não. Em se tratando de Email, as classes que herdam algo de outra são: MockEmailService e SmtpEmailService. Elas extendem a classe EmailService.

@Lucas_Camara, o problema voltou quando implementei configurações de segurança JWT no projeto e define o profile de teste como sendo o que deve ser usado na hora da execução.
Porém, consegui resolver com @Autowaired(required = false) na dependência que estava dando problema.
Obviamente, já estava anotando a dependência com @Autowired. o que acrescentei foi o (required = false).

Você saberia me informar se isso é considerado uma boa prática? O que acontece quando coloco required = false no @Autowired?

@Autowaired(required = false) diz ao spring que não é obrigatória a injeção desta dependência para a construção do bean em questão.

1 curtida

@DiasMateus Mas esse é um bean usado somente nos testes?

@Lucas_Camara Não. É utilizado no profile de teste, dev, prod…

@Jonathan_Medeiros Entendi, obrigado!