Spring application with EmptyInterceptor no longer calling `onSave` or `onFlushDirty` in tests - spring

We have a Spring Boot 2.4.5 application that uses a custom EmptyInterceptor. This interceptor is working in both the application and the tests where both the onSave and onFlushDirty methods are being executed properly.
We just updated to Boot which also upgrades Hibernate and JUnit along with many other libraries (details below). With this upgrade, our application still works as expected but for some reason, the interceptor's onSave and onFlushDirty methods are not being executed. We do see other interceptor methods being executed so we can assume that the interceptor is properly registered.
I've been combing through release notes for Spring, Spring JPA, and Hibernate but can't find anything that appears to be related to how this works. Is there something we're missing as part of the upgrade that may be related to changing the way the interceptor works?
Technical details:
Our custom interceptor:
#Component
public class AuditedEntityInterceptor extends EmptyInterceptor implements BeanFactoryAware
.. which is then wired into the application by:
#Configuration
#Slf4j
public class HibernateInterceptorConfig implements HibernatePropertiesCustomizer {
#Autowired
private AuditedEntityInterceptor auditedEntityInterceptor;
//...
#Override
public void customize(Map<String, Object> hibernateProperties)
{
hibernateProperties.put("hibernate.session_factory.interceptor", auditedEntityInterceptor);
//...
}
}
I'm trying to not overload this question with too much detail but it's probably important how our integration tests are configured:
// Each single integration test is structured like:
#Transactional
public class SomethingSomethingServiceIT extends AppSharedIT
#SpringBootTest(classes = { AppSharedItConfig.class })
public abstract class AppSharedIT extends AppSupportHsqlIT
#SpringBootTest(classes = AppSupportItConfig.class)
#Sql(scripts = {
// ... a bunch of SQL scripts to initialize reference data
})
#Transactional
#ActiveProfiles(profiles = { "test" })
public abstract class AppHsqlIT implements BeanFactoryAware
#Configuration
#EnableAutoConfiguration(exclude = { QuartzAutoConfiguration.class })
#Import({ AppSupportConfig.class })
#ComponentScan(basePackages = {
//...
})
public class AppSupportItConfig
#Configuration
#EnableTransactionManagement
#EntityScan({
//...
})
#EnableJpaRepositories({
//...
})
#ComponentScan(
basePackages = {
//...
})
#Import(value = { //...
HibernateInterceptorConfig.class })
public class AppSupportConfig
Among many other libraries, I've been focusing on the following version changes:
junit-jupiter: 5.7.1 -> 5.8.2
spring-boot: 2.4.5 -> 2.7.4
hibernate-core: 5.4.30.Final -> 5.6.11.Final

Related

Spring boot #Inject proxy resolves to null

