0
votes

I am trying to implement Spring Boot AOP for data-source pointcut - where before running any query I need to set client context in DB connection.

I was trying this approach of using DelegatingDataSource. But I am getting below error during server startup

 org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

Please let me know DeletegatingDatasource for JNDI based DB lookup.

Edit 1: AOP - I tried to add pointcut execution(public * javax.sql.DataSource+.getConnection(..)). This works only when Spring datasource is used with username/password. Once i deploy in Jboss with JNDI I am getting WildFlyDataSource Proxy error. So, instead of AOP approach I thought of using DelegatingDatasource

   // AOP Example

    @Pointcut("execution(public * javax.sql.DataSource+.getConnection(..))")
    void prepareConnectionPointcut() {
        logger.debug("prepareConnectionPointcut");
    }
    
    @AfterReturning(pointcut = "prepareConnectionPointcut()", returning = "connection")
    void afterPrepareConnection(Connection connection) {
        // Set context in Connection - return same connection for query execution
    }

But when i deploy this code in JBoss - I am getting WildFlyDataSource datasource bean creation error.

Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/JndiDataSourceAutoConfiguration.class]: Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class org.jboss.as.connector.subsystems.datasources.WildFlyDataSource: Common causes of this problem include using a final class or a non-visible class; nested exception is org.springframework.cglib.core.CodeGenerationException: java.lang.NoClassDefFoundError-->org/jboss/as/connector/subsystems/datasources/WildFlyDataSource

I have also added proxyTargetClass flag during initialization

@EnableAspectJAutoProxy(proxyTargetClass = true)

1
That will obviously not work, as you are defining the data source yourself and have a dependency on it it is a circular dependency. Also not sure why you would call this AOP as it is just a proxy. Use a BeanPostProcessor to wrap an existing DataSource with your delegating implementation.M. Deinum
@Deinum: Thanks for your response. I have added reason for terming AOP. Will BeanPostProcessor be called whenever datasource bean is called ? Meaning whenever some query is executed in DAO (through entitymanager.createNativeQuery or Repository.find ?Karthikeyan
I suggest you readup on what a BeanPostProcessor is and does.M. Deinum
I tried to implement BeanPostProcessor & understand its execution. It is called first time during initialization or bean. But what i am looking is to aspect pointcut every time before running any queries/storedprocedure to set DB session context. Equivalent of this in spring bootKarthikeyan
You clearly don't understand the usage of BeanPostProcessor and how to combine it with DelegatingDataSourc. Use the BeanPostProcessor to wrap the existing datasource with a delegating one, which executes your logic in the getCOnnection method. You don't need to use app for this.M. Deinum

1 Answers

0
votes

Thanks @M.Deinum for recommendation of using BeanPostProcessor & Implement DelegatingDatasource for setting client info. Please find snippet below which i have implemented to accomplish this in Spring Boot which works well with JBoos JNDI based connection or Spring Boot URL Datasource connection.

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if (bean instanceof DataSource) {
            // Check DataSource bean initialization & enclose it with DelegatingDataSource 
            logger.debug("MyBeanPostProcessor:: postProcessAfterInitialization:: DataSource");

            DataSource beanDs = (DataSource) bean;
            return new MyDelegateDS(beanDs);
        }

        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DataSource) {
            logger.debug("MyBeanPostProcessor:: postProcessBeforeInitialization:: DataSource");
        }

        logger.debug("MyBeanPostProcessor:: postProcessBeforeInitialization:: " + beanName);

        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

}

My implementation of DelegatingDataSource to handle each user request to set client context in DB connection session

 public class MyDelegateDS extends DelegatingDataSource {

    private static Logger logger = LoggerFactory.getLogger(MyDelegateDS.class);

    public MyDelegateDS(DataSource delegate) {
        super(delegate);
        logger.debug("MyDelegateDS:: constructor");
    }

    @Override
    public Connection getConnection() throws SQLException {
        logger.debug("MyDelegateDS:: getConnection");

        // To do this context only for user Request - to avoid this during Server initialization
        if (RequestContextHolder.getRequestAttributes() != null
                && ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest() != null) {

            logger.debug("MyDelegateDS:: getConnection: valid user request");

            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
                    
            // Checking each user request & calling SP to set client context before invoking actual native query/SP
        }

        logger.debug("MyDelegateDS:: getConnection: Not User Request");

        return super.getConnection();
    }

}

Hope this is helpful for someone facing same problem