Communication between microservices using Feign throws bean could not be found - java

I'm trying to get my microservice java spring boot to communicate with another microservice using Feign but I'm getting this message when starting the application:
APPLICATION FAILED TO START
***************************
Description: Parameter 0 of constructor in ProductService required a bean of type ProductClientRepository' that could not be found.
Action: Consider defining a bean of type 'ProductClientRepository' in your configuration.
I don't know what could be wrong, I already checked if all the declared variables are in the project's properties and I already checked the imports, I don't know why it is saying that something is missing in the bean.
pom.xml:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
SaleService:
#Service
#RequiredArgsConstructor
public class SaleService {
private final ProductService productService;
#Transactional
public Sale createSale(Sale sale) {
Set<Long> codesFromRequest = sale.getProducts().stream().map(p -> p.getCode())
.collect(Collectors.toSet());
validateProduct(codesFromRequest);
return saleRepository.save(sale);
}
public void validateProduct(Set<Long> codesFromRequest) {
List<SaleProductDTO> products = productService.findProduct(codesFromRequest);
Set<Long> returnedCodes = products.stream().map(p -> p.getCode()).collect(Collectors.toSet());
throwExceptionIf(validateSizeList(codesFromRequest, returnedCodes),
new ProductNotFoundException());
}
public boolean validateSizeList(Collection<?> codesFromRequest, Collection<?> returnedCodes) {
return codesFromRequest.size() != returnedCodes.size();
}
}
ProductService:
#Service
#Slf4j
#AllArgsConstructor
public class ProductService {
private final ProductClientRepository productRepository;
#Retryable(value = { Exception.class }, maxAttempts = 3, backoff = #Backoff(delay = 50))
public List<SaleProductDTO> findProduct(Set<Long> codes) {
Page<SaleProductDTO> resultPage;
try {
var search = SearchProductDTO
.builder()
.codes(codes)
.build();
resultPage = productRepository.getProducts(search);
} catch (FeignException f) {
return Collections.emptyList();
}
return resultPage.getContent();
}
}
ProductClientRepository:
#FeignClient(name = "product-service", url = "${ms-product.url}", configuration = ProductOAuth2FeignConfig.class)
public interface ProductClientRepository {
#GetMapping(value = "/chunk")
Page<SaleProductDTO> getProducts(#RequestBody SearchProductDTO searchDTO);
}
ProductOAuth2FeignConfig:
public class ProductOAuth2FeignConfig {
#Autowired
private ProductConfig productConfig;
#Bean
public RequestInterceptor stockOAuth2RequestInterceptor() {
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
}
private OAuth2ProtectedResourceDetails resource() {
ClientCredentialsResourceDetails resource = new ClientCredentialsResourceDetails();
resource.setAccessTokenUri(productConfig.getTokenUri());
resource.setClientId(productConfig.getTokenClientId());
resource.setClientSecret(productConfig.getTokenClientSecret());
resource.setGrantType(productConfig.getTokenGrantType());
resource.setScope(List.of(productConfig.getTokenScope()));
return resource;
}
}
ProductConfig:
#Data
#Configuration
#ConfigurationProperties(prefix = "ms-product")
public class ProductConfig {
private String tokenUri;
private String tokenGrantType;
private String tokenClientId;
private String tokenClientSecret;
private String tokenScope;
}
application.properties:
external.api=https://myadress.com/api
ms-product.url=${external.api}/products
ms-product.token-uri=${external.api}/products/oauth2/token
ms-product.token-grant-type=client_credentials
ms-product.token-client-id=client-id-value
ms-product.token-client-secret=secret-value
ms-product.token-scope=read
feign.client.config.default.connect-timeout=3000
feign.client.config.default.read-timeout=7000

Seems you need to add #EnableFeignClients annotation. Please refer Spring Boot OpenFeign

Related

Java Service require a bean (Interface)

have anyone a idea why the Service is asking for a bean ? I'm building a small microservice with a Article- and Authorservice. Where is the issue here ?
I'm using feign-reactor-spring-cloud-starter dependency for the communication between the services.
Service:
#Slf4j
#Service
#RequiredArgsConstructor
public class ArticleService{
private final IArticleRepository articleRepository;
private final IAuthorResponse authorResponse;
public Mono<ArticleRequest> createArticle(
ArticleRequest articleRequest,
String authorEmail
){
Mono<AuthorResponse> requestedAuthor = authorResponse
.getAuthorByEmail(authorEmail)
.switchIfEmpty(Mono.error(new BadRequestException(valueOf(AUTHOR_NOT_FOUND))))
.map(author -> new AuthorResponse(
author.firstName(),
author.lastName(),
author.email()))
.log();
Article articleModel = Article.builder()
.articleId(UUID.randomUUID())
.articleTitle(articleRequest.title())
.articleContent(articleRequest.content())
.publishedAt(IDateTimeCreator.createDateTime())
.authorEmail(requestedAuthor.map(AuthorResponse::email).block()) //TODO: implement current loggedIn AuthorEmail
.build();
return articleRepository
.existsArticleByAuthorEmailAndAndArticleTitle(
articleModel.getAuthorEmail(),
articleModel.getArticleTitle())
.flatMap(exists -> {
if (exists) {
return Mono.error(
new BadRequestException(valueOf(ARTICLE_ALREADY_EXISTS)));
} else {
return articleRepository
.save(articleModel)
.map(article -> new ArticleRequest(
article.getArticleTitle(),
article.getArticleContent()))
.log();
}
});
}
}
Interface:
#ReactiveFeignClient(name = "authorservice", configuration = ReactiveFeignConfig.class)
public interface IAuthorResponse {
#GetMapping(path = "/api/v1/author/{authorEmail}")
Mono<AuthorResponse> getAuthorByEmail(#PathVariable("authorEmail") String authorEmail);
}
Config
#Configuration
public class ReactiveFeignConfig {
#Bean
public ReactiveLoggerListener loggerListener(){
return new DefaultReactiveLogger(Clock.systemUTC(), LoggerFactory.getLogger("articleservice"));
}
// Here we specify our Exception between the articleservice and authorservice
#Bean
public ReactiveStatusHandler reactiveStatusHandler() {
return ReactiveStatusHandlers.throwOnStatus(
(status) -> (status == 500),
(methodKey, response) ->
new RetryableException(
response.status(),
"Failed. The ArticleService couldn't reach the Authorservice. Please try again!",
null,
Date.from(Instant.EPOCH),
null));
}
// In case of an error such as a simple timeout occurred, we've the opportunity with this methode to try again.
#Bean
public ReactiveRetryPolicy reactiveRetryPolicy() {
return BasicReactiveRetryPolicy.retryWithBackoff(3, 2500);
}
}
This is a part of my pom.xml:
<dependency>
<groupId>com.salenaluu</groupId>
<artifactId>clients</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
The Interface is in a seperate module with no main class.
Mainclass:
#EnableReactiveFeignClients(basePackages = "com.salenaluu.clients}")
#EnableEurekaClient
#SpringBootApplication
public class ArticleServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ArticleServiceApplication.class,args);
}
}
Message:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of constructor in ...ArticleService required a bean of type '....IAuthorResponse' that could not be found.
Action:
Consider defining a bean of type '....IAuthorResponse' in your configuration.
Process finished with exit code 0

