BeanPostProcessor can't process bean RedisProperties - spring

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...

Related

Customize RabbitMQ message deserialization in Spring Boot

I have a hierarchy of messages. The topmost message in the hierarchy is defined with #JsonTypeInfo and #JsonSubTypes. The class is not under my control. I extended the hierarchy with my own message and to allow Jackson to deserialize my message, customized ObjectMapper. By debugging, I figured out that I need to register subtypes to the "jacksonObjectMapper" bean. So my code is as follows:
#Component
public class JacksonConfig implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("jacksonObjectMapper")) {
((ObjectMapper) bean).registerSubtypes(new NamedType(ExtendedMessage.class, "ExtendedMessage"));
}
return bean;
}
}
The solution works, but I'm afraid it's a workaround and it won't work after Spring update (e.g. the bean name changes). Is there a documented way to customize the ObjectMapper that is used to deserialize messages?
Construct the deserializer yourself and pass it into the consumer factory, via a constructor or setter.
https://docs.spring.io/spring-kafka/docs/current/reference/html/#prog-json
If you are using Boot's auto-configured factory, use something like this
#Bean
JsonDeserializer deser(ConsumerFactory<Object, Object> cf) {
...
cf.setDeserializer(...);
}

Delaying Dependency Injection in Spring

I'm writing an app that talks to one database, obtains credentials for other databases, and connects to the others. It does this using a DataSource and EntityManagerFactory constructed at runtime.
If I want to use Spring Data Repositories, I think I'd need to Autowire them, and therefore they must be Spring Beans.
How can I use Spring Data if I don't have a constructed DataSource until after I run a query against the first database?
I believe that conditional bean creation is your answer. check here.
Also, you have to get the bean after you make sure the conditions are met. check here.
#Component
public class RuntimeBeanBuilder {
#Autowired
private ApplicationContext applicationContext;
public MyObject load(String beanName, MyObject myObject) {
ConfigurableApplicationContext configContext = (ConfigurableApplicationContext) applicationContext;
SingletonBeanRegistry beanRegistry = configContext.getBeanFactory();
if (beanRegistry.containsSingleton(beanName)) {
return beanRegistry.getSingleton(beanName);
} else {
beanRegistry.registerSingleton(beanName, myObject);
return beanRegistry.getSingleton(beanName);
}
}
}
#Service
public MyService{
//inject your builder and create or load beans
#Autowired
private RuntimeBeanBuilder builder;
//do something
}
So, define a bean for your Spring Data Repository and set its condition to be met when the other database credentials are fetched.
And then, reloading the bean using the RuntimeBeanBuilder in your service will get you the bean because now its condition is met.

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.

Camel HeaderFilterStrategy Bean Registration in Spring

I am having difficulty with Spring-Camel getting a HeaderFilterStrategy class registered as a Bean so it can be found by the Camel Route. My attempts to annotate the HeaderFilterStrategy custom class seem futile... so how do I register this thing so it gets found at run time?
I have a camel application with a route utilizing a custom HeaderFilterStrategy
The Strategy Class looks like :
public class HeaderFilter implements HeaderFilterStrategy {
#Override
public boolean applyFilterToCamelHeaders(String s, Object o, Exchange exchange) {
return false;
}
#Override
public boolean applyFilterToExternalHeaders(String s, Object o, Exchange exchange) {
return true;
}
}
I register it with camel using a simple registry:
SimpleRegistry registry = new SimpleRegistry();
registry.put("HeaderFilter" ,new HeaderFilter());
.
.
final CamelContext ctx = new DefaultCamelContext(registry);
And I reference it in my Route in
.to("https://myhost/endpoint&headerFilterStrategy=#HeaderFilter")
And all like Ralphy on Christmas night with his trusty Red Rider BB Gun, all is right with the world.
So, now I am trying to take this pure camel app and put it under Spring. I make sure all the appropriate Camel, and Spring-Camel and Spring things are imported.. However, when I attempt to annotate my HeaderStrategy as a Bean for Spring and it fails:
#Component
public class HeaderFilter implements HeaderFilterStrategy {
#Bean
#Override
public boolean applyFilterToCamelHeaders(String s, Object o, Exchange exchange) {
return false;
}
#Override
public boolean applyFilterToExternalHeaders(String s, Object o, Exchange exchange) {
return true;
}
}
Now when I do this, the IDE basically tells me it can't autowire any of the parameters in the method calls becaue there is more than one bean of type String or Object and no beans of type Exchange found..
At Runtime, Camel does attempt to interpret the route, but throws a failure with "No Qualifying bean type of "java.lang.String" available, since this is the first parameter in the method call...
So, How do I get this thing to be able register with annotations correctly? Or manually register this bean without it attempting to autowire? All I need is the class to be registered as a BEAN so it can be found by camel at runtime... Or at least that is what I understand needs to happen... so how the heck to I do this?
I figured it out, I was not properly using the annotationsI added the following to my AppConfig class:
#Configuration
public class AppConfig{
#Bean
public HeaderFilter HeaderFilter(){
return new HeaderFilter();
}
}
I am not sure if the suggestion above will work, but this clearly does.

Replace ScheduledAnnotationBeanPostProcessor in integration tests

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
}
}
}

Resources