I'm refactoring an existing application to use Spring Boot. The issues I've faced here are generally of the type "why is this not working anymore".
I have three packages
- nl.myproject.boot
- nl.myproject
- nl.myproject.rest
My current problem is that all #Services that I #Inject in a #RESTController resolve to null when a method is called on them.
The service and dao are part of the nl.myproject package and the reason it's not nl.myproject.core is a legacy issue.
A related issue is that my #Configuration components don't seem to be loaded through #ComponentScan and I have to import them manually. I also had to exclude Test configuration to prevent Test configs from being loaded, which also seemed weird.
Internal calls from the service layer during start up, such as data preparation works normally. Any such manager is also #Injected. This is just to say that any of the typical injection mistakes such as manual instantiation or injecting a class instead of an interface don't apply.
I'd also be grateful for debugging tips. My Java has gotten a little rusty.
#EnableAutoConfiguration
#ComponentScan(basePackages= {
"nl.myproject",
"nl.myproject.boot",
"nl.myproject.dao",
"nl.myproject.service",
"nl.myproject.webapp"},
excludeFilters= {
#ComponentScan.Filter(type=FilterType.REGEX,pattern={".*Test.*"}),
#ComponentScan.Filter(type=FilterType.REGEX,pattern={".*AppConfig"})
}
)
#Configuration
#EnableConfigurationProperties
#Import({
JPAConfig.class,
RestConfig.class,
BootConfig.class
})
public class Startup {
public static void main(String[] args) throws Exception {
SpringApplication.run(Startup.class, args);
}
}
#RestController
#RequestMapping(value="/json/tags")
public class JsonTagController extends JsonBaseController {
#Inject
TagManager tagMgr;
public interface TagManager extends BaseManager<Tag,Long> {
[...]
}
#Service("tagManager")
public class TagManagerImpl extends BaseManagerImpl<Tag, Long> implements
TagManager {
#Inject
TagDao dao;
[...]
#Inject is a annotation specified by JSR-330 (standard) whereas #Autowired is annotation specified by Spring.
They just do the same dependency injection. You can both of them in the same code.
Just the modification (separation of the concerns) you need :
public interface TagManager {
[...]
}
#Service
public class TagManagerImpl implements TagManager {
#Inject
private TagDao dao;
// inject that service rather than extending
#Inject
private BaseManager<Tag,Long> baseManager;
}
public interface BaseManager<Tag,Long> {
[...]
}
#Service
public class BaseManagerImpl<Tag,Long> implements BaseManager<Tag,Long> {
....
}
Just one thing you do for checking, just modify to basePackages= {"nl.myproject"} - just provide only base package, that's enough for spring to scan the components in every package.
Hope this may help :)

Disable Spring TestExecutionListeners from inherited class

I have the following scenario:
#TestExecutionListeners(BasicListener.class)
public #interface AnnotationOne {
}
#AnnotationOne
public class TestClassOne extends AbstractJUnit4SpringContextTests {
...
}
And since:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners({ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class})
public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
...
}
For some reason, the TestExecutionListeners don't get merged. So, is there a way to disable the TestExecutionListeners defined by the parent class?
My main concern is to have the BasicListener working.
Thanks
The behavior you are experiencing is due to a bug in core Spring: an inherited annotation will shadow a locally declared composed annotation.
In your use case, #TestExecutionListeners is the inherited annotation, and #AnnotationOne is the locally declared composed annotation.
This bug has been fixed in Spring Framework 4.2 RC1. For details, see SPR-12749.
If you'd like a work-around before 4.2 is released, the following should work for you, but you will not be able to use your custom #AnnotationOne annotation if your class extends from AbstractJUnit4SpringContextTests:
#TestExecutionListeners(listeners = BasicListener.class, inheritListeners = false)
public class TestClassOne extends AbstractJUnit4SpringContextTests {
// ...
}
Generally speaking, AbstractJUnit4SpringContextTests adds very little value. So you're probably better off with the following approach:
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners(BasicListener.class)
public class TestClassOne {
// ...
}
Or even:
#RunWith(SpringJUnit4ClassRunner.class)
#AnnotationOne
public class TestClassOne {
// ...
}
Regards,
Sam (author of the Spring TestContext Framework)

Custom platform transaction manager in spring

