Hi I was wondering if it's possible to leverage Spring annotated Caching within Scala. I have tried but am receiving the error below. I am running the application from a java package that depends on the scala package.
No cache could be resolved for 'CacheableOperation[public scala.collection.immutable.List MerchantDataGateway.getAllMerchants()]
My Configuration Class
#Configuration
#EnableCaching
#ComponentScan(basePackages = "xxx.xxx.xxx.xxx")
public class EnvironmentHelperConfig {
#Bean
public CacheManager getCacheManager() {
final int ttl = 12;
final int maxCacheSize = 1012;
GuavaCacheManager result = new GuavaCacheManager();
result.setCacheBuilder(CacheBuilder
.newBuilder()
.expireAfterWrite(ttl, TimeUnit.HOURS)
.maximumSize(maxCacheSize));
return result;
}
}
My Scala Class
#Component
class MerchantDataGateway {
#Autowired
var fmcsProxy: MerchantResource = null;
#Cacheable
def getAllMerchants(): List[MerchantViewModel] = {
val merchants = getAllMerchantsFromFMCS()
merchants.map(merchant => MerchantViewModel.getLightWeightInstance(merchant))
}
}
Add a name to the #Cacheable annotation:
#Cacheable(Array("MerchantDataGateway.getAllMerchants"))
It needed a name, or an entry for the value
#Cacheable(value = Array("MerchantDataGateway.getAllMerchants")
Related
In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))
I am trying to use #Cacheable to cache the roles regardless of the parameter. But the #Cacheable does not quite work and the method would get called twice.
CachingConfig:
#Configuration
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager(#Value("${caching.ttl.period}") long period,
#Value("${caching.ttl.unit}") String unit) {
return new ConcurrentMapCacheManager() {
#Override
public Cache createConcurrentMapCache(String name) {
return new ConcurrentMapCache(name, CacheBuilder.newBuilder()
.expireAfterWrite(period, TimeUnit.valueOf(unit)).build().asMap(), true);
}
};
}
}
RoleMappingService:
#Service
public class RoleMappingService {
private final AdminClient adminClient;
public RoleMappingService(AdminClient adminClient) {
this.adminClient = adminClient;
}
#Cacheable(value = "allRoles", key = "#root.method")
public List<Role> getAllRoles(String sessionToken) {
AdminSession adminSession = new AdminSession();
AdminSession.setSessionToken(sessionToken);
List<RoleGroup> allRoleGroups = this.adminClient.getAllRoleGroups(adminSession)
.orElse(Collections.emptyList());
List<Role> allRoles = allRoleGroups
.stream()
.map(RoleGroup::getRoles)
.flatMap(List::stream)
.collect(Collectors.toList());
return allRoles;
}
Test:
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RoleCachingTest {
private final JFixture fixture = new JFixture();
private AdminClient adminClient = mock(AdminClient.class);
#Test
public void allRolesShouldBeCached(){
RoleGroup mockRoleGroup = mock(RoleGroup.class);
Role mockRole = this.fixture.create(Role.class);
when(this.adminClient.getAllRoleGroups(any(AdminSession.class)))
.thenReturn(Optional.of(Arrays.asList(mockRoleGroup)));
when(mockRoleGroup.getRoles()).thenReturn(Arrays.asList(mockRole));
RoleMappingService sut = new RoleMappingService(adminClient);
List<Role> firstRes = sut.getAllRoles(
fixture.create(String.class));
List<Role> secondRes = sut.getAllRoles(
fixture.create(String.class));
assertEquals(firstRes.size(), secondRes.size());
assertEquals(firstRes.get(0).getId(), secondRes.get(0).getId());
assertEquals(firstRes.get(0).getRoleName(), secondRes.get(0).getRoleName());
// The getAllRoleGroups() should not be called on the second call
verify(this.adminClient, times(1)).getAllRoleGroups(any(AdminSession.class));
}
The adminClient.getAllRoleGroups() would always get called twice in this test, while I expect it would only get called once because of #Cacheable.
The project structure:
project structure
I think your #Cacheable annotation is not working because you have not specified Interface for class. This is because of proxy created for caching by Spring. Spring has specified below in its documentation . I thing you have not specified proxy-target-class, it means it will be default to false. If it is false it will use JDK interface based proxies. But in your case you class i.e. RollMappingService is not implementing interface. Create interface RollMappingService with method getAllRoles and implement it, will sole your problem.
Controls what type of caching proxies are created for classes annotated with the #Cacheable or #CacheEvict annotations. If the proxy-target-class attribute is set to true, then class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, then standard JDK interface-based proxies are created. (See Section 9.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)
Also modify your test class to create Spring bean for RoleMappingService in following ways and inject mock of AdminClient into it
#Mock
private AdminClient mockedAdminClient;
#InjectMocks
#Autowired
private RoleMappingService roleMappingService
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(roleMappingService,
"adminClient",
mockedAdminClient);
}
I have a SpringBoot multimodule application, something like that:
core
customer1 -> depends on core
customer2 -> depends on core
I want to write integration tests for both, but I don't want to duplicate my core test code. Now I have an abstract class with SpringBootTest(classes = Customer1Application.class) and a lot of test classes, mostly testing the core functionality.
#ContextConfiguration
#SpringBootTest(classes = Customer1Application.class)
#AutoConfigureMockMvc
public abstract class AbstractSpringBootTest
{
#Autowired
protected MockMvc mockMvc;
#Autowired
protected Validator validator;
...
}
I want to check if the changes in Customer2 application break something in core functionality, so I want to run these tests with #SpringBootTest(classes = Customer2Application.class) annotation.
How is it possible to configure the application class in the annotation? Is there a way to run the tests with my other application context without manually changing the annotation or duplicating all the steps?
I don't know if it will work, but I would try removing #SpringBootTest from AbstractSpringBootTest and then defining two test classes as follows:
#SpringBootTest(classes = Customer1Application.class)
class Customer1ApplicationSpringBootTest extends AbstractSpringBootTest {}
#SpringBootTest(classes = Customer2Application.class)
class Customer2ApplicationSpringBootTest extends AbstractSpringBootTest {}
EDIT:
So I dug around Spring Boot sources and came up with this solution.
Essentially to be able to use system property or property file to configure which #SpringBootApplication is supposed to be tested you need to copy the source of class org.springframework.boot.test.context.SpringBootConfigurationFinder to your own test source root and the edit method private Class<?> scanPackage(String source) to look something like this (you do not have to use Lombok of course):
private Class<?> scanPackage(String source) {
while (!source.isEmpty()) {
val components = this.scanner.findCandidateComponents(source);
val testConfig = System.getProperties();
val testConfigFile = "test-config.properties";
val applicationClassConfigKey = "main.application.class";
try {
testConfig.load(this.getClass().getResourceAsStream("/" + testConfigFile));
} catch (IOException e) {
logger.error("Error reading configuration file: {}, using default algorithm", testConfigFile);
}
if (testConfig.containsKey(applicationClassConfigKey)) {
if (!components.isEmpty() && testConfig.containsKey(applicationClassConfigKey) && testConfig.getProperty(applicationClassConfigKey) != null) {
boolean found = false;
val configClassName = testConfig.getProperty(applicationClassConfigKey);
for (BeanDefinition component: components) {
if (configClassName.equals(component.getBeanClassName())) {
found = true;
break;
}
}
Assert.state(found,
() -> "Found multiple #SpringBootConfiguration annotated classes "
+ components + ", none of which are of type " + configClassName);
return ClassUtils.resolveClassName(
configClassName, null);
}
} else {
if (!components.isEmpty()) {
Assert.state(components.size() == 1,
() -> "Found multiple #SpringBootConfiguration annotated classes "
+ components);
return ClassUtils.resolveClassName(
components.iterator().next().getBeanClassName(), null);
}
}
source = getParentPackage(source);
}
return null;
}
Check the link for the entire project.
Did you check?
#SpringBootTest(classes = {Customer1Application.class, Customer2Application.class})
In spring boot application, I define some config properties in yaml file as below.
my.app.maxAttempts = 10
my.app.backOffDelay = 500L
And an example bean
#ConfigurationProperties(prefix = "my.app")
public class ConfigProperties {
private int maxAttempts;
private long backOffDelay;
public int getMaxAttempts() {
return maxAttempts;
}
public void setMaxAttempts(int maxAttempts) {
this.maxAttempts = maxAttempts;
}
public void setBackOffDelay(long backOffDelay) {
this.backOffDelay = backOffDelay;
}
public long getBackOffDelay() {
return backOffDelay;
}
How can I inject the values of my.app.maxAttempts and my.app.backOffdelay to Spring Retry annotation? In the example below, I want to replace the value 10 of maxAttempts and 500Lof backoff value with the corresponding references of config properties.
#Retryable(maxAttempts=10, include=TimeoutException.class, backoff=#Backoff(value = 500L))
Staring from spring-retry-1.2.0 we can use configurable properties in #Retryable annotation.
Use "maxAttemptsExpression", Refer the below code for usage,
#Retryable(maxAttemptsExpression = "#{${my.app.maxAttempts}}",
backoff = #Backoff(delayExpression = "#{${my.app. backOffDelay}}"))
It will not work if you use any version less than 1.2.0.Also you don't require any configurable property classes.
You can also use existing beans in expression attributes.
#Retryable(include = RuntimeException.class,
maxAttemptsExpression = "#{#retryProperties.getMaxAttempts()}",
backoff = #Backoff(delayExpression = "#{#retryProperties.getBackOffInitialInterval()}",
maxDelayExpression = "#{#retryProperties.getBackOffMaxInterval" + "()}",
multiplierExpression = "#{#retryProperties.getBackOffIntervalMultiplier()}"))
String perform();
#Recover
String recover(RuntimeException exception);
where
retryProperties
is your bean which holds retry related properties as in your case.
You can use Spring EL as shown below to load the properties:
#Retryable(maxAttempts="${my.app.maxAttempts}",
include=TimeoutException.class,
backoff=#Backoff(value ="${my.app.backOffDelay}"))
I was trying to find it but I found many different scenarios but not this one.
What I want to do is to add "/api/" prefix to all routes in controllers under com.myproject.api .
I want "/api/*" for all controllers under package com.myapp.api and no prefix for all controllers under com.myapp.web
Is it possible with Spring / Spring Boot ?
With Spring Boot, this worked for me :
#Configuration
#EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api",
HandlerTypePredicate.forBasePackage("com.your.package"));
}
}
If you are using springboot, you can add the following:
server.servlet.context-path=/api
to application.properties file.
I achieved the result I think you are looking for in the following way, so long as you are using MVC.
First make a configuration class that implements WebMvcRegistrations
#Configuration
public class WebMvcConfig implements WebMvcRegistrations {
#Value("${Prop.Value.String}") //"api"
private String apiPrefix;
#Value("${Prop.Package.Names}") //["com.myapp.api","Others if you like"]
private String[] prefixedPackages;
#Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new PrefixedApiRequestHandler(apiPrefix,prefixedPackages);
}
}
Then create a class that extends RequestMappingHandlerMapping
and overrides getMappingForMethod
#Log4j2
public class PrefixedApiRequestHandler extends RequestMappingHandlerMapping {
private final String prefix;
private final String[] prefixedPackages;
public PrefixedApiRequestHandler(final String prefix, final String... packages) {
super();
this.prefix = prefix;
this.prefixedPackages = packages.clone();
}
#Override
protected RequestMappingInfo getMappingForMethod(final Method method, final Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if (info == null) {
return null;
}
for (final String packageRef : prefixedPackages) {
if (handlerType.getPackageName().contains(packageRef)) {
info = createPrefixedApi().combine(info);
log.trace("Updated Prefixed Mapping " + info);
return info;
}
}
log.trace("Skipped Non-Prefixed Mapping " + info);
return info;
}
private RequestMappingInfo createPrefixedApi() {
String[] patterns = new String[prefix.length()];
for (int i = 0; i < patterns.length; i++) {
// Build the URL prefix
patterns[i] = prefix;
}
return new RequestMappingInfo(
new PatternsRequestCondition(patterns,
getUrlPathHelper(),
getPathMatcher(),
useSuffixPatternMatch(),
useTrailingSlashMatch(),
getFileExtensions()),
new RequestMethodsRequestCondition(),
new ParamsRequestCondition(),
new HeadersRequestCondition(),
new ConsumesRequestCondition(),
new ProducesRequestCondition(),
null);
}
}
You should then see /api/(ControllerMapping) for all mappings, in the specified packages only. Note: I have #RequestMapping("/") at the top of my controller.
Already answered here and here.
Add an application.properties file under src/main/resources, with the following option:
server.contextPath=/api
Check the official reference for common properties.
You should add #RequestMapping("/api") to top of every desired #Controller or #RestController class.
When both the class and method have that annotation, Spring Boot appends them while building the url. In below example the method will be bound to /api/user route.
#RequestMapping("/api")
#RestController
public class MyController {
#RequestMapping("/user")
public List<User> getUsers(){
return ...
}
}