external URL with API endpoint in SpringBoot [duplicate] - java

I want to call another web-api from my backend on a specific request of user. For example, I want to call Google FCM send message api to send a message to a specific user on an event.
Does Retrofit have any method to achieve this? If not, how I can do that?

This website has some nice examples for using spring's RestTemplate.
Here is a code example of how it can work to get a simple object:
private static void getEmployees()
{
final String uri = "http://localhost:8080/springrestexample/employees.xml";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}

Modern Spring 5+ answer using WebClient instead of RestTemplate.
Configure WebClient for a specific web-service or resource as a bean (additional properties can be configured).
#Bean
public WebClient localApiClient() {
return WebClient.create("http://localhost:8080/api/v3");
}
Inject and use the bean from your service(s).
#Service
public class UserService {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(3);
private final WebClient localApiClient;
#Autowired
public UserService(WebClient localApiClient) {
this.localApiClient = localApiClient;
}
public User getUser(long id) {
return localApiClient
.get()
.uri("/users/" + id)
.retrieve()
.bodyToMono(User.class)
.block(REQUEST_TIMEOUT);
}
}

Instead of String you are trying to get custom POJO object details as output by calling another API/URI, try the this solution. I hope it will be clear and helpful for how to use RestTemplate also,
In Spring Boot, first we need to create Bean for RestTemplate under the #Configuration annotated class. You can even write a separate class and annotate with #Configuration like below.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Then, you have to define RestTemplate with #Autowired or #Injected under your service/Controller, whereever you are trying to use RestTemplate. Use the below code,
#Autowired
private RestTemplate restTemplate;
Now, will see the part of how to call another api from my application using above created RestTemplate. For this we can use multiple methods like execute(), getForEntity(), getForObject() and etc. Here I am placing the code with example of execute(). I have even tried other two, I faced problem of converting returned LinkedHashMap into expected POJO object. The below, execute() method solved my problem.
ResponseEntity<List<POJO>> responseEntity = restTemplate.exchange(
URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<POJO>>() {
});
List<POJO> pojoObjList = responseEntity.getBody();
Happy Coding :)

Create Bean for Rest Template to auto wiring the Rest Template object.
#SpringBootApplication
public class ChatAppApplication {
#Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ChatAppApplication.class, args);
}
}
Consume the GET/POST API by using RestTemplate - exchange() method. Below is for the post api which is defined in the controller.
#RequestMapping(value = "/postdata",method = RequestMethod.POST)
public String PostData(){
return "{\n" +
" \"value\":\"4\",\n" +
" \"name\":\"David\"\n" +
"}";
}
#RequestMapping(value = "/post")
public String getPostResponse(){
HttpHeaders headers=new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<String> entity=new HttpEntity<String>(headers);
return restTemplate.exchange("http://localhost:8080/postdata",HttpMethod.POST,entity,String.class).getBody();
}
Refer this tutorial[1]
[1] https://www.tutorialspoint.com/spring_boot/spring_boot_rest_template.htm

As has been mentioned in the various answers here, WebClient is now the recommended route.
You can start by configuring a WebClient builder:
#Bean
public WebClient.Builder getWebClientBuilder(){
return WebClient.builder();
}
Then inject the bean and you can consume an API as follows:
#Autowired
private WebClient.Builder webClientBuilder;
Product product = webClientBuilder.build()
.get()
.uri("http://localhost:8080/api/products")
.retrieve()
.bodyToMono(Product.class)
.block();

Does Retrofit have any method to achieve this? If not, how I can do that?
YES
Retrofit is type-safe REST client for Android and Java. Retrofit turns your HTTP API into a Java interface.
For more information refer the following link
https://howtodoinjava.com/retrofit2/retrofit2-beginner-tutorial