I'm trying to implement custom transactional cache in a spring boot application. I've created my own implementation of AbstractPlatformTransactionManager and some unit tests, which show transactions are working as expected. However the real application ignores my transaction manager - it`s methods are never called. What I'm doing wrong? Thank you.
Transaction manager implementation:
#Component
public class CacheTransactionManager extends AbstractPlatformTransactionManager{
#Override
protected Object doGetTransaction() throws TransactionException {
...
}
...
}
Cache transaction configuration:
#Configuration
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class CacheTransactionConfiguration {
#Bean(name = "cacheTransactionManager")
public PlatformTransactionManager cacheTransactionManager() {
return new CacheTransactionManager();
}
}
Custom transactional annotation (I've tried also without this, but no difference):
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(value = "cacheTransactionManager", rollbackFor = Exception.class)
public #interface CacheTransactional {
}
Cache service:
#Component
public class CacheService {
#CacheTransactional
public void add(Object o){
...
}
}
Working JUnit test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestApplication.class)
#Configuration
#EntityScan(...)
#IntegrationTest
#TransactionConfiguration(defaultRollback = false)
public class CacheTransactionManagerTest {
#Autowired
private CacheService cacheService;
#Test
#CacheTransactional
public void transactionTest(){
cacheService.add(new Object());
}
}
Not working wicket application main class (ignores cacheTransactionManager):
#Configuration("MyApplication")
#EnableAutoConfiguration
#EntityScan(...)
#EnableJpaRepositories(...)
#EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
#ComponentScan(...)
#ImportResource({...})
public class MyApplication extends AuthenticatedWebApplication {
...
}
My env: Java 8, Spring Boot 1.2.1, Spring 4.1.4, Spring data JPA 1.7.2, Hibernate 4.3.7, Apache Tomcat 8.0.15, Wicket 6.17.0
I found out some new facts:
when I remove AdviceMode.ASPECTJ from #EnableTransactionManagement on CacheTransactionConfiguration, transactions begin to work, but propagation of transaction is ignored - nested call from one #CacheTransactional method to another #CacheTransactional methods always creates new transaction. Same behavior in JUnit test and real application.
when AdviceMode.ASPECTJ is on CacheTransactionConfiguration setted, but I remove #CacheTransactional annotation from junit test, transaction stops working also in juint (in test body is a #CacheTransaction method called, so there should be a transaction created).
application log contains this entry:
o.s.c.a.ConfigurationClassBeanDefinitionReader isOverriddenByExistingDefinition:290 - Skipping bean definition for [BeanMethod:name=cacheTransactionManager,declaringClass=x.y.z.CacheTransactionConfiguration]: a definition for bean 'cacheTransactionManager' already exists. This top-level bean definition is considered as an override.
So I can get this working, but without propagation...
For propagation, you need to tell Spring's #Transactional what propagation mode to apply. You can define several tx annotations, each inherit from #Transactional, but with a different propagation mode.

Spring junit error single matching bean but found 2

I have a test class, and have created a java config class to use with this class.. But im having issues as other tests seem to throw up found two instances of bean in configuration...
my test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=TestConfiguration.class)
public class ListenerTest {
// various tests.. just basic stuff..
}
#Configuration
public class TestConfiguration {
#Bean
public MyListsner ListenerImpl() {
return Mockito.mock(MyListsner .class);
}
}
Now for this test class passes fine when i use a mock as above. My other test classes seem to fail and they are as follows:
test class which fails...
This class throws the error
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=GeneratorTestConfiguration.class)
#Transactional
public class GeneratorTest {
// various tests
}
Main config
#Configuration
#Import({
BaseConfiguration.class,
CoreBaseConfiguration.class
})
#Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
#EnableTransactionManagement(proxyTargetClass = true)
#EnableJpaRepositories(basePackages={
"com.persistence.repository"
})
#ComponentScan({ // where the components are
"com.tests"
})
public class GeneratorTestConfiguration {
}
I dont know why, when i add listener mock to the above class ListenerTest, the toher tests fail, as im being specific in those classes to use the relevant configuration when autowiring.
Seems the bean was defined twice.

Spring #Configuration bean created in #Bean method not enhanced by CGLIB

I'm trying to create a MainConfig that imports another Config by using an #Bean method instead of #Import like this :
#Configuration
public class MainConfig {
#Bean
public Service service() {
return new Service(infrastructureConfig().database());
}
#Bean
public OtherService otherService() {
return new OtherService(infrastructureConfig().database());
}
#Bean
public InfrastructureConfig intrastructureConfig() {
return new InfrastructureConfig();
}
}
#Configuration
public class InfrastructureConfig {
#Bean
public Database database() {
return new Database();
}
...
}
When using this technique, the Database is created twice because Spring doesn't seem to consider the #Configuration annotation on InfrastructureConfig. When using #Import, it works fine.
I don't want to use #Import because I want to mock my InfrastructureConfig like this :
#Configuration
public class TestConfig extends MainConfig {
#Override
public InfrastructureConfig infrastructureConfig() {
return mock(InfrastructureConfig.class);
}
}
Am I missing something or it is not supported ?
Thanks
When I first tried out Spring Java configuration I think I made the same assumption and was surprised when it didn't work.
I'm not sure this is the neatest way of solving this but I have used the following approach successfully.
To include that #Configuration class you can add this annotation to your MainConfig:
#ComponentScan(basePackages = "org.foo", includeFilters = {#Filter(filterType = ANNOTATION, value = CONFIGURATION)}, excludeFilters = {#Filter(filterType = ASSIGNABLE_TYPE, value = MainConfig)})
Since #Configuration classes are also candidates for component scanning this allows you to scan for all classes annotated with #Configuration. Since you're putting this annotation on MainConfig you need to exclude that with the ASSIGNABLE_TYPE filter since you'll get a circular reference.
I opened a Spring ticket SpringSource JIRA and they said that it is a known limitation and it is working as designed.

Resources