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());
}
}
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)
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.