简体   繁体   中英

Spring BeanDefinition class name contains null for an instantiated bean

I want to post-process a Spring bean in some manner after it has been instantiated completely.

However when I am unable to get the original bean class name (since it is proxied) from ConfigurableListenerFactory after the ContextRefreshedEvent occurs.

I can't get bean class from the ApplicationContext , because it is proxied by JDK Dynamic Proxy. Question - how can I obtain the original bean's Class?

Please see verifiable example below :

import java.lang.reflect.Proxy;
import java.util.Objects;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;

public class ApplicationContextRefreshedEventTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfigurationClass.class);
        MyBean myBean = applicationContext.getBean(MyBean.class);
        myBean.hello();
    }
}

@Configuration
class MyConfigurationClass {

    @Bean
    public MyBean myBean() {
        return new MyBeanImp();
    }

    @Bean
    public MyAppEventListener myAppEventListener() {
        return new MyAppEventListener();
    }

    @Bean
    static MyBeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}

class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("myBean")) {
            final Class<?> aClass = bean.getClass();
            return Proxy.newProxyInstance(aClass.getClassLoader(), aClass.getInterfaces(),
                    (proxy, method, args) -> method.invoke(bean, args));
        } else {
            return bean;
        }
    }
}

class MyAppEventListener implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    ConfigurableListableBeanFactory configurableListableBeanFactory;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        final String[] beanDefinitionNames = event.getApplicationContext().getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            final BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanName);
            final String beanClassName = beanDefinition.getBeanClassName();
            if (Objects.isNull(beanClassName)){
                System.out.println(beanDefinition);
            }
        }
    }
}


interface MyBean {
    void hello();
}

class MyBeanImp implements MyBean {
    @Override
    public void hello() {
    }
}

Output :

Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=myConfigurationClass; factoryMethodName=myBean; initMethodName=null; destroyMethodName=(inferred); defined in com.example.MyConfigurationClass
null
Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=myConfigurationClass; factoryMethodName=myAppEventListener; initMethodName=null; destroyMethodName=(inferred); defined in com.example.MyConfigurationClass
null

在此处输入图片说明

Spring version : 5.2.7.RELEASE JDK version : 1.8.0_172

I don't think you can access the original bean class in a simple way from the listener: In the application context, there is already a proxy of the bean when the listener gets executed.

Also, note, since java.lang.Proxy works with the interfaces, there is no "underlying" bean actually, only the interface and the proxy that implements this interface.

So you could expose the method in the interface of the bean that would get the class and implement the proxy so that it would delegate to that method.

interface MyBean {
  ...
  Class<?> getRealClass();
}

Another way is to create a map of Class<?> to Class<?> . This map would contain a class of the Proxy as a key mapped to the "real" bean class as a value, bean post-processor would add entries to this map, the map could be accessed from the application context later on during the listener, but again, this is not a straightforward way to do this.

All in all, It might point on some design issue because actually beans are not created so I can't think of any concrete example os such processing given a fact that the original bean even doesn't exist...

It turns out that in Spring Java Config (in comparison with XML configurations) there is no notion of bean class names within Bean Definitions. Beans are created using @Configuration class as a factory and real bean definition names are not retained.

I was able to achieve my objective : invoke a bean's original method (the bean was proxied) once Spring context initialization completed as follows :

  1. Created custom implementation of InvocationHandler and passed in required bean information.
  2. In Application Listener obtained my custom InvocationHandler .

Code :

public class MyInvocationHandler implements InvocationHandler {

    private Object bean;
    private ProfilingController controller;

    public MyInvocationHandler(Object bean) {
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // Custom logic here - irrelevant for the purpose of this example
      return method.invoke(bean, args);
    }

    public Object getBean() {
        return bean;
    }
}


public class PostProxyInvocationProcessor implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        final String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanDefinitionNames) {
            final Object proxy = applicationContext.getBean(beanName);
            if (proxy instanceof Proxy) {
                final MyInvocationHandler invocationHandler = (MyInvocationHandler) Proxy.getInvocationHandler(proxy);
                final Object bean = invocationHandler.getBean();
                final Class<?> aClass = bean.getClass();
                final Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(MyCustomAnnotation.class)) {
                        try {
                            method.invoke(bean);
                        }
                        catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

}

I think the Bean Definition name is actually the Bean Name. 在此处输入图片说明

So, the class names can be retrieved directly from configurableListableBeanFactory

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        final ApplicationContext applicationContext = event.getApplicationContext();
        final String[] beanDefinitionNamesFromAppContext = applicationContext.getBeanDefinitionNames();

        for (String beanDefinitionName: beanDefinitionNamesFromAppContext) {
            System.out.println( configurableListableBeanFactory.getBean(beanDefinitionName).getClass());
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM