About register "BeanPostProcessor" use"BeanFactoryPostProcessor" - spring

When I use "BeanFactoryPostProcessor" to register "BeanPostProcessor" and at the same time use AutoConfiguration to wire up my defined "BeanFactoryPostProcessor", I found that my "BeanPostProcessor" execution order is after "#PostConstruct", I am curious why, and please what is good way to deal with
The key code is as follows
JDK17\SpringBoot3.0.0-M3
#RequiredArgsConstructor
public class MapstructFactory implements BeanPostProcessor {
private final MapstructRegistry registry;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Converter) {
registry.addConverter((Converter<?, ?>) bean);
}
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
}
#RequiredArgsConstructor
public class MapstructFactoryRegister implements BeanFactoryPostProcessor {
private final MapstructRegistry registry;
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.addBeanPostProcessor(new MapstructFactory(registry));
}
}
#AutoConfiguration
public class MapstructAutoConfiguration {
#Bean
public MapstructFactoryRegister mapstructFactoryRegister(MapstructRegistry registry) {
return new MapstructFactoryRegister(registry);
}
}

Related

How to use a custom deserializer with Spring #RequestParam

I have an interface that's not aware of its implementations (module-wise):
public interface SomeInterface {
}
and an enum implementing it:
public enum SomeInterfaceImpl {
}
I have a #RestController:
#RequestMapping(method = RequestMethod.GET)
public List<SomeClass> find(#RequestParam(value = "key", required = false) SomeInterface someInt,
HttpServletResponse response){
}
Although Jackson is aware that it should deserialize SomeInterface as SomeInterfaceImpl as follows:
public class DefaultSomeInterfaceDeserializer extends JsonDeserializer<SomeInterface> {
#Override
public SomeInterface deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return parser.readValuesAs(SomeInterfaceImpl.class).next();
}
}
and:
#Configuration
public class MyJacksonConfiguration implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof Jackson2ObjectMapperBuilder){
Jackson2ObjectMapperBuilder builder = (Jackson2ObjectMapperBuilder) bean;
builder.deserializerByType(SomeInterface.class, new DefaultSomeInterfaceDeserializer());
}
return bean;
}
}
and successfully serializes and deserializes SomeInterface as SomeInterfaceImpl in the #RequestBody, it doesn't seem to have any effect on mapping SomeInterface to SomeInterfaceImpl with #RequestParam. How do I overcome this?
Having a Converter in the application context as M.Denium suggested does the job:
#Component
public class SomeInterfaceConverter implements Converter<String, SomeInterface> {
#Override
public SomeInterface convert(String value) {
return new SomeInterfaceImpl(value);
}
}

#Before #PostConstruct Spring AOP ineterception

I'm trying to write an aspect that can intercept PostConstruct methods. I've looked at related questions on SO and others, and following them, this is what I have so far:
The Spring configuration
#Configuration
#EnableAspectJAutoProxy
#EnableLoadTimeWeaving
#...//other config annotations
public class WebConfiguration {
#Bean
public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
return new CommonAnnotationBeanPostProcessor();
}
... // etc
}
The annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface Secured {
Permission[] permissions() default {};
}
The bean
#Component
#Scope("request")
public class SomeWebBean {
#Secured(permissions = Permission.SOME_PERMISSION)
#PostConstruct
public void secure() {
... // some stuff
}
}
The aspect
#Component
#Aspect
public class SecuredAspect {
#Before("#annotation(secured)")
public void doAccessCheck(Secured secured) {
... // actually do the access check
}
}
If I call someWebBean.secure() from a page, then the aspect is invoked. However, it is not invoked on bean creation.
So as a note to future me - this absolutely cannot be done in this way using Spring AOP.
However, the same effect can be achieved by implementing a BeanPostProcessor as below:
public class SecureBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Secured secured = bean.getClass().getAnnotation(Secured.class);
if (secured != null) {
// do your security test here, throw an exception, return false, however you like
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
You can extend CommonAnnotationBeanPostProcessor and override postProcessBeforeInitialization(Object bean, String beanName)
Then register replace the original CommonAnnotationBeanPostProcessor with a BeanFactoryPostProcessor .
public class InitCommonAnnotationBeanPostProcessor extends CommonAnnotationBeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return super.postProcessBeforeInitialization(bean, beanName);
}
}
public class InitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
RootBeanDefinition def = new RootBeanDefinition(InitCommonAnnotationBeanPostProcessor.class);
def.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME, def);
}
}
#Configuration
public class InitialisationMonitoringConfig {
public static final String BEAN_INIT_MONITOR = "BEAN_INIT_MONITOR";
#Bean
public static InitBeanFactoryPostProcessor initBeanFactoryPostProcessor() {
return new InitBeanFactoryPostProcessor();
}
}
This is ugly, but I had to do that to analyse startup times in dev environment.
Maybe it's enough to just declare InitCommonAnnotationBeanPostProcessor as a bean, I didn't tried.

WebSocketMessageBrokerStats - how to set loggingPeriod