How to Autowire conditionally in spring boot?

I have created one scheduler class
public class TestSchedulderNew {
#Scheduled(fixedDelay = 3000)
public void fixedRateJob1() {
System.out.println("Job 1 running");
}
#Scheduled(fixedDelay = 3000)
public void fixedRateJob2() {
System.out.println("Job 2 running");
}
}
In configuration i have put #ConditionalOnProperty annotation to enable this on conditional purpose.
#Bean
#ConditionalOnProperty(value = "jobs.enabled")
public TestSchedulderNew testSchedulderNew() {
return new TestSchedulderNew();
}
Now in controller, i have created "stopScheduler" method to stop those scheduler , in this controller i have autowired
TestSchedulderNew class
#RestController
#RequestMapping("/api")
public class TestCont {
private static final String SCHEDULED_TASKS = "testSchedulderNew";
#Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor; /]
#Autowired
private TestSchedulderNew testSchedulderNew;
#GetMapping(value = "/stopScheduler")
public String stopSchedule(){
postProcessor.postProcessBeforeDestruction(testSchedulderNew,
SCHEDULED_TASKS);
return "OK";
}
}
Now the problem is if conditional property is false then i get below exception
Field testSchedulderNew in com.sbill.app.web.rest.TestCont required a bean of type 'com.sbill.app.schedulerJob.TestSchedulderNew
In case of true everything works fine,
Do we have any option to solve this ?
You can use #Autowired(required=false) and null check in stopScheduler method.
#Autowired(required=false)
private TestSchedulderNew testSchedulderNew;
#GetMapping(value = "/stopScheduler")
public String stopSchedule() {
if (testSchedulderNew != null) {
postProcessor.postProcessBeforeDestruction(testSchedulderNew,
SCHEDULED_TASKS);
return "OK";
}
return "NOT_OK";
}

how to resolve Spring boot #EnableAsync and #Async problem?

