Reputation: 3612
I have custom annotation named Retry with following Bean Post Processor:
@Component
public final class RetryBPP implements BeanPostProcessor {
private final Map<String, ClassDefinition> map = new HashMap<>(40);
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
final Class<?> asClass = bean.getClass();
final Method[] methods = asClass.getMethods();
final List<Method> collect = Stream.of(methods)
.filter(method -> method.isAnnotationPresent(Retriable.class))
.collect(Collectors.toList());
if(!collect.isEmpty()){
this.map.put(beanName,new ClassDefinition(collect,asClass));
}
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
final ClassDefinition definition = this.map.get(beanName);
if(definition != null){
final Class beanClass = definition.baseCLass;
return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), (proxy, method, args) -> {
if(definition.isMethodPresent(method)){
return this.retry(definition.originalMethod(method),bean,args);
} else{
return method.invoke(bean,args);
}
});
} else{
return bean;
}
}
private Object retry(final Method method,final Object originalBean,Object[] argc){
final Retriable retriable = method.getAnnotation(Retriable.class);
int attempts = retriable.attempts();
while(true){
try{
return method.invoke(originalBean,argc);
}catch (final Exception throwable){
if(this.support(retriable.exceptions(),throwable.getClass()) && attempts != 0){
attempts--;
log.warn("Error on method {} wait to repeat",method.getName());
this.sleep(retriable.delay(),retriable.timeUnit());
} else{
throw new RuntimeException(throwable);
}
}
}
}
@SneakyThrows(InterruptedException.class)
private void sleep(final long time, final TimeUnit timeUnit){
timeUnit.sleep(time);
}
private boolean support(final Class<? extends Exception>[] exceptions,Class<? extends Exception> exception){
boolean support = false;
for (Class _class : exceptions) {
if(_class.equals(exception)){
support = true;
break;
}
}
return support;
}
@AllArgsConstructor
private static final class ClassDefinition{
final List<Method> methods;
final Class baseCLass;
boolean isMethodPresent(final Method method){
return this.methods.stream()
.anyMatch(mthd->methodEquals(mthd,method));
}
Method originalMethod(final Method method){
return this.methods.stream()
.filter(mthd->methodEquals(mthd,method))
.findFirst().orElseThrow(NullPointerException::new);
}
}
}
Interface:
public interface Inter {
String qqq(String url);
}
Abstract class:
public abstract class Abs implements Inter {
@Override
@Retry
public String qqq(final String url) {
someLogic(url);
}
protected String someLogic(String str);
Custom qualifier annotation:
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@Target({ElementType.CONSTRUCTOR, ElementType.TYPE, ElementType.PARAMETER, ElementType.FIELD})
public @interface Qualif {
VV.v type();
}
And implementation:
@Service
@Qualif(type = First)
@Slf4j
public final class Impl extends Abs {
@Override
protected String someLogic(String str){...}
}
And when I autowire by qualifier like this:
@Autowired
@Qualif(type = First)
Inter inter;
Spring throw exception No qualifying bean of type [com.devadmin.downloader.sites.VideoDownloader] found for dependency Inter
:, but when I remove @Retry annotation from abctract class everything work cool. How to solve this issue.
BTW I checked that my class that extend Abstract class inside ApplicationContext
but Spring didn't see my custom qualifier
Upvotes: 1
Views: 222
Reputation: 3612
Finally I find an answer. For those who didn't understand the problem.
I have an interface with method foo()
, then an abstract class that implements this interface and has own abstract method
abstract class FooAbs impl Inter{
@Override
void foo(){
childMethod();
}
protected void childMethod();
}
And finally I have class with @Component that extends FooAbs class.
@Component
class First extends FooAbs{
@Override
void childMethod(){}
}
The problem was with dynamic proxy.
This code from below wasn't able to get interfaces from class
Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), (proxy, method, args) -> {}
Dynamic proxy doesn't see getInterfaces
of First
class because this class doesn't implement interface directly, therefore I changed my class signature to this:
@Component
class First extends FooAbs implement Inter{
@Override
void childMethod(){}
}
After this step
beanClass.getInterfaces()
sees Inter interface. I know that it's a work around and I will rewrite my code to avoid such structure. But it works and maybe for some one it will be helpful
Upvotes: 1