Spring 3.1 #Cacheable - method still executed - spring

I'm trying to implement Spring 3.1 caching as explained here and here, but it doesn't seem to be working: my method is run through every time even though it is marked #cacheable. What am I doing wrong?
I've moved it into a junit test case with its own configuration file to isolate it from the rest of my application, but the problem still happens. Here are the relevant files:
Spring-test-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:config-location="classpath:ehcache.xml"/>
</beans>
ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir"/>
<cache name="cache"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
MyTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath:spring-test-servlet.xml"})
#Component
public class MyTest extends TestCase {
#Test
public void testCache1(){
for(int i = 0; i < 5; i++){
System.out.println("Calling someMethod...");
System.out.println(someMethod(0));
}
}
#Cacheable("testmethod")
private int someMethod(int val){
System.out.println("Not from cache");
return 5;
}
}
Relevant Pom entries: (spring-version = 3.1.1.RELEASE)
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
when I run the test, Spring puts out some debug messages that looks like my cache is initialized without errors
DEBUG: config.ConfigurationHelper - No CacheManagerEventListenerFactory class specified. Skipping...
DEBUG: ehcache.Cache - No BootstrapCacheLoaderFactory class specified. Skipping...
DEBUG: ehcache.Cache - CacheWriter factory not configured. Skipping...
DEBUG: config.ConfigurationHelper - No CacheExceptionHandlerFactory class specified. Skipping...
DEBUG: store.MemoryStore - Initialized net.sf.ehcache.store.MemoryStore for cache
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.data
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
DEBUG: disk.DiskStorageFactory - Matching data file missing (or empty) for index file. Deleting index file /var/folders/qg/xwdvsg6x3mx_z_rcfvq7lc0m0000gn/T/cache.index
DEBUG: disk.DiskStorageFactory - Failed to delete file cache.index
DEBUG: ehcache.Cache - Initialised cache: cache
DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured. Skipping for 'cache'.
DEBUG: config.ConfigurationHelper - CacheDecoratorFactory not configured for defaultCache. Skipping for 'cache'.
but the debug output shows no cache checks between method calls to someMethod and the print statement from inside someMethod prints every time.
Is there something I'm missing?

From http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/cache.html
In proxy mode (which is the default), only external method calls
coming in through the proxy are intercepted. This means that
self-invocation, in effect, a method within the target object calling
another method of the target object, will not lead to an actual
caching at runtime even if the invoked method is marked with
#Cacheable - considering using the aspectj mode in this case.
and
Method visibility and #Cacheable/#CachePut/#CacheEvict
When using proxies, you should apply the #Cache annotations only to
methods with public visibility.
You self-invoke someMethod in the same target object.
Your #Cacheable method is not public.

You need to define a cache that matches the name you are referencing in you annotation ("testmethod"). Create an entry in your ehcache.xml for that cache as well.

In addition to Lee Chee Kiam: Here is my solution for small projects with only marginal usage of bypassing (not annotated) method calls. The DAO is simply injected into itself as a proxy and calls it's own methods using that proxy instead of a simple method call. So #Cacheable is considered without doing complicated insturmentation.
In-code documentation is strongly advidsed, as it may look strange to colleagues. But its easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd also advice the AspectJ solution as Lee Chee Kiam did.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {
private final PersonDao _personDao;
#Autowired
public PersonDao(PersonDao personDao) {
_personDao = personDao;
}
#Cacheable(value = "defaultCache", key = "#id")
public Person findPerson(int id) {
return getSession().getPerson(id);
}
public List<Person> findPersons(int[] ids) {
List<Person> list = new ArrayList<Person>();
for (int id : ids) {
list.add(_personDao.findPerson(id));
}
return list;
}
}

Related

Spring Boot, Sleuth, OTEL, and Honeycomb