my service:
#Service
public class ForgetService{
#Async
public CompletableFuture<Object> getTokenService() {
Map<String, Object> map = new HashMap<>(8);
map.put("status", ErrorEnum.TOKEN_SUSSCESS.getStatus());
map.put("message", ErrorEnum.TOKEN_SUSSCESS.getMessage());
return CompletableFuture.completedFuture(map);
}
}
my controller:
#RestController
public class ForgetController {
private final ForgetService forgetService;
#Autowired
private ForgetController(ForgetService forgetService) {
this.forgetService = forgetService;
}
#PostMapping(value = "/forget/token")
#Async
public CompletableFuture<Object> getTokenController() {
return CompletableFuture.completedFuture(forgetService.getTokenService());
}
}
spring boot application class:
#SpringBootApplication
#EnableAsync
public class ApitestApplication {
public static void main(String[] args) {
SpringApplication.run(ApitestApplication.class, args);
}
}
when i run my application, console log :
The bean 'forgetService' could not be injected as a 'com.apitest.service.ForgetService' because it is a JDK dynamic proxy that implements:
com.apitest.inf.ForgetServiceInf
Action:
Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on #EnableAsync and/or #EnableCaching.
i tried setting 'spring.aop.proxy-target-class=true' in application.properties and setting '#EnableAsync(proxyTargetClass=true), but it's useless,so what's wrong? how to resolve it?
please use below approach, it might help you to fix this issue.
#Service
public class ForgetService{
#Bean("GetTokenService")
public CompletableFuture<Object> getTokenService() {
Map<String, Object> map = new HashMap<>(8);
map.put("status", ErrorEnum.TOKEN_SUSSCESS.getStatus());
map.put("message", ErrorEnum.TOKEN_SUSSCESS.getMessage());
return CompletableFuture.completedFuture(map);
}
#RestController
public class ForgetController {
private final ForgetService forgetService;
#Autowired
private ForgetController(ForgetService forgetService) {
this.forgetService = forgetService;
}
#PostMapping(value = "/forget/token")
#Async("GetTokenService")
public CompletableFuture<Object> getTokenController() {
return CompletableFuture.completedFuture(forgetService.getTokenService());
}
}

mocking beans in Configuration of a FeignClient

I've got a Spring Cloud FeignClient:
#FeignClient(name = "AccountSettingsClient", url = "${account.settings.service.url}", decode404 = true,
configuration = AccountSettingsClientConfig.class, fallbackFactory = AccountSettingsClientFallbackFactory.class)
public interface AccountSettingsClient {
#RequestMapping(method = RequestMethod.GET, value = "/settings/{uuid}",
headers = "User-Agent=page/okhttp", consumes = MediaType.APPLICATION_JSON_VALUE)
AccountSettings accountSettings(#PathVariable("uuid") String uuid);
}
The AccountSettingsClientConfig is:
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(SomeProperties.class)
#EnableFeignClients
public class AccountSettingsClientConfig {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), resource());
}
}
Now in a integration test I need to mock the oauth2FeignRequestInterceptor bean and it doesn't work:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE,
properties = {"account.settings.service.url=http://localhost:6565/users/accountsettings/"},
classes = { AccountSettingsClientConfig.class,
HttpMessageConvertersAutoConfiguration.class,
FeignAutoConfiguration.class, AccountSettingsClientIT.TestConfig.class })
#Slf4j
public class AccountSettingsClientIT {
#Inject
private AccountSettingsClient accountSettingsClient;
#TestConfiguration
static class TestConfig {
#Primary
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor() {
return mock(RequestInterceptor.class);
}
}
}
I tried also it with #MockBean, it was the same effect.
Any ideas how to fix it?

Jersey Test #Autowired field in tested class is null

