Testing Spring Boot Eureka Server 404 - spring-boot

I'm trying to test authentication in my Spring Boot Eureka Server. To do so, I perform a GET on /eureka/apps. I get a 404 instead of 200.
#RunWith(SpringRunner.class)
#WebAppConfiguration
#SpringBootTest(classes = Application.class)
public class GlobalSecurityTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void givenRoleDiscoveryClient_whenGetEureka_then200() throws Exception {
mockMvc.perform(get("/eureka/apps").header(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT)
.andExpect(status().isOk());
}
}
Eureka starts correctly as the logs prove:
2018-04-12 23:07:39.308 INFO 80833 --- [ Thread-12] e.s.EurekaServerInitializerConfiguration : Started Eureka Server
2018-04-12 23:07:39.315 INFO 80833 --- [ main] GlobalSecurityTest : Started GlobalSecurityTest in 7.255 seconds (JVM running for 8.007)
...
2018-04-12 23:07:39.822 DEBUG 80833 --- [ main] o.s.security.web.FilterChainProxy : /eureka/apps/REGISTRY reached end of additional filter chain; proceeding with original chain
2018-04-12 23:07:39.831 DEBUG 80833 --- [ main] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl#0: Authentication: StateTokenAuthentication{principalTokenState=be.charliebravo.ibpt.qos3.commons.security.models.ClientState#50b624da, tokenStates={}}' stored to HttpSession: 'org.springframework.mock.web.MockHttpSession#50b4e7b2
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2018-04-12 23:07:39.833 DEBUG 80833 --- [ main] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
java.lang.AssertionError: Status
Expected :200
Actual :404
My security config:
#Configuration
public class WebSecurityConfig {
#Configuration
#Order(3)
public static class DiscoveryClientSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private StateTokenHttpSecurityConfigurer stateTokenHttpSecurityConfigurer;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/eureka/**").authorizeRequests()
.anyRequest().hasRole(Role.DISCOVERY_CLIENT.toString())
.and().exceptionHandling().authenticationEntryPoint(new Http401UnauthorizedEntryPoint());
stateTokenHttpSecurityConfigurer.configure(http);
}
}
}
The Eureka server works fine when I run the application instead of the test.

Don't use MockMvc, because it is limited to testing the web layer, but Eureka mappings aren't registered there. Instead, use TestRestTemplate.
Remove #WebAppConfiguration and add weEnvironment setting in #SpringBootTest
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Autowire TestRestTemplate and local server port
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int localServerPort;
Perform the request
#Test
public void givenRoleDiscoveryClient_whenGetEurekaPage_then200() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.AUTHORIZATION, TOKEN_DISCOVERY_CLIENT);
HttpEntity entity = new HttpEntity<>(null, headers);
String endpoint = "https://localhost:" + localServerPort + "/eureka/apps";
ResponseEntity responseEntity = restTemplate.exchange(endpoint, HttpMethod.GET, entity, String.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
And off you go.

Related

#SpringBootTest constructor is running multiple times

I am trying to run below test case in Spring Boot.
:: Spring Boot :: (v2.3.1.RELEASE)
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = com.dineoutsafe.api.dosadmin.DOSAdminAPIApplication.class)
#ActiveProfiles("test")
public class POSTSessionTest {
public POSTSessionTest() {
System.out.println("Calling post construct");
}
#Test
public void testOne(){
assertThat(45,equalTo(30+15));
}
#Test
public void testTwo(){
assertThat(45,equalTo(30+15));
}
#Test
public void testThree(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFour(){
assertThat(45,equalTo(30+15));
}
#Test
public void testFive(){
assertThat(45,equalTo(30+15));
}
}
And I noticed that the constructor is running multiple times. Actually it is running (no. of #Test -1) times.
In standard output
2020-06-21 16:00:26.668 INFO 93912 --- [ task-1] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-06-21 16:00:26.679 INFO 93912 --- [ task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-06-21 16:00:27.025 INFO 93912 --- [ Test worker] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-06-21 16:00:27.034 INFO 93912 --- [ Test worker] c.d.a.d.i.session.POSTSessionTest : Started POSTSessionTest in 5.511 seconds (JVM running for 6.414)
Calling post construct
Calling post construct
Calling post construct
Calling post construct
Same behaviour I noticed for #PostConstruct.
Is it normal for #SpringBootTest?
This is the default behavior of JUnit5, you can change it by annotating the per-class lifecycle on the class: https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/TestInstance.Lifecycle.html

Spring - How to build a junit test for a soap service

I'm following the spring guide to create a hello world soap ws. The link below :
https://spring.io/guides/gs/producing-web-service/
I successfully make it work. When i run this command line :
curl --header "content-type: text/xml" -d
#src/test/resources/request.xml http://localhost:8080/ws/coutries.wsdl
I get this response.
<SOAP-ENV:Header/><SOAP-ENV:Body><ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service"><ns2:country><ns2:name>Spain</ns2:name><ns2:population>46704314</ns2:population><ns2:capital>Madrid</ns2:capital><ns2:currency>EUR</ns2:currency></ns2:country></ns2:getCountryResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
Now i'm trying to create a junit test for this service (the controller layer) but it doesn't work.
Here is my unit test :
#RunWith(SpringRunner.class)
#WebMvcTest(CountryEndpoint.class)
#ContextConfiguration(classes = {CountryRepository.class, WebServiceConfig.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
mockMvc.perform(
get(URI)
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)
)
.andDo(print())
.andExpect(status().isOk());
}
static String request = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n" +
" xmlns:gs=\"http://spring.io/guides/gs-producing-web-service\">\n" +
" <soapenv:Header/>\n" +
" <soapenv:Body>\n" +
" <gs:getCountryRequest>\n" +
" <gs:name>Spain</gs:name>\n" +
" </gs:getCountryRequest>\n" +
" </soapenv:Body>\n" +
"</soapenv:Envelope>";
}
here's the error :
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
I changed the log level to debug and i found this :
2020-01-27 18:04:11.880 INFO 32723 --- [ main] c.s.t.e.s.endpoint.CountryEndpointTest : Started CountryEndpointTest in 1.295 seconds (JVM running for 1.686)
2020-01-27 18:04:11.925 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Looking up handler method for path /ws/countries.wsdl
2020-01-27 18:04:11.929 DEBUG 32723 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Did not find handler method for [/ws/countries.wsdl]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Matching patterns for request [/ws/countries.wsdl] are [/**]
2020-01-27 18:04:11.930 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : URI Template variables for request [/ws/countries.wsdl] are {}
2020-01-27 18:04:11.931 DEBUG 32723 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapping [/ws/countries.wsdl] to HandlerExecutionChain with handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#c7a977f]]] and 1 interceptor
I tried another solution (below) but it doesn't work either.
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = {WebServiceConfig.class, CountryRepository.class})
public class CountryEndpointTest {
private final String URI = "http://localhost:8080/ws/countries.wsdl";
private MockMvc mockMvc;
#Autowired
CountryRepository countryRepository;
#Before
public void setup() {
this.mockMvc = standaloneSetup(new CountryEndpoint(countryRepository)).build();
}
Spring doc says :
https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html
By default, #SpringBootTest will not start a server.
You need to define
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
to RUN the server.
I tried with mockserver, but I can't access to the endpoint (even with WebEnvironment.DEFINED_PORT)
So I did it as follow :
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
public class FacturationEndpointTest {
#Autowired
private WebTestClient webClient;
#Test
public void testWSDL() throws Exception {
this.webClient.get().uri("/test_service/services.wsdl")
.exchange().expectStatus().isOk();
}
You need to add the following dependency in your pom.xml if you want to use WebTestClient like me :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
Please change the GET method to POST.
mockMvc.perform(
postURI) // <-- This line!!!
.accept(MediaType.TEXT_XML)
.contentType(MediaType.TEXT_XML)
.content(request)
if you are using a spring ws framework to implements your endpoints, please see spring-ws-test. you will find a MockWebServiceClient that mocks a client and tests your endpoint. I propose you to see this example : https://memorynotfound.com/spring-ws-server-side-integration-testing/
this works only for spring web service and not for CXF web services.

Getting "reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response" while mocking a third part API

My Spring boot API is consuming a third part API using WebClient
DemoAPIController
#RestController
public class DemoAPIController
{
#Autowired
DemoService demoService;
#GetMapping("/mytest")
public Mono<UserType> getUserType()
{
return demoService.getUserType();
}
}
DemoService.java
#Component
public class DemoService
{
#Value("${base.url}")
private String baseUrl;
public Mono<UserType> getUserType()
{
System.out.println("baseUrl:" + baseUrl);
WebClient webClient = WebClient.builder().baseUrl(baseUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("apikey", "test").build();
Mono<UserType> testResult = webClient.get()
.uri("/valic/valic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO").retrieve()
.bodyToMono(UserType.class);
testResult.subscribe(value -> System.out.println(value.getUserType()));
return testResult;
}
}
I have mocked the third part API using MockWebServer. When I try to test the API using mockMvc.perform(...). My assertions are working as expected. But while shutting down the MockWebServer, I am getting the following exception
2019-11-29 16:02:43.308 INFO 13048 --- [127.0.0.1:53970] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] received request: GET /valic/valic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO HTTP/1.1 and responded: HTTP/1.1 200 OK
2019-11-29 16:03:18.362 INFO 13048 --- [ckWebServer 443] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] done accepting connections: socket closed
2019-11-29 16:03:18.385 WARN 13048 --- [ctor-http-nio-1] r.netty.http.client.HttpClientConnect : [id: 0x186c45c1, L:0.0.0.0/0.0.0.0:53970 ! R:localhost/127.0.0.1:443] The connection observed an error
reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
2019-11-29 16:03:18.389 INFO 13048 --- [127.0.0.1:53970] okhttp3.mockwebserver.MockWebServer : MockWebServer[443] connection from /127.0.0.1 failed: java.net.SocketException: Socket closed
2019-11-29 16:03:20.509 INFO 13048 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
My Test case
#ExtendWith(SpringExtension.class)
#WebAppConfiguration()
#ContextConfiguration(classes = WebclientApplication.class)
#SpringBootTest
public class EmployeeServiceMockWebServerTest
{
public static MockWebServer mockWebServer;
private ObjectMapper MAPPER = new ObjectMapper();
public MockMvc mockMvc;
public MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
public MediaType contentTypeURLEncoded = new MediaType(MediaType.APPLICATION_FORM_URLENCODED.getType(),
MediaType.APPLICATION_FORM_URLENCODED.getSubtype(), Charset.forName("utf8"));
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeAll
static void setUp() throws IOException
{
mockWebServer = new MockWebServer();
mockWebServer.start(443);
}
#AfterAll
static void tearDown() throws IOException
{
mockWebServer.shutdown();
}
#BeforeEach
void initialize()
{
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
System.out.println("Hostname:" + mockWebServer.getHostName());
System.out.println("Port:" + mockWebServer.getPort());
}
#Test
void getEmployeeById() throws Exception
{
UserType userType = new UserType();
userType.setUserType("PAR-Mock");
ServiceMessage serviceMessage = new ServiceMessage();
serviceMessage.setCode("200");
serviceMessage.setType("OK");
serviceMessage.setDescription("From Mock");
userType.setServiceMessage(serviceMessage);
mockWebServer.enqueue(
new MockResponse().setBody(MAPPER.writeValueAsString(userType)).addHeader("Content-Type", "application/json"));
mockMvc.perform(get("/mytest").contentType(contentType)).andExpect(status().isOk());
RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertEquals("GET", recordedRequest.getMethod());
assertEquals("/logic/logic-security-data-api/v1/utility/users/ID873366777/usertype?appName=EAO",
recordedRequest.getPath());
}
}
I tried all the solution mentioned in other start overflow posts but still I am getting the same error. Any help is appreciated.
I fixed the issue by commenting the below line.
testResult.subscribe(value -> System.out.println(value.getUserType()));
I created WebClient object using the below code to get additional logging
WebClient webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection().compress(true).wiretap(true)))
.baseUrl(baseUrl).defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("apikey", "somekey").build();
I was able to see two requests.Then after commenting, everything worked as expected.

How to mock custom JWT claims in #WebMvcTest

I am using Spring Boot 2.2.0.RELEASE and my Spring-based backend acts as an OAuth2 Resource server which runs fine in production.
All my REST endpoints are protected :
public class BookingController {
#PreAuthorize("hasAuthority('booking:WRITE')")
#PostMapping(value = "/book")
public ResponseEntity<Void> createBooking(#RequestBody BookModel bookModel, JwtAuthenticationToken jwt) {..}
I wanted to write a unit test against my REST APIs and I would like to mock the JWT token.
I tried the following but I always get the "Access denied message"
My Unit test looks like the following:
#WebMvcTest(controllers = BookingController.class)
public class BookingControllerTests {
#Autowired
private ObjectMapper objectMapper;
#Autowired
MockMvc mockMvc;
#MockBean
JwtDecoder jwtDecoder;
#Test
public void when_valid_booking_then_return_200() {
BookModel bookModel = new BookModel();
mockMvc
.perform(post("/book")
.with(jwt(jwt ->jwt().authorities(new SimpleGrantedAuthority("booking:WRITE"))))
.contentType("application/json")
.content(objectMapper.writeValueAsBytes(bookModel)))
.andExpect(status().isCreated());
}
Somehow the claims which are defined in mockMvc are ignored. See the debug output :
PrePostAnnotationSecurityMetadataSource : #org.springframework.security.access.prepost.PreAuthorize(value=hasAuthority('booking:WRITE')) found on specific method: public org.springframework.http.ResponseEntity BookingController.createBooking(BookModel ,org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken)
o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /book; Attributes: [permitAll]
o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#eca97305: Principal: org.springframework.security.oauth2.jwt.Jwt#bbd01fb9; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: SCOPE_read
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#18907af2, returned: 1
o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
o.s.s.w.FilterChainProxy : /book reached end of additional filter chain; proceeding with original chain
o.s.s.a.i.a.MethodSecurityInterceptor : Secure object: ReflectiveMethodInvocation: public org.springframework.http.ResponseEntity BookingController.createBooking(BookModel,org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken); target is of class [BookModel]; Attributes: [[authorize: 'hasAuthority('booking:WRITE')', filter: 'null', filterTarget: 'null']]
o.s.s.a.i.a.MethodSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken#eca97305: Principal: org.springframework.security.oauth2.jwt.Jwt#bbd01fb9; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: SCOPE_read
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter#67afe909, returned: -1
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter#79f1e22e, returned: 0
o.s.s.a.v.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter#6903ed0e, returned: 0
c.s.d.r.e.GlobalExceptionHandler : mapped AccessDeniedException to FORBIDDEN
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
In your lambda for mocking the JWT you are calling the post processor twice by using the parentheses twice .with(jwt(jwt ->jwt()...))
Instead, try
mockMvc
.perform(post("/book")
.with(jwt().authorities(new SimpleGrantedAuthority("booking:WRITE"))))
If you need the security context up, then you are not writing an unit-test.
Anyway, why not to use #WithMockUser?
Here you can see a snapshot of how to use it in an Integration Test which is mocking the Business Layer.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ActiveProfiles("api")
public class GetAllProfilesControllerITest {
#MockBean
private GetAllProfilesDAO getAllProfilesDAO;
#MockBean
private ProfileControllerMapper profileControllerMapper;
#Inject
private WebApplicationContext context;
private MockMvc mockMvc;
private static final String ENDPOINT = "/profiles";
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#WithMockUser(authorities = "MINION")
#Test
public void should_returnUnauthorized_when_cantView() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isUnauthorized());
}
#WithMockUser(authorities = {"VIEW", "CREATE"})
#Test
public void should_returnOk_when_canView() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isOk());
}
#WithMockUser(authorities = "PUPPY")
#Test
public void should_returnOk_when_puppy() throws Exception {
//when
mockMvc.perform(get(ENDPOINT))
//then
.andExpect(status().isOk());
}
}
MvcResult result =
mvc.perform(
MockMvcRequestBuilders.get("/v1/path/example")
.with(jwt(token -> token.claim("claimKey","claimValue"))
.accept(MediaType.APPLICATION_JSON))
.andReturn();
to use this you have to use mock mvc. so annotate your test class with #AutoConfigureMockMvc
and inject MockMvc
#Autowired private MockMvc mvc;

#Cacheable and initialization during startup

I would like to initialize all entries in cache during startup of my spring boot application (loading stuff from DB). Ideally, this is done before the application is already ready. So I implemented all loading in #PostConstruct. I remarked, that the cache is not already setup in #PostContruct and I followed some tips to do such initializations in the ApplicationReadyEvent. However, this still does not work as expected:
Even though I already call a #Cacheable Method in ApplicationReadyEvent, the second invocation re-enters the method instead of using the cache.
My Service:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
getEntry();
}
}
My Caffeine CacheManager Config:
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
List<CaffeineCache> caffeineCaches = chacheList(Arrays.asList(
"entry"
));
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(caffeineCaches);
System.out.println("*** #Bean CacheManager");
return simpleCacheManager;
}
private List<CaffeineCache> chacheList(List<String> cacheNames) {
return cacheNames.stream().map(s -> new CaffeineCache(s, Caffeine.newBuilder().build()))
.collect(Collectors.toList());
}
}
A simple REST endpoint using the service:
#RestController
public class MyController {
#Autowired
MyService myService;
#GetMapping("/test")
public void test()
{
System.out.println("*** GET /test");
myService.getEntry();
}
}
If I start the application and perform two GET /test, I get the following output:
INFO 20120 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 907 ms
*** #Bean CacheManager
INFO 20120 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
INFO 20120 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
INFO 20120 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.639 seconds (JVM running for 2.473)
*** onApplicationEvent
*** getEntry called!
INFO 20120 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
INFO 20120 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
*** GET /test
*** getEntry called!
*** GET /test
So why does the second invocation of MyService.getEntry (i.e. the first invocation after "Startup") enters the code again?
At the end, I need a solution, which performs the first loading before the application finished to startup - i.e. I will try ContextRefreshedEvent or again #PostConstruct (and #Autowire CacheManager to have it configured before executing #PostConstruct). But the first step would be to get this example here behave as expected.
Ok, stupid error: in my service, the call to getEntry() must be done over proxy object rather than directly:
#Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {
#Autowired
MyService self;
#Cacheable("entry")
public List<String> getEntry() {
System.out.println("getEntry called!");
return Arrays.asList("aaa", "bbb");
}
#Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("*** onApplicationEvent");
self.getEntry();
}
}

Resources