In this case need download whit my API, files hosted in other server.
In my case, don't need use a HTTP client to download the file in a external URL, I combined several answers and methods worked in previous code for files that were in my local server.
My code is:
#GetMapping(value = "/download/file/pdf/", produces = MediaType.APPLICATION_PDF_VALUE)
public ResponseEntity<Resource> downloadFilePdf() throws IOException {
String url = "http://www.orimi.com/pdf-test.pdf";
RestTemplate restTemplate = new RestTemplate();
byte[] byteContent = restTemplate.getForObject(url, String.class).getBytes(StandardCharsets.ISO_8859_1);
InputStream resourceInputStream = new ByteArrayInputStream(byteContent);
return ResponseEntity.ok()
.header("Content-disposition", "attachment; filename=" + "pdf-with-my-API_pdf-test.pdf")
.contentType(MediaType.parseMediaType("application/pdf;"))
.contentLength(byteContent.length)
.body(new InputStreamResource(resourceInputStream));
}
and it works with HTTP and HTTPS urls!

Since the question explicitly tags spring-boot, it worth noting that recent versions already ship a pre-configured instance of a builder for WebClient, thus you can directly inject it inside your service constructor without the needing to define a custom bean.
#Service
public class ClientService {
private final WebClient webClient;
public ClientService(WebClient.Builder webClientBuilder) {
webClient = webClientBuilder
.baseUrl("https://your.api.com")
}
//Add all the API call methods you need leveraging webClient instance
}
https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/boot-features-webclient.html

Simplest way I have found is to:
Create an annotated interface (or have it generated from somehing like OpenAPI)
Give that interface to Spring RestTemplate Client
The Spring RestTemplate Client will parse the annotations on the interface and give you a type safe client, a proxy-instance. Any invocation on the methods will be seamlessly translated to rest-calls.
final MyApiInterface myClient = SpringRestTemplateClientBuilder
.create(MyApiInterface.class)
.setUrl(this.getMockUrl())
.setRestTemplate(restTemplate) // Optional
.setHeader("header-name", "the value") // Optional
.setHeaders(HttpHeaders) // Optional
.build();
And a rest call is made by inoking methods, like:
final ResponseEntity<MyDTO> response = myClient.getMyDto();

Related

Mockito.When not working for custom RestTemplate

I need to do testing for the following service. When I try to do Mockito.When for custom restTemplate I get the error "org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);".
However, when I don't do when(env.getProperty("test1")).thenReturn("test"); I get the error "I/O error on POST request for "/null/Test/null": null;"
How to make test properly for such service ?
Test class
#InjectMocks
private AccountService _accountService;
#Mock
private RestTemplate _restTemplate;
#Test
public void shouldGetAccount_200() {
//when(_accountService.getRestTemplate()).thenReturn(_restTemplate);
when(env.getProperty("test1")).thenReturn("test");
when(env.getProperty("test2")).thenReturn("2");
when(_restTemplate.exchange(eq(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2")), _accId), eq(HttpMethod.GET), any(), eq(TestRequest.class)))
.thenReturn(new ResponseEntity<>(_resp, HttpStatus.OK));
assertThat(_accountService.getAcc(_accId, "test", "test", "test")).isEqualTo(_resp);
}
Service
public IResponse getAcc(String accId, String sessionId, String apiKey, String userName) {
RestTemplate restTemplate = getRestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.COOKIE, sessionId);
headers.add("X-Forwarded-For", getIPAddress());
headers.add("Authorization", String.format("PS-Auth key=%s; runas=%s;", apiKey, userName));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(null, headers);
ResponseEntity<TestRequest> response;
return restTemplate.exchange(String.format("%s/Test/%s", env.getProperty("test1"), env.getProperty("test2"))), accId),HttpMethod.GET, entity, TestRequest.class);
}
public RestTemplate getRestTemplate() {
SystemDefaultRoutePlanner routePlanner = new SystemDefaultRoutePlanner(ProxySelector.getDefault());
CloseableHttpClient httpClient = HttpClients.custom().setRoutePlanner(routePlanner)
.setConnectionManager(poolingHttpClientConnectionManager).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<ClientHttpRequestInterceptor> interceptorList = restTemplate.getInterceptors();
if (tracingClientHttpRequestInterceptor != null) {
interceptorList.add(tracingClientHttpRequestInterceptor);
interceptorList.add(traceLogClientHttpRequestInterceptor);
restTemplate.setInterceptors(interceptorList);
}
return restTemplate;
}
Using when() you assign the behavior to the mock that Mockito creates for you. This mock, however, never gets used since your getAcc() method calls the getRestTemplate that always creates a new instance of RestTemplate.
The obvious solution here is to use the put the getRestTemplate method in your configuration class and use it as a bean factory, autowiring* the RestTemplate bean into your AccountService. Then your #InjectMocks annotation will work as expected and you'll see that the service under test does indeed use the provided mock object.
*remember, there's three ways you can inject a bean in Spring - using field injection, method injection or constructor. That's not difficult to look up online so, I'll just say that most use-cases are handled best using constructor injection:
#Autowired
public AccountService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
Secondly, you don't show this but I'm pretty sure that env variable in getAcc is some kind of environment variables holder and is also not injected into the service properly so it doesn't get mocked. I would generally recommend to stick to #ConfigurationProperties (and you could easily mock those), or at least look into MockEnvironment.
As an aside, I can't think of why a service should need a new RestTemplate instance every time, and creating the RestTemplate is definitely beyound the scope of AccountService, so moving that to configuration also improves your code's structure. As a bonus, you can reuse the same RestTemplate bean in other services should you need that.
Evetually you might want to add a layer of abstraction making a separate bean to take care of constructing the url and adding the necessary headers to the RestTemplate request, so that the getAcc method gets more readable.

