As a Spring developer we know how to load a resource in a Service. The prefixes of classpath: and file: are commonly used. But what if we want to load resources from a different location like a database table RESOURCE, with our own prefix db:? The Spring Framework provides some default built-in resource implementations, which can be found in the chapter Resources in the Spring Framework Reference Guide. The URL is prefixed and handled by the corresponding ResourceLoader (JavaDoc). If we want to load a resource from a database table RESOURCE we have to create our own ResourceLoader which triggers on the prefix db:.

DatabaseResourceDao

Let's create an interface which describes the action of getting a resource based on the resourceName.

import java.io.InputStream;
import org.springframework.core.io.Resource;

public interface DatabaseResourceDao {
     Resource getResource(String resourceName);
}

In this example our filename is equal to our resourcename. So if we execute getResource("db:myResource.txt") the name of the resource would be myResource.txt.

Database table

Next we create a database table which contains our binary resources. It has two columns: one for the name and one with the binary data.

//HSQLDB script
CREATE TABLE RESOURCE
(
  NAME VARCHAR(256) NOT NULL PRIMARY KEY,
  DATA BLOB         NOT NULL
);

DatabaseResourceDao implementation

After we have created our database table it is time to write a DatabaseResourceDao implementation to retrieve the Resource data from our database table.

import java.io.InputStream;
import java.sql.ResultSet;
import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.stereotype.Repository;

@Repository
public class DatabaseResourceDaoImpl implements DatabaseResourceDao {

     private final NamedParameterJdbcTemplate namedJdbcTemplate;

     @Autowired
     public DatabaseResourceDaoImpl(
          NamedParameterJdbcTemplate namedJdbcTemplate) {
          this.namedJdbcTemplate = namedJdbcTemplate;
     }

     @Override
     public Resource getResource(final String resourceName) {
          byte[] byteArray = namedJdbcTemplate.queryForObject(
               "SELECT data FROM resource WHERE name = :name",
               Collections.singletonMap("name", resourceName),
               (ResultSet rs, int rowNum) ->
                    new DefaultLobHandler().getBlobAsBytes(rs, "data")
          );
          return new ByteArrayResource(byteArray);
     }
}

A Custom ResourceLoader

We our able to retrieve a Resource with the created bean DatabaseResourceDaoImpl. This bean needs to be injected into a custom ResourceLoader, a standard Spring interface for loading a Resource. Our DatabaseResourceLoader is an implementation that looks for "db:". If the string parameter location contains the prefix db:, we know we need to load the resource by making a call to the DatabaseResourceDao implementation and returning the result. When the location parameter does not contain the "db:" prefix, the DatabaseResourceLoader will delegate to the delegate, which will be the original ResourceLoader. NB. Our DatabaseResourceLoader isn't ComponentScanned. In the next step we will construct it manually.

import org.apache.commons.lang.StringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class DatabaseResourceLoader implements ResourceLoader {

     private static final String DB_URL_PREFIX = "db:";
     private final ApplicationContext applicationContext;
     private final ResourceLoader delegate;

     public DatabaseResourceLoader(
          ApplicationContext applicationContext,
          ResourceLoader delegate) {
          this.applicationContext = applicationContext;
          this.delegate = delegate;
     }

     @Override
     public Resource getResource(String location) {
          if (location.startsWith(DB_URL_PREFIX)) {
             DatabaseResourceDao databaseResourceDao =
                 this.applicationContext.getBean(DatabaseResourceDao.class);
             String resourceName =
                 StringUtils.removeStart(location, DB_URL_PREFIX);
             return databaseResourceDao.getResource(resourceName);
         }
         return this.delegate.getResource(location);
     }

     @Override
     public ClassLoader getClassLoader() {
          return this.delegate.getClassLoader();
     }
}

Activate the ResourceLoader

At last we need to construct and bind the DatabaseResourceLoader and inject the original ResourceLoader. By defining a bean that implements the BeanFactoryPostProcessor and the ResourceLoaderAware interface we get the original ResourceLoader injected and register the new one by calling beanFactory.registerResolvableDependency(..). We also need to implement ApplicationContextAware to be able to inject the ApplicationContext into the DatabaseResourceLoader. The BeanPostProcessor interface is implemented to make sure that every other bean that implements ResourceLoaderAware gets the correct ResourceLoader injected.

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.Ordered;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;

@Component
public class ResourceLoaderBeanPostProcessor
     implements BeanPostProcessor, BeanFactoryPostProcessor, Ordered,
                ResourceLoaderAware, ApplicationContextAware {

     private ResourceLoader resourceLoader;
     private ApplicationContext applicationContext;

     @Override
     public Object postProcessBeforeInitialization(
          Object bean, String beanName) {
          if (bean instanceof ResourceLoaderAware) {
               ((ResourceLoaderAware)bean).
                    setResourceLoader(this.resourceLoader);
          }
          return bean;
     }

     @Override
     public Object postProcessAfterInitialization(
          Object bean,
          String beanName) {
          return bean;
     }

     @Override
     public void postProcessBeanFactory(
          ConfigurableListableBeanFactory beanFactory) {
          this.resourceLoader =
                new DatabaseResourceLoader(
                     this.applicationContext,
                     this.resourceLoader);
          beanFactory.registerResolvableDependency(
                ResourceLoader.class, this.resourceLoader);
     }

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

     @Override
     public void setResourceLoader(
          ResourceLoader resourceLoader) {
          this.resourceLoader = resourceLoader;
     }

     @Override
     public void setApplicationContext(
          ApplicationContext applicationContext) {
          this.applicationContext = applicationContext;
     }
}

Use in our Service

Finally we are able to load a resource with db: as prefix.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;

@Service
public class MyService {

     @Autowired
     public MyService(
          ResourceLoader resourceLoader) {
          //I should have myDbResource now!
          resourceLoader.getResource("db:myResource.txt");
     }
}
shadow-left