I have a scenario where I have Spring Boot integrated with OTEL and shipping to Honeycomb.io. I am trying to add an environment tag to each trace. I have created a class:
#Component
public class EnvironmentSpanProcessor implements SpanProcessor {
#Value("${ENVIRONMENT")
private String environment;
Queue<SpanData> spans = new LinkedBlockingQueue<>(50);
#Override
public void onStart(Context context, ReadWriteSpan readWriteSpan) {
readWriteSpan.setAttribute("env", environment);
}
#Override
public boolean isStartRequired() {
return false;
}
#Override
public void onEnd(ReadableSpan readableSpan) {
this.spans.add(readableSpan.toSpanData());
}
#Override
public boolean isEndRequired() {
return true;
}
}
I have set break points in this class, and they never hit on startup, even though the bean can be seen in actuator. I have put breakpoints on:
SdkTracerProvider otelTracerProvider(SpanLimits spanLimits, ObjectProvider<List<SpanProcessor>> spanProcessors,
SpanExporterCustomizer spanExporterCustomizer, ObjectProvider<List<SpanExporter>> spanExporters,
Sampler sampler, Resource resource, SpanProcessorProvider spanProcessorProvider) {
SdkTracerProviderBuilder sdkTracerProviderBuilder = SdkTracerProvider.builder().setResource(resource)
.setSampler(sampler).setSpanLimits(spanLimits);
List<SpanProcessor> processors = spanProcessors.getIfAvailable(ArrayList::new);
processors.addAll(spanExporters.getIfAvailable(ArrayList::new).stream()
.map(e -> spanProcessorProvider.toSpanProcessor(spanExporterCustomizer.customize(e)))
.collect(Collectors.toList()));
processors.forEach(sdkTracerProviderBuilder::addSpanProcessor);
return sdkTracerProviderBuilder.build();
}
in OtelAutoConfiguration and am not seeing them firing either on startup.
My pom.xml relevant section is:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-brave</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-otel-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.47.0</version>
</dependency>
And my configuration from application.yaml
sleuth:
enabled: true
web:
additional-skip-pattern: /readiness|/liveness
client.skip-pattern: /readiness
sampler:
probability: 1.0
rate: 100
propagation:
type: OT_TRACER
otel:
config:
trace-id-ratio-based: 1.0
log.exporter.enabled: true
exporter:
otlp:
endpoint: https://api.honeycomb.io
headers:
x-honeycomb-team: ${TELEMETRY_API_KEY}
x-honeycomb-dataset: app-telemetry
sleuth-span-filter:
enabled: true
resource:
enabled: true
I am getting traces, so it appears the system itself is working, however I cannot get my env tag added.
Preemptive thank you to #marcingrzejszczak for the help so far on my gist: https://gist.github.com/fpmoles/b880ccfdef2d2138169ed398e87ec396
I'm unsure why your span processor is not being picked up by Spring and being added to your list of processors being registered with the tracer provider.
An alternative way to set process consistent values, like environment, would be to set it as a resource attribute. This is more desireable because it's set once and delivered once per batch of spans sent to the configured backend (eg Honeycomb). Using a span processor adds the same attribute to every span.
This can be done in a few different ways:
If using AutoConfigure, you can set via system property or environment variable
Set directly on the resource during your otelTracerProvider method:
resource.setAttribute("environment", "${environment}");
FYI Honeycomb has OTel Java SDK & Agent distros to help simplify sending data that reduces required configuration and sets sensible defaults.

Is using hibernate type property names when using spring boot a good approach

In my current codebase, I have a RestController, and I am using hibernate with it. As of now, I am using the same hibernate configuration which one would usually use
<?xml version="1.0" encoding="UTF-8" ?>
<hibernate-configuration xmlns="http://www.hibernate.org/xsd/orm/cfg">
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
Also, in my pom.xml, I have added:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
But to get this working I had to add the attribute exclude like this :
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
Otherwise, it was throwing some error. Another issue I noticed was even though
hbm2ddl.auto is set to true, which would have created the tables in a simple hibernate application. But it's not happening now. It throws an error if the table is not there but seems to be working fine if it's there.
I changed the MySQL dialect in the hibernate.cfg.xml to :
org.hibernate.dialect.MySQL8Dialect
And now it's automatically creating the table. That's weird when I was using the same hibernate code without spring boot the earlier Dialect was working
This is the main code that starts the application:
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class SpringServerMain {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringServerMain.class, args);
}
}

Argument passed to when() is not a mock! exception thrown with Spring Boot project which doesn't have #SpringBootApplication/main class

My project is a simple spring boot application which doesn't have a main/#SpringBootApplication class. It is used as a dependency library for other modules. I am trying to write the unit tests for the classes present in this project like below and getting the below pasted error. Any quick help is much appreciated.
pom dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
As this project doesn't have main class, to get the spring application context using below configuration class.
#Configuration
public class TestServiceConfig {
#Bean
public TestService productService() {
return Mockito.mock(TestService.class);
}
#Bean
public MongoDriverService productMongo() {
return Mockito.mock(MongoDriverService.class);
}
}
Below is my test class which is throwing exception. Actual java class has a method called getPlanCode(which takes 6 arguments) and returns void. In this method mongo object is used for connecting the db so that I used #InjectMocks on service object.
public class ValidationServiceTest {
#Mock
MongoDriverService mongo;
#InjectMocks
TestService service;
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
doNothing().when(service).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
service.getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
verify(service, times(1)).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
}
}
Below is the exception
12:51:33.829 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#45b4c3a9 testClass = DefaultMedicareBFTAccumsValidationServiceTest, testInstance = com.anthem.rxsmart.service.standalone.batchvalidation.DefaultMedicareBFTAccumsValidationServiceTest#14dda234, testMethod = getPlanCodeTest#DValidationServiceTest, testException = org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
service is not a mock since you are using #InjectMocks ( assume you are using #RunWith(MockitoRunner.class) or #ExtendWith but you are hiding that for whatever reasons).
What #InjectMocks does, is create of a new instance of TestService and literally inject mocks into it (mocked required dependencies). So service is a real thing, not a mock
IMO this test makes not sense as you are suppose to test your implementation of singular entity contract, not to test mocks...
Your test case and assertions are pointless as it is like "call method A and check if I just called method A" while you should check and validate eg return value of a call, or if some methods of mocks have been called eg if Mongo was queried with proper arguments. I just hope it is a really bad example, not real test scenario
Also test setup is wrong as you show us that you want to use #Configuration class with #Bean but then you are using #Mock in the test which will create brand new mocks for you. In other words - that config is not used at all
Posting this answer just for the developers who are in same understanding state.
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
service = new ValidationService(mongo);
Mockito.when(mongo.aggregateIterable("test", pipeline)).thenReturn(tierFilterDocs);
service.getPlanCode("", "", null, batchFile, null, "");
verify(mongo, times(1)).aggregateIterable("test", pipeline);
}
I have updated my test case so it solves the purpose now. Now no need of the Configuration file as I am mocking the object in test class itself.

