javaspringaopspring-aopdynamic-proxy

No qualifying bean of type exception when added custom beanpostprocessor and trying to get beans that are classes


I'm learning proxy usage in java and want to log time of method's execution.

All of this components are placed in com.example.testproxyproject package.

I have Order interface which is implemented by OrderImpl class

Order:

@Component
public interface Order {
    String getName();
    String getVector();
    void setName(String name);
    void setVector(String vector);
}

OrderImpl only overrides those methods and don't add something new expect annotations

OrderImpl:

@Component
public class OrderImpl implements Order {
    private String name;
    private String vector;
    @Time
    @Override
    public String getName() {
        return name;
    }
    @Override
    public String getVector() {
        return vector;
    }
    @Time
    @Override
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public void setVector(String vector) {
       this.vector = vector;
    }
}

Time annotation is quite simple

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Time {
}

For proxy I have custom invocation handler where I measure time of method execution which are marked with Time annotation

public class CustomInvokeHandler implements InvocationHandler {
    private Object object;
    private List<Method> methods;
    public CustomInvokeHandler(Object object,List<Method> methods){
        this.object = object;
        this.methods = methods;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long timeBeforeInvoking = System.nanoTime();
        Object result = method.invoke(object,args);
        if(methods.stream().anyMatch(listMethod->listMethod.getName().equals(method.getName()))) {
            System.out.println(System.nanoTime()-timeBeforeInvoking);
        }
        return result;
    }
}

I use it in beanPostProcessor class

CustomBeanProcessor:

@Component
public class CustomBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> beanClass = bean.getClass();
        if (beanClass.getInterfaces().length == 0) return bean;
        return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), new CustomInvokeHandler(bean, getAnnotatedMethods(bean.getClass().getMethods(), Time.class)));
    }
    public <T extends Annotation>List<Method> getAnnotatedMethods(Method[] methods, Class<T> annotation){
        return Stream.of(methods).filter(method-> method.isAnnotationPresent(annotation)).collect(Collectors.toList());
    }
}

In Demo Application I test my custom beanpostprocessor

@Configuration
@ComponentScan("com.example.testproxyproject")
public class DemoApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(DemoApplication.class);
        Order order = context.getBean(Order.class);
        order.setName("new Name");
        System.out.println(order.getName());
    }
}

Result: enter image description here

When I run It works as it should be, but If i change needed bean to OrderImpl class I will get this exception

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.testproxyproject.OrderImpl' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
    at com.example.testproxyproject.DemoApplication.main(DemoApplication.java:12)

Solution

  • This is precisely why you declare your dependencies as an interface instead of the implementation class: You replaced the original bean, which was OrderImpl, with a wrapped (proxied) version that is no longer an OrderImpl. The exact same thing happens with other kinds of advice.