I have a little problem. I think this is typical question. However, I can't find good example. My application is using Jersey. And I want to test controller by client as test. Controller has private field - StudentService. When I debug test I see, that field is null. This leads to error. And I need to inject this field. I tried this:
My Controller
#Path("/student")
#Component
public class StudentResourse {
#Autowired
private StrudentService service; // this field Spring does not set
#Path("/getStudent/{id}")
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Student getStudent(#PathParam("id") long id) {
return service.get(id);
}
}
My JUnit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:config.xml")
#TestExecutionListeners({ DbUnitTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class StudentResourseTest extends JerseyTest {
private static final String PACKAGE_NAME = "com.example.servlet";
private static final String FILE_DATASET = "/data.xml";
#Autowired
private StudentService service; // this field is setted by Spring, but I do not need this field for test
public StudentResourseTest() {
super(new WebAppDescriptor.Builder(PACKAGE_NAME).build());
}
#Override
protected TestContainerFactory getTestContainerFactory() {
return new HTTPContainerFactory();
}
#Override
protected AppDescriptor configure() {
return new WebAppDescriptor.Builder("restful.server.resource")
.contextParam("contextConfigLocation",
"classpath:/config.xml").contextPath("/")
.servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class).build();
}
#Test
#DatabaseSetup(FILE_DATASET)
public void test() throws UnsupportedEncodingException {
ClientResponse response = resource().path("student").path("getStudent")
.path("100500").accept(MediaType.APPLICATION_XML)
.get(ClientResponse.class);
Student student = (Student) response.getEntity(Student.class);
} }
I guees, that problem is in test class. Because, when I run my application not in test, I can directly request students and everything working fine. But when I test classes, internal field of Controller does not setted. How to fix this bug? Thanks for your answers.
This is in my config.xml
<context:component-scan base-package="com.example" />
<bean id="StudentResourse" class="com.example.servlet.StudentResourse">
<property name="service" ref="studentService" />
</bean>
<bean id="service" class="com.example.service.StudentServiceImpl" />
One issue may be that you're trying to configure your test application in constructor and in configure() method. Use one or another but not both because in this case your configure() method is not invoked and hence you may not be using SpringServlet and everything that is defined in this method.
Reference: https://github.com/jiunjiunma/spring-jersey-test and http://geek.riffpie.com/unit-testing-restful-jersey-services-glued-together-with-spring/
Idea is to get a hold of the application context inside jersey by using ApplicationContextAware interface. There after we can grab the exact bean already created by spring, in your case, StudentService. Below example shows a mocked version of the dependency, SampleService, used to test the resource layer apis.
Resource class delegating the processing to a service layer
#Component
#Path("/sample")
public class SampleResource {
#Autowired
private SampleService sampleService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path ("/{id}")
public Sample getSample(#PathParam("id") int id) {
Sample sample = sampleService.getSample(id);
if (sample == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
return sample;
}
}
Service layer encapsulating business logic
#Service
public class SampleService {
private static final Map<Integer, Sample> samples = new HashMap<>();
static {
samples.put(1, new Sample(1, "sample1"));
samples.put(2, new Sample(2, "sample2"));
}
public Sample getSample(int id) {
return samples.get(id);
}
}
Unit test for the above resource
public class SampleResourceTest extends SpringContextAwareJerseyTest {
private SampleService mockSampleService;
// create mock object for our test
#Bean
static public SampleService sampleService() {
return Mockito.mock(SampleService.class);
}
/**
* Create our own resource here so only the test resource is loaded. If
* we use #ComponentScan, the whole package will be scanned and more
* resources may be loaded (which is usually NOT what we want in a test).
*/
#Bean
static public SampleResource sampleResource() {
return new SampleResource();
}
// get the mock objects from the internal servlet context, because
// the app context may get recreated for each test so we have to set
// it before each run
#Before
public void setupMocks() {
mockSampleService = getContext().getBean(SampleService.class);
}
#Test
public void testMock() {
Assert.assertNotNull(mockSampleService);
}
#Test
public void testGetSample() {
// see how the mock object hijack the sample service, now id 3 is valid
Sample sample3 = new Sample(3, "sample3");
Mockito.when(mockSampleService.getSample(3)).thenReturn(sample3);
expect().statusCode(200).get(SERVLET_PATH + "/sample/3");
String jsonStr = get(SERVLET_PATH + "/sample/3").asString();
Assert.assertNotNull(jsonStr);
}
}
SpringContextAwareJerseyTest
#Configuration
public class SpringContextAwareJerseyTest extends JerseyTest {
protected static String SERVLET_PATH = "/api";
final private static ThreadLocal<ApplicationContext> context =
new ThreadLocal<>();
protected String getResourceLocation() {
return "example.rest";
}
protected String getContextConfigLocation() {
return getClass().getName();
}
static private String getContextHolderConfigLocation() {
return SpringContextAwareJerseyTest.class.getName();
}
protected WebAppDescriptor configure() {
String contextConfigLocation = getContextConfigLocation() + " " +
getContextHolderConfigLocation();
Map<String, String> initParams = new HashMap<>();
initParams.put("com.sun.jersey.config.property.packages",
getResourceLocation());
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
return new WebAppDescriptor.Builder(initParams)
.servletClass(SpringServlet.class)
.contextParam(
"contextClass",
"org.springframework.web.context.support.AnnotationConfigWebApplicationContext")
.contextParam("contextConfigLocation", contextConfigLocation)
.servletPath(SERVLET_PATH) // if not specified, it set to root resource
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class)
.build();
}
protected final ApplicationContext getContext() {
return context.get();
}
#Bean
public static ContextHolder contextHolder() {
return new ContextHolder();
}
private static class ContextHolder implements ApplicationContextAware {
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context.set(applicationContext);
}
}
}
Using the above with jersey 1.8

Categories