Replace ScheduledAnnotationBeanPostProcessor in integration tests - spring

So, I've got a spring application that uses the #Scheduled annotation to do various jobs. In prod, it works great. However this feature causes us some problems when running spock integration tests-as soon as the container starts up, all our tasks are fired and it mucks up our test runs.
I'm looking for a way to turn off the scheduling functionality, but still have the container (configured with #ComponentScan) pick it up as a regular 'ol bean.
Based on some legwork I've done so far, it seems that if I could override the built-in ScheduledAnnotationBeanPostProcessor with a no-op implementation I could achieve this goal..but when I create this bean in the container(created using the #Bean("scheduledAnnotationBeanPostProcessor"-see the code section below), it just seems to be added to the list of BeanPostProcessors-which still contains the original implementation.
#Bean(name="scheduledAnnotationBeanPostProcessor")
ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor(){
return new ScheduledAnnotationBeanPostProcessor(){
#Override
public Object postProcessAfterInitialization(final Object bean, String beanName){
return bean
}
}
}
So, I guess my question is-how can I wire in a bean that will replace a built-in BeanPostProcessor? FYI I'm using Spring 3.2.4 and the application is configured 100% via Spring annotations.
thanks.

My mistake was that I didn't name the bean correctly. I ended up finding where this bean was being built(in org.springframework.scheduling.annotation.SchedulingConfiguration) and I copied it's configuration.
This method demonstrates the proper names/config:
#Bean(name=AnnotationConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
BeanPostProcessor scheduledAnnotationBeanPostProcessor(){
return new BeanPostProcessor(){
#Override
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean
}
#Override
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean
}
}
}

Related

BeanPostProcessor can't process bean RedisProperties

The verion of spring boot which I use is 2.1.5.RELEASE.
My project work with redis.For security,I encrypt my redis password.I set value in my application.properties as follows:
spring.redis.password=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
I want to decrypt before spring bean's init,so I want to change the value of RedisProperties's passowrd property.So I customize a BeanPostProcesser like this:
#Component
public class PasswordBeanPostProcessor implements BeanPostProcessor {
#Autowired
private Cryptor cryptor;
#Value("${spring.redis.password}")
private String password;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
log.info("beanName = {}",beanName);
if (bean instanceof RedisProperties) {
RedisProperties redisPropertiesBean = (RedisProperties) bean;
try {
redisPropertiesBean.setPassword(cryptor.decrypt(password));
log.debug(redisPropertiesBean.getPassword());
return redisPropertiesBean;
} catch (Exception ex) {
log.error("redis password decrypt error", ex);
throw new RuntimeException(ex);
}
}
return bean;
}
}
But this didnot work well,when I run my application ,there is no log like this print:
beanName = redisProperties
To make sure there is a bean named redisProperties in my applicationContext,I inject bean RedisProperties to another Bean.It work well ,I can get properties in RedisProperties.
To make my application run success with encrypt password,I decrypt redis's password in another's #PostConstruct method.But I think this way is not graceful,what is the right way?
who can help me,please
Ok, I understood that jasypt can't be used.
However, take a look at its source code which is fairly simple, given the fact you already work with Bean Post Processors which is a fairly advanced stuff in spring / spring boot
It's starter (autoconfig module) is available Here
So you'll see that it has spring.factories that enable some bootstrap and autoconfigurations.
Eventually you'll come to the code that actually handles the encryption
It uses a bean factory post processor - something that kicks in when the bean definitions are ready but the actual beans have not been created yet. This is a hook that micht be relevant to you. Of course the implementation will be different but the "orchestration" is the same...

#Lazy vs "BeanFactoryPostProcessor" Spring Boot