how to junit test on restTemplate?

I have a problem to mock restTemplate with Mockito
code want to be test:
public class Feature{
public static String getfeature(String url){
RestTemplate restTemplate = new RestTemplate();
String xml = "\"feature\": 1";
String json = restTemplate.postForObject(url, xml, String.class);
return json;
}
}
junit code:
#Mock
RestTemplate restTemplate=mock(RestTemplate.class);
#Test
public void testGetfeature(){
string testResponse= "\"feature\": 1";
Mockito.when((String)restTemplate.postForObject(
Mockito.any(String.class),
Mockito.any(Map.class),
Mockito.any(Class.class)
)).thenReturn(testResponse);
Feature feature = new Feature();
feature.getfeature("http://mockValue");
}
I set breaking point at feature.getfeature("http://mockValue"). It still try to connect to the remote server. I don't want postForObject to connect to the http://mockValue.
How should I mock the restTemplate to make postForObject not to connect to http://mockValue?
You are creating a new RestTemplate object in getfeature() method. So, mocking RestTemplate has no effect. Either take RestTemplate as an argument in getfeature() method or take it as constructor argument in Feature class.
Then from the test class, you can mock RestTemplate and pass it like below:
Feature feature= new Feature(mockRestTemplate);
feature.getfeature(url);
Or
Feature feature = new Feature();
feature.getfeature(mockRestTemplate, url);
You have to make the necessary changes in the Feature class based on the decision.
Here is the running code sample:
Main class:
public class Feature {
public static String getFeature(String url, RestTemplate restTemplate) {
return restTemplate.postForObject(url, "", String.class);
}
}
Test class:
Notice the way the RestTemplate is mocked and then the response is mocked.
public class FeatureTest {
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
}
The running code sample is also available at github
Feature.java and FeatureTest.java
There is no need to constantly create a RestTemplate object.
You can create one and inject it into the class.
Then, in your unit test, create a mock RestTemplate and inject that.
Edit:
Concerning the static method.
Don't use a static method,
that is terrible.
If you are using Spring (or any other Dependency Injection framework)
just inject an instance of Feature where ever you want to make the rest call.
Also,
in the Spring world Feature is a service class.
Use the #Service annotation and make the method not static.
I think you need to change your code to make your unit test work atleast using Mockito or you have to use some other library like powermock to mock local object instantiation.
1) create a constructor that accepts RestTemplate as argument to inject your mock.
or
2) create some setter method to inject that RestTemplate.
one more way is to create another method where RestTemplate can be passed.
public String getStringAsJson(RestTemplate restTemplate, String url, String xml) {
return restTemplate.postForObject(url, xml, String.class);
}
then in your test:
RestTemplate mockRestTemplate = mock(RestTemplate.class);
when(restTemplate.postForObject(mockurl, mockUrl, mockXml)).thenReturn("Json");
feature.getStringAsJson(mockRestTemplate,mockUrl,mockXml);
how to junit test on restTemplate?
We test what it returns.
Currently your implementation does nothing itself, it simply delegates to a RestTemplate and return its result.
The fiveelements answer describes the implementation (with broad accepted values as parameters) :
#Test
public void testFeature() {
RestTemplate restTemplate = Mockito.mock(RestTemplate.class);
Mockito.when(restTemplate.postForObject(Mockito.any(String.class),
Mockito.any(Object.class), Mockito.any(Class.class))).thenReturn("abc");
System.out.println(Feature.getFeature("http://abc", restTemplate));
}
That doesn't assert the actual behavior : the URL could be erroneous, the body posted or the response could be wrong and so for... and this test will never detect that. Finally, very important things could be wrong in this implementation and we could not detect that. The value of this kind of test is so weak.
Since this method doesn't perform itself logic, this is better suited for an integration test that could assert and catch much more thing/issues in the actual behavior :
#Test
public void testFeature() {
String actualFeature = Feature.getFeature("http://...");
String expectedFeature = ...;
Assertions.assertEquals(expectedFeature, actualFeature);
}
If you use PowerMockito then you can do like following:
No code change needed here for Feature class then you can use this directly if PowerMockito lib available in prj
RestTemplate mockedRestTemplate = PowerMockito.mock(RestTemplate.class);
PowerMockito.whenNew(RestTemplate.class).withAnyArguments().thenReturn(mockedRestTemplate);

