Gray
Gray

Reputation: 116908

How can a Spring bean detect if it itself has been wrapped in an AOP proxy?

We are using Spring's TransactionInterceptor to set some database partition information using ThreadLocal whenever a DAO method marked with the @Transactional annotation is executed. We need this to be able to route our queries to different database partitions.

This works fine for most DAO methods:

// this causes the invoke method to set a thread-local with the host name of
// the database server the partition is on
@Transactional
public int deleteAll() throws LocalDataException {

The problem is when we need to reference the DAO proxy object itself inside of the DAO. Typically we have to have the caller pass in the proxy-dao:

public Pager<Foo, Long> getPager(FooDao proxyDao) {

This looks like the following in code which is obviously gross.

fooDao.getPager(fooDao);

The problem is that when we are inside of FooDao, the this is not the proxy DAO that we need.

Is there a better mechanism for a bean to discover that it has a proxy wrapper around it? I've looked at the Spring AOPUtils but I see no way to find the proxy for an object. I don't want isAopProxy(...) for example. I've also read the Spring AOP docs but I can't see a solution there unless I implement my own AOP native code which I was hoping to avoid.

I suspect that I might be able to inject the DAO into itself with a ApplicationContextAware utility bean and a setProxyDao(...) method, but that seems like a hack as well. Any other ideas how I can detect the proxy so I can make use of it from within the bean itself? Thanks for any help.

Upvotes: 7

Views: 3811

Answers (3)

Biju Kunjummen
Biju Kunjummen

Reputation: 49935

A hacky solution along the lines of what you have suggested, considering that AspectJ compile time or load time weaving will not work for you:

Create an interface along these lines:

public interface ProxyAware<T> {
    void setProxy(T proxy);
}

Let your Dao's implement the ProxyAware implementation, now create a BeanPostProcessor with an Ordered interface to run last, along these lines:

public class ProxyInjectingBeanPostProcessor implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (AopUtils.isAopProxy((bean))){
            try {
                Object target = ((Advised)bean).getTargetSource().getTarget();
                if (target instanceof ProxyAware){
                    ((ProxyAware) target).setProxy(bean);
                }
            } catch (Exception e) {
                // ignore
            }
        }
        return bean;
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

It is ugly, but works.

Upvotes: 5

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340873

There is a handy static utility AopContext.currentProxy() method provided by Spring which returns a proxy to object from which it was called.

Although using it is considered a bad practice, semantically the same method exists in Java EE as well: SessionContext.getBusinessObject().

I wrote few articles about this utility method and various pitfalls: 1, 2, 3.

Upvotes: 4

Kent Dorsey
Kent Dorsey

Reputation: 336

Use Spring to inject a bean reference into the bean, even the same bean, just as you would for any other bean reference. No special action required.

The presence of such a variable explicitly acknowledges in the class design that the class expects to be proxied in some manner. This is not necessarily a bad thing, as aop can change behavior that breaks the class contract.

The bean reference would typically be for an interface, and that interface could even be a different one for the self-referenced internal methods.

Keep it simple. That way lies madness. :-)

More importantly, be sure that the semantics make sense. The need to do this may be a code smell that the class is mixing in multiple responsibilities best decomposed into separate beans.

Upvotes: 3

Related Questions