I am not good at English. And I'm a "Spring Boot" beginner. Please understand.
https://spring.io/blog/2019/03/14/lazy-initialization-in-spring-boot-2-2#enabling-lazy-initialization
I have a question in this article.
In the writing...
"It's possible to enable laser initialization in any version of Spring Boot if you're getting your hands dirty and write a BeanFactoryPostProcessor. "
I would like to know what the difference is to use the "Lazy Annotation."
Using "#Lazy Annotation"
"...dirty and write a BeanFactoryPostProcessor. "
If you just want to configure certain beans to be lazy initialised , you can annotate those beans using #Lazy.It is convenient but rather static. It cannot handle the cases that if you want to have more dynamic behaviour to configure some beans to be lazy based on certain conditions.
BeanFactoryPostProcessor provides a way to modify the bean definitions after Spring context is initialised which means that we can use it to configure which beans to be lazy programmatically .
All beans by default are not lazy. So if we want to configure all beans to be lazy in order to increase the Spring startup time , we have to manually annotate all beans with #Lazy.It is not so convenient if we have many beans. So what the articles mentioned is that in SpringBoot 2.2 will have a new feature to make all beans to be lazy by default such that so we don't need to manually annotate #Lazy for all beans. Behind the scene , it does it by registering this BeanFactoryPostProcessor which simply do the followings:
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String name : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(name);
if (beanDefinition instanceof AbstractBeanDefinition) {
Boolean lazyInit = ((AbstractBeanDefinition) beanDefinition).getLazyInit();
if (lazyInit != null && !lazyInit) {
continue;
}
}
beanDefinition.setLazyInit(true);
}
}
If BeanFactoryPostProcessor you can tweak about bean definition (NOT bean instance). One of the tweak is the laziness property:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.getBeanDefinition("YourBeanName").setLazyInit(true);
}
}
It is equivalent to set #Lazy on the initialization of YourBeanName bean

How to make globally shared objects available to freemarker templates in Spring Boot 2

What is the best way of making global shared objects available to freemarker templates when using Spring Boot 2.x, without losing Spring Boot's FreeMarker auto configuration?
The underlying mechanism for doing this is Spring Boot's FreeMakerConfigurer.setFreemarkerVariables, which in turn calls FreeMarker's Configuration.setAllSharedVariables
However, there is no obvious way (to me) to modify the FreeMarkerConfigurer that is setup by FreeMarkerServletWebConfiguration beyond the predefined freemarker properties that Spring Boot supports. (Search for "freemarker" here).
A common approach is to create a custom FreemarkerConfigurer bean, but I believe that then loses some of the auto configuration provided by spring boot, especially around the handling of various external properties.
One option that seems to work is to use a BeanPostProcessor like this:
public class CustomFreeMarkerConfig implements BeanPostProcessor {
Object sharedWithAllFreeMarkerTemplatesObj = new Object();
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FreeMarkerConfigurer) {
FreeMarkerConfigurer configurer = (FreeMarkerConfigurer) bean;
Map<String, Object> sharedVariables = new HashMap<>();
sharedVariables.put("obj", sharedWithAllFreeMarkerTemplatesObj);
configurer.setFreemarkerVariables(sharedVariables);
}
return bean;
}
}
It seems like there should be a cleaner way of doing it, perhaps by somehow extending or configuring FreeMarkerConfigurationFactory, but I haven't been able to find it.
I found a solution from spring git
Spring Boot 2.0 breaks the solution provided by #wo8335224, as FreeMarkerWebConfiguration is replaced by FreeMarkerServletWebConfiguration, which is unfortunately package-private and thus cannot be subclassed.
A currently working solution is to configure freemarker.template.Configuration bean:
#Configuration
public class FreemarkerConfig {
public FreemarkerConfig(freemarker.template.Configuration configuration) throws TemplateModelException {
configuration.setSharedVariable("name", "whatever type of value");
}
}
Internally FreeMarkerConfigurer#setFreemarkerVariables delegates its work to freemarker.template.Configuration#setAllSharedVariables.

ClassBridge with DAO class injected