writing unit test cases for an interface which has methods without arguments

I have an interface which has a method without arguments
public interface HealthStatus {
Integer healthCheck();
}�
The implementation is as below
#Override
public Integer healthCheck() {
Integer status = 0;
try {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
if (proxyConfigProperties.getEnabled()) {
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxyConfigProperties.getUrl(), proxyConfigProperties.getPort()));
simpleClientHttpRequestFactory.setProxy(proxy);
}
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory));
ResponseEntity<String> healthResponse = restTemplate.exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
status = (healthResponse.getStatusCode() == HttpStatus.OK) ? 200 : 202;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}�
How can I unit test this method for positive scenarios and negative scenarios.
Edit:
I have refactored my class as below
#Service
#Qualifier("implementation")
public class HealthStatusImpl implements HealthStatus {
#Autowired
RestTemplateConfig restTemplateConfig;
#Autowired
private EVerifyGovernmentProperties eVerifyGovernmentProperties;
#Override
public Integer healthCheck() {
Integer status = 0;
try {
ResponseEntity<String> healthResponse = restTemplateConfig.getRestTemplate().exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
status = (healthResponse.getStatusCode() == HttpStatus.OK) ? 200 : 202;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}
}
Here is the class which instantiates the RestTemplate
#Component
public class RestTemplateConfig {
#Autowired
ProxyConfigProperties proxyConfigProperties;
public RestTemplate getRestTemplate(){
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
if (proxyConfigProperties.getEnabled()) {
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(proxyConfigProperties.getUrl(), proxyConfigProperties.getPort()));
simpleClientHttpRequestFactory.setProxy(proxy);
}
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory));
return restTemplate;
}
}
Actually this is what you want to mock :
restTemplateConfig.getRestTemplate().exchange(eVerifyGovernmentProperties.getUrl(), HttpMethod.GET, null, String.class);
but this will require deep mocking and that is also a bad smell : you should not need to define such a statement to invoke the rest template. This responsibility should be defined in a specific class.
So move it into a method of a specific bean for example RestTemplateService that will relieve you from passing as many as parameters and it will also balance better responsibility of this class by reducing its dependencies :
ResponseEntity<String> healthResponse = restTemplateService.getForHealthCheck();
Now just mock it with Mockito.
Concerning RestTemplateService, you could create your own class or rely on Feign (here Spring Feign makes more sense) that enables declarative rest client via interface.
It would give :
public class HealthStatusImpl implements HealthStatus {
private RestTemplateService restTemplateService;
// favor constructor injection
public HealthStatusImpl(RestTemplateService restTemplateService){
this.restTemplateService = restTemplateService;
}
#Override
public Integer healthCheck() {
Integer status = 0;
try {
ResponseEntity<String> healthResponse = restTemplateService.getForHealthCheck();
status = healthResponse.getStatusCode().is2xxSuccessful() ? 200 : 400;
} catch (Throwable th) {
th.printStackTrace();
}
return status;
}
}
Note that Status.is2xxSuccessful() is generally better as it returns true for any successful response (200, 201, etc..). And if it is not successful you want to return an error response code.
From the unit test side, you should mock this dependency and record a mock behavior according to your scenarios.
Note that in your case, you don't want to load a whole spring context but you want to perform a plain unit test, that is without container. So don't use #SpringBootTest but only JUnit and Mockito.
For example with JUnit 5 :
#ExtendWith(MockitoExtension.class)
public class HealthStatusImplTest{
private HealthStatusImpl healthStatusImpl;
#Mock
private RestTemplateService restTemplateServiceMock;
#BeforeEach
public void beforeEach(){
healthStatusImpl = new HealthStatusImpl(restTemplateService);
}
#Test
public void healthCheck_when_200_is_returned(){
Mockito.when(restTemplateServiceMock)
.getForHealthCheck().thenReturn(new ResponseEntity(HttpStatus.OK));
assertEquals(200, healthStatusImpl.healthCheck());
}
#Test
public void healthCheck_when_200_is_not_returned(){
Mockito.when(restTemplateServiceMock)
.getForHealthCheck().thenReturn(new ResponseEntity(HttpStatus.NOT_FOUND));
assertEquals(400, healthStatusImpl.healthCheck());
}
}
Of course the RestTemplateService should also be unitary test and unitary tests don't relieve from writing integration tests for higher level components.
To unit test a web client such as a RestTemplate based class, you need a framework which mocks the server, e.g.
the one from Spring: MockRestServiceServer:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-client
https://www.baeldung.com/spring-mock-rest-template
WireMock: http://wiremock.org/docs/getting-started/
I'm not sure that your check (healthResponse.getStatusCode() == HttpStatus.OK) is correct, because if a status is not 2xx RestTemplate throws HttpStatusCodeException.
That's why it's always better to have some mocks if you integrate with a third party side.
That's why I also recommend you to consider MockRestServiceServer in your tests. see #Puce answer for links.
Also, there is no necessity to create a new RestTemplate for each request.
That's why I also recommend you to consider constructor injection. see #davidxxx answer for refactoring approaches. And don't forget to put connection timeouts in your RestTemplate settings.