How to configure Jcache with Ecache as Provider in Spring application-context.xml?

Spring documentation provides below information.
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
p:cache-manager-ref="jCacheManager"/>
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>
I want to know exactly how to configure this jcacheManager bean (with EhCache as provider) in spring application context xml.
I have already configured dependency, as below, in pom.xml which is fine.
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>jcache</artifactId>
<version>1.0.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
It really depends how you want to configure it. If you're using Spring Boot 1.3, it will be automatically created for you. Maybe you could have a look to the source of JCacheCacheConfiguration?
You can retrieve the default javax.cache.CacheManager via Caching.getCachingProvider().getCacheManager()
It's not convenient for us to integrate Ehache3.x with Spring4.x now. Spring boot does it, and it rewrites some codes:
<bean id="cacheManager"
class="org.springframework.cache.jcache.JCacheCacheManager"
in Spring boot, it's:
#Bean
public JCacheCacheManager cacheManager(CacheManager jCacheCacheManager) {
return new JCacheCacheManager(jCacheCacheManager);
}
and it needs a javax.cache.CacheManager instance,
<!-- JSR-107 cache manager setup -->
<bean id="jCacheManager" .../>
Ehcache have no in-depth introduction for us.
Spring boot does like:
#Bean
#ConditionalOnMissingBean
public CacheManager jCacheCacheManager() throws IOException {
CacheManager jCacheCacheManager = createCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!CollectionUtils.isEmpty(cacheNames)) {
for (String cacheName : cacheNames) {
jCacheCacheManager.createCache(cacheName, getDefaultCacheConfiguration());
}
}
customize(jCacheCacheManager);
return jCacheCacheManager;
}
It's a normal operation to create javax.cache.CacheManager just follows the Ehcache document.

How do I get my Spring Aspect to kick in for a #Valid annotation on a service method?

We're using Spring 3.2.11.RELEASE and Maven 3.0.3. I'm trying to set up validation of a parameter being passed into a service method. The method is below. Notice the #Valid annotation.
package org.mainco.subco.mypck.service;
#Service
#RemoteProxy
#Transactional
public class MypckServiceImpl implements MypckService {
#RemoteMethod
#Override
public String myMethod(#Valid final MyObjectDto request) {
// ...
}
}
Here is the aspect I have set up to help validate the object:
#Aspect
#Component
public class MyObjectValidatingAspect extends AbstractDWRAspectValidator<MyObjectDto>
{
#Before("execution(* org.mainco.subco.mypck.service.MypckService.myMethod(..))")
public void validateBefore(JoinPoint jp)
{
errors = new ArrayList<String>();
final MyObjectDto request = validate(jp);
validateMyObject(request);
throwErrors();
} // validateBefore
This is in included in my application context file:
<global-method-security pre-post-annotations="enabled">
</global-method-security>
<aop:aspectj-autoproxy/>
And this is what I've included in the Maven pom.xml file:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version>
</dependency>
Unfortunately when the method is invoked, the aspectj's validateBefore is never called. What else do I need to do so that this gets invoked?
Since Spring 3.1 there is the MethodValidationInterceptor which basically does what you want to achieve yourself. To have this interceptor applied the only thing you need to do is to register a MethodValidationPostProcessor in your application context.
By default it will check for the #Validated annotation from Spring but you can instruct it to scan for the #Valid annotation.
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validatedAnnotationType" value="javax.validation.Valid" />
<property name="validator" ref="refToYOurLocalValidatorFactoryBean" />
</bean>
If you don't specify a validator the default JSR-303 validator mechanism will be used (or the more hibernate specific one if that is available). But I can imagine you want to reuse the already configured instance.

Resources