How to set loggingPeriod in WebSocketMessageBrokerStats to decrease value (default is 30')
WebSocketMessageBrokerStats is loaded by #Bean in WebSocketMessageBrokerConfigurationSupport
version : Spring 4.2.0.RELEASE
My current Config :
#Configuration
#EnableWebSocketMessageBroker
#EnableScheduling
public class AppWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/entry")
.setAllowedOrigins("*")
.withSockJS()
.setDisconnectDelay(10000);
}
}
According to the WebSocketMessageBrokerStats javadoc:
This class is declared as a Spring bean by the above configuration with the name "webSocketMessageBrokerStats"
So you can add this to your configuration class:
#Autowired
private WebSocketMessageBrokerStats webSocketMessageBrokerStats;
#PostConstruct
public void init() {
webSocketMessageBrokerStats.setLoggingPeriod(10 * 1000); // desired time in millis
}
If you want to setup the logging period before Spring has put the bean into service, you can use a PostBeanProcessor:
#Bean
public BeanPostProcessor beanPostProcessor() {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof WebSocketMessageBrokerStats) {
WebSocketMessageBrokerStats webSocketMessageBrokerStats = (WebSocketMessageBrokerStats) bean;
webSocketMessageBrokerStats.setLoggingPeriod(30 * 1000); // your customization
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
};
}
With WebSocketMessageBrokerStats it is not much of a difference to Toyo's way. However if you have a bean which won't let you change a setting after initialization, this way will always work.

why BeanDefinition.getPropertyValues return an empty list

I configured spring with AnnotationConfigApplicationContext. While I called BeanDefinition.getPropertyValues() in a BeanFactoryPostProcessor implementation , I got an empty list . But if i configured spring with ClasspathXmlApplicationContext, it return the right value. Does anyone know the reason?
Here is my codes:
#Configuration
public class AppConfig {
#Bean
public BeanFactoryPostProcessorTest beanFactoryPostProcessorTest(){
return new BeanFactoryPostProcessorTest();
}
#Bean
public Person person(){
Person person = new Person();
person.setName("name");
person.setAge(18);
return person;
}
}
public class BeanFactoryPostProcessorTest implements BeanFactoryPostProcessor{
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition bd = beanFactory.getBeanDefinition("person");
MutablePropertyValues propertyValues = bd.getPropertyValues();
if(propertyValues.contains("name")){
propertyValues.addPropertyValue("name","zhangfei");
}
System.out.println("Factory modfy zhangfei");
}
}
public class MainClass {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
/*ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");*/
Person person = context.getBean(Person.class);
System.out.println(person.getName());
}
}
Thanks very much.
You have got a null list simply because the BeanFactoryPostProcessor is called before the instances are created and assigned values. They will be called before the instance creation.
I have done the same thing which you were trying to do using a different interface. Use the BeanPostProcessor interface.
public class BeanPostProcessorTest implements BeanPostProcessor{
#Override
public Object postProcessAfterInitialization(Object bean, String name)
throws BeansException {
if("person".equals(name)){
Person person =((Person)bean);
if("name".equals(person.getName())){
person.setName("zhangfei");
}
return person;
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object arg0, String arg1)
throws BeansException {
return arg0;
}
}

What is the Spring DI equivalent of CDI's InjectionPoint?

I would like to create a Spring's bean producer method which is aware who invoked it, so I've started with the following code:
#Configuration
public class LoggerProvider {
#Bean
#Scope("prototype")
public Logger produceLogger() {
// get known WHAT bean/component invoked this producer
Class<?> clazz = ...
return LoggerFactory.getLogger(clazz);
}
}
How can I get the information who wants to get the bean injected?
I'm looking for some equivalent of CDI's InjectionPoint in Spring world.
Spring 4.3.0 enables InjectionPoint and DependencyDescriptor parameters for bean producing methods:
#Configuration
public class LoggerProvider {
#Bean
#Scope("prototype")
public Logger produceLogger(InjectionPoint injectionPoint) {
Class<?> clazz = injectionPoint.getMember().getDeclaringClass();
return LoggerFactory.getLogger(clazz);
}
}
By the way, the issue for this feature SPR-14033 links to a comment on a blog post which links to this question.
As far as I know, Spring does not have such a concept.
Then only thing that is aware of the point that is processed is a BeanPostProcessor.
Example:
#Target(PARAMETER)
#Retention(RUNTIME)
#Documented
public #interface Logger {}
public class LoggerInjectBeanPostProcessor implements BeanPostProcessor {
public Logger produceLogger() {
// get known WHAT bean/component invoked this producer
Class<?> clazz = ...
return LoggerFactory.getLogger(clazz);
}
#Override
public Object postProcessBeforeInitialization(final Object bean,
final String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean,
final String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(),
new FieldCallback() {
#Override
public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
field.set(bean, produceLogger());
}
},
new ReflectionUtils.FieldFilter() {
#Override
public boolean matches(final Field field) {
return field.getAnnotation(Logger.class) != null;
}
});
return bean;
}
}

Resources