I have a Hibernate Search ClassBridge where I want to use #Inject to inject a Spring 4.1 managed DAO/Service class. I have annotated the ClassBridge with #Configurable. I noticed that Spring 4.2 adds some additional lifecycle methods that might do the trick, but I'm on Spring 4.1
The goal of this is to store a custom field into the index document based on a query result.
However, since the DAO, depends on the SessionFactory getting initialized, it doesn't get injected because it doesn't exist yet when the #Configurable bean gets processed.
Any suggestions on how to achieve this?
You might try to create a custom field bridge provider, which could get hold of the Spring application context through some static method. When provideFieldBridge() is called you may return a Spring-ified instance of that from the application context, assuming the timing is better and the DAO bean is available by then.
Not sure whether it'd fly, but it may be worth trying.
Hibernate Search 5.8.0 includes support for bean injection. You can see the issue https://hibernate.atlassian.net/browse/HSEARCH-1316.
However I couldn't make it work in my application and I had implemented a workaround.
I have created an application context provider to obtain the Spring application context.
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
ApplicationContextProvider.context = context;
}
}
I have added it to the configuration class.
#Configuration
public class RootConfig {
#Bean
public ApplicationContextProvider applicationContextProvider() {
return new ApplicationContextProvider();
}
}
Finally I have used it in a bridge to retrieve the spring beans.
public class AttachmentTikaBridge extends TikaBridge {
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
// get service bean from the application context provider (to be replaced when HS bridges support beans injection)
ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
ExampleService exampleService = applicationContext.getBean(ExampleService .class);
// use exampleService ...
super.set(name, content, document, luceneOptions);
}
}
I think this workaround it's quite simple in comparision with other solutions and it doesn't have any big side effect except the bean injection happens in runtime.

Setting up Custom HandlerExceptionResolver in Spring MVC

I am trying to override WebMvcConfigurerAdapter.configureHandlerExceptionResolvers() and provide my own ExceptionHandlerExceptionResolver to Spring MVC. The motive behind this is to provide Custom Content Negotiation in such a way that if the user requests for any garbage in "Accept" header, I can return him a JSON response with "media not supported exception". I was partially able to acheive the configuration using the bellow setup.
#Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(new ErrorContentNegotiation());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = exceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setContentNegotiationManager(contentNegotiationManager);
exceptionHandlerExceptionResolver.afterPropertiesSet();
exceptionResolvers.add(exceptionHandlerExceptionResolver);
}
#Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(getHttpMessageConverter());
ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
exceptionHandlerExceptionResolver.setMessageConverters(messageConverters);
return exceptionHandlerExceptionResolver;
}
public class ErrorContentNegotiationStrategy implements ContentNegotiationStrategy {
#Override
public List<MediaType> resolveMediaTypes(final NativeWebRequest webRequest) {
return Lists.newArrayList(Globals.JSON);
}
}
I am getting this exception when the spring starts up.
No qualifying bean of type [org.springframework.web.accept.ContentNegotiationStrategy] is defined: expected single matching bean but found 2: errorContentNegotiationStrategy,mvcContentNegotiationManager
Doesn't work when I add a #Qualifier annotation to my ErrorContentNegotiationStrategy class and give it a unique name. Throws the same Exception.
If I remove #Compoenent annotation and leave the code as is, then ErrorContentNegotiationStrategy() method in ErrorContentNegotiaionStrategy is not getting called.
Did anyone face this issue ?
Add the #Primary annotation to your ErrorContentNegotiationStrategy class:
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
This should at least solve the exception during startup.
After debugging the issue, I found that I was trying to load 2 beans of same type ( which is what the error message says. One of the bean was actual implementation and other one was a Mock for test case. Since both resided in the same package #component scanned the base package and couldn't decide which one to load. I resolved the issue by using #profile, which helps in loading beans based on the profile you load. I used 2 profiles, one for testing and one for development.

Resources