Search code examples
javaspringannotationsaop

Why can not get annotation from beanClass?


@Transactional
@Component
@EntranceLog
public class TransferServiceImpl implements TransferService {

    xxxx

}

I hava a class with Transactional annotation and Component annotation. EntranceLog is my customize annotation to print log by aop.

public class LogProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware {

    private static final LogInterceptor LOG = new LogInterceptor();

    private static Logger log = LoggerFactory.getLogger(LogProxyCreator.class);

    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String s, TargetSource targetSource) throws BeansException {
        Annotation anno = null;

        for (Annotation annotationTemp : beanClass.getAnnotations()) {
            Log temp = annotationTemp.annotationType().getAnnotation(EntranceLog.class);
            if (temp != null) {
                anno = temp;
                break;
            }
        }

        if (anno == null) {
            return null;
        }
        Object[] additional =  new Object[]{LOG};

        log.error(beanClass.getName() + " has register the fc log.");

        return additional;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        LOG.setContext(applicationContext);
    }
}

When my app is starting, the bean transferServiceImpl start, but beanClass.getAnnotations() can not get any annotation. Why?

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Log(logName = "entrance")
public @interface EntranceLog {
    @AliasFor(
        annotation = Log.class,
        attribute = "subLogName"
    )
    String logName() default "";

    @AliasFor(
        annotation = Log.class,
        attribute = "openInfoLog"
    )
    boolean openInfoLog() default false;
}

This is my annotation.


Solution

  • In Spring @Transactionalis already an AOP processed annotation, so adding your own will require some additional work. Let me explain how Spring AOP and @Transactional works.

    Spring has two ways of doing AOP, if the class implements an interface it can use a standard JDK Proxy, if the class does not implement an interface it will create a new subclass by using CGLib to emit bytecode at runtime. Unless you are very careful you will almost always get a CGLib proxy with Spring AOP.

    When Spring encounters a @Transactional (class or method level) it creates a new subclass using CGLib, you can think of this class as a decorator, which forwards all calls to your implementation class. Before and after (around Advice) it check the @Transactional annotation properties, and check Thread Local storage to see if a transaction already exist, if there is no transaction it creates one, and remembers it so it can commit it afterwards. If you set a breakoint inside a Transactional method and look at the callstack you will see the call to your implementation came from the decorater class, and that there is no source code for it.

    In your case the bean that is added to the Application Context, is not your TransferServiceImplbean, but the CGLib proxy created by Spring when it found the @Transactional annotation on your class, it will be named something like TransferServiceImpl$$FastClassBySpringCGLIB$$<hexstring> - This class does not have the @EntranceLog annotation, which is why your own aspect is not working.

    I have never encountered this problem myself, as I try to avoid AOP in general, or at always on classes that are already being CGLib proxied by Spring. Unless you want to dig deep into the Spring source, or find someone on the Spring dev team to help you with this, I suggest that you create another layer of indirection, so you don't need to handle two aspects in the same class.