Test RestTemplate Interceptors

i've implemented a Interceptors for RestTemplate (RestTemplateInterceptor.class):
#Autowired
private RestConfigurations configurations;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.additionalInterceptors((ClientHttpRequestInterceptor) (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
headers.add("X-API-KEY", configurations.getApiKey());
return clientHttpRequestExecution.execute(httpRequest,bytes);
}).build();
}
Now I want to test it. :)
One try looks like that:
#ExtendWith(SpringExtension.class)
#RestClientTest({RestTemplateInterceptor.class, RestConfigurations.class})
#AutoConfigureMockMvc
#SpringBootTest
class MyTestClass {
public static final String URL = "http://testMe.com/";
#Autowired
MockRestServiceServer mockServer;
#Autowired
RestTemplateBuilder restTemplateBuilder;
#Test
#DisplayName("Should include header X-API-KEY")
void headerShouldContainsXApiKey() throws Exception {
mockServer.expect(requestTo(URL)).andRespond(withSuccess("Hello world", TEXT_PLAIN));
String body = restTemplateBuilder.build().exchange(URL//
, GET, //
null, //
String.class).getBody();
assertThat(body).isEqualTo("Hello world");
mockServer.expect(header("X-API-KEY", ""));
}
But i failed with:
java.lang.AssertionError: No further requests expected: HTTP GET http://test.hornbach.de/
0 request(s) executed.
Anyone a hint? Thanks
If you check the Javadoc, you'll see that when you call additionalInterceptors, you're not modifying the existing builder instance but instead getting a new builder with a slightly different configuration. When you then call restTemplateBuilder.build() in your test case, you're building a template that has the unmodified configuration.
The ideal way to test something like this is to do so directly on the interceptor instance itself, but if you're wanting to perform a functional test by calling through to a mock server, you should inject the completed RestTemplate instance into your test case.

Service injected into spring controller is not available in one of the functions

I am writing spring controller, which injects a bean.
The bean is added in config(we use java config for everything):
#Bean
public NotificationService notificationService() {
return new NotificationService();
}
The service itself has few injected dependencies and few functions:
public class NotificationService {
#Inject
NotificationRepository notificationRepository;
#Inject
ProjectRepository projectRepository;
#Inject
ModelMapper modelMapper;
public NotificationDto create(NotificationDto notificationDto) {
//convert to domain object, save, return dto with updated ID
return notificationDto;
}
public void markAsRead(Long id, String recipientNip) {
//find notification, update status
}
}
Model mapper has almost no configuration, is only set to strict. Meanwhile repositoriers are interfaces extending JpaRepository with no custom functions. They are found by #EnableJpaRepositories.
Finally I have controller that tries to use the code above:
#RestController
#RequestMapping("/notifications")
public class NotificationController extends ExceptionHandlerController {
#Autowired
private NotificationService notificationService;
#PreAuthorize("isFullyAuthenticated() and hasRole('create_notification')")
#RequestMapping(method = RequestMethod.POST, consumes = MediaTypeExtension.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> createNotification(#Valid #RequestBody(required = true) final NotificationDto notification) {
this.notificationService.create(notification);
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
#PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
#RequestMapping(value = "/{id}/read", method = RequestMethod.PUT)
private ResponseEntity<?> markNotificationAsRead(#PathVariable("id") Long id, #AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
}
All controllers are added trough #ComponentScan, based on their package.
As you can see both functions use notificationService. When I send POST for create on /notifications the notificationService is properly injected. In the same controller, when I do PUT request on /{id}/read, the notificationService is null.
I think it has something to do with spring putting things into its container, and for some reason not being able to do it for that one function. I have few more functions in the controller and in all of them notificationService is properly injected. I don't see any real difference between createNotification and markNotificationAsRead functions and I couldn't find anything even remotely related on google/stack. In all cases the service wouldn't inject at all because of configuration mistake.
Edit
I have tried changing things around in the function until it has started working. My final code looks like this:
#PreAuthorize("isFullyAuthenticated() and hasRole('update_notification')")
#RequestMapping(value = "{id}/read", method = RequestMethod.PUT)
public ResponseEntity<?> read(#PathVariable("id") Long id, #AuthenticatedContractor ContractorDto contractor) {
this.notificationService.markAsRead(id, contractor.getNip());
final HttpHeaders headers = new HttpHeaders();
return new ResponseEntity<>(headers, HttpStatus.OK);
}
and it works. Honestly I can't see any difference from my original code, and I have been staring at it for last hour or so. The imports are the same too.
I have also noticed(on unworking code) that while all functions from the controller on debug stack were marked as
NotificationController.functionName(arguments) line: x
The non working function was:
NotificationController$$EnhancerBySpringCGLIB$$64d88bfe(NotificationController).‌​markNotificationAsRead(ContractorDto) line: 86
Why this single function was enhanced by spring CGLIB I have no idea. I have tried looking it up, but for now I came empty handed. Even though the code started to work I am leaving the question open in order to find the underlying cause.
Your method markNotificationAsRead is private and that probably causes the issue. I've just had same issue with final method - this message appeared in log:
2016-11-28 17:19:14.186 INFO 97079 --- [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy method [public final java.util.Map com.package.controller.MyController.someMethod(javax.servlet.http.HttpServletResponse)] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
Looks like in one case we see a CGLib proxy, and in another - the actual class. Only one of those has all the fields injected, looks like the proxy has all fields nulls. But it doesn't matter - the point is your method should be public and not final in order to be proxied properly by #PreAuthorize methods.
I was also facing the same issue. It was all due to the private access modifier used and #PreAuthorize. Making the controller method private does not make an issue if you do not make it secure. But, to make secure, make it public.

Categories

Resources