mad_fox
mad_fox

Reputation: 3188

Get request attributes and store them for later use anywhere in the application

I am using Spring MVC and I would like to get some parameters from the request headers and store them an an object that is available anywhere in the application during the current request. Imagine a an application wide metadata class.

I would like to avoid using the RequestContextHolder because other parts of the application below the controller shouldn't care that the values came from the request. It should just be available.

I also thought of using a request scoped bean, but then I would have to wire it up everywhere I wanted to use it. I which goes against "just having it available"

Any info on where to start would be great.

Update:

This is my idea. I'm worried that when other requests come, they will set the transactionId of the the previous request.

Wire up a request scoped bean:

     <bean id="metaDataContextHolder" class="com.yp.common.context.MetadataContextHolder" scope="request">
        <aop:scoped-proxy/>
     </bean>

Here is the request scoped bean

public class MetadataContextHolder {

    private static String transactionId;
    //removed other properties for brevity 

    public static String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    } 
}

Capture the request headers in the filter to be stored for use throughout the application:

public class RequestMetatDataFilter implements Filter
{

        @Autowired
        MetadataContextHolder metaDataHolder;

        @Override
        public void init(FilterConfig arg0) throws ServletException
        {

        }

        /**
         * This filter will add common request metatdata to the MetaDataContextHolder
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain filerChain) throws IOException, ServletException
        {
                HttpServletRequest request = ((HttpServletRequest)servletRequest);
                String transactionId = request.getHeader("transactionId");
                metaDataHolder.setTransactionId(transactionId);
                ...
                //store other values
                ...

                filerChain.doFilter(request, response);

        }

        @Override
        public void destroy() {
            // TODO Auto-generated method stub

        }
}

Then the metadata can be accessed anywhere without having to wire the metadata bean again like:

MetaDataContextHolder.getTransactionId();

Update 2

As I suspected, the static property "transactionId" in MetadataContextHolder is updated by every request.

Is it possible to achieve what I am trying to do?

Upvotes: 6

Views: 9935

Answers (3)

user3152140
user3152140

Reputation: 1

Coming across the same issue right now. I need an easily & unobtrusively accessible id for each request. I'm going to solve it by creating a Bean with a Request Scope annotation, and using an Interceptor/Filter to populate it for each request. Then autowire that Bean in classes where I need it.

Reference link: http://www.baeldung.com/spring-bean-scopes

Upvotes: 0

mad_fox
mad_fox

Reputation: 3188

To store a request scoped object and be able to retrieve it in a static way, I needed to implement my own context holder and store the object in thread local.

I started by duplicating the code from RequestContextHolder but instead I parameterized my thread local object with the Metadata class.

public class MetadataContextHolder {

    private static final ThreadLocal<Metadata> metadataHolder =
            new NamedThreadLocal<Metadata>("Metadata Context");

    private static final ThreadLocal<Metadata> inheritableMetadataHolder =
            new NamedInheritableThreadLocal<Metadata>("Metadata Context");

    /**
     * Reset the metadata for the current thread.
     */
    public static void resetMetadata() {
        metadataHolder.remove();
    }

    /**
     * Bind the given Metadata to the current thread,
     * <i>not</i> exposing it as inheritable for child threads.
     * @param metadata the Metadata to expose
     * @see #setMetadata(Metadata, boolean)
     */
    public static void setMetadata(Metadata metadata) {
        setMetadata(metadata, false);
    }

    /**
     * Bind the given Metadata to the current thread.
     * @param metadata the Metadata to expose,
     * or {@code null} to reset the thread-bound context
     * @param inheritable whether to expose the Metadata as inheritable
     * for child threads (using an {@link InheritableThreadLocal})
     */
    public static void setMetadata(Metadata metadata, boolean inheritable) {
        if (metadata == null) {
            resetMetadata();
        }
        else {
            if (inheritable) {
                inheritableMetadataHolder.set(metadata);
                metadataHolder.remove();
            }
            else {
                metadataHolder.set(metadata);
                inheritableMetadataHolder.remove();
            }
        }
    }

    /**
     * Return the Metadata currently bound to the thread.
     * @return the Metadata currently bound to the thread,
     * or {@code null} if none bound
     */
    public static Metadata getMetadata() {
        Metadata metadata = metadataHolder.get();
        if (metadata == null) {
            metadata = inheritableMetadataHolder.get();
        }
        return metadata;
    }

}

I then created a filter to populate the metadata context, but this can be populated anywhere really:

public class RequestMetatDataFilter implements Filter
{

        @Override
        public void init(FilterConfig arg0) throws ServletException
        {

        }

        /**
         * This filter will add common request metatdata to the MetadataContextHolder
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain filerChain) throws IOException, ServletException
        {
                HttpServletRequest request = ((HttpServletRequest)servletRequest);
                String transactionId = request.getHeader("transactionId");

                Metadata metadata = new Metadata(transactionId);
                MetadataContextHolder.setMetadata(metadata);

                filerChain.doFilter(request, response);
        }

        @Override
        public void destroy() {
            // TODO Auto-generated method stub

        }
}

I then needed to register a request listener in my web.xml to clean up the thread local when the request completes:

<listener>
    <listener-class>com.ws.listener.RequestListener</listener-class>
</listener>

Lastly I need to perform the cleanup in the request listener:

public class RequestListener implements ServletRequestListener {

    /**
     * Reset the metadata for the current request thread
     */
    public void requestDestroyed(ServletRequestEvent event) {
        MetadataContextHolder.resetMetadata();
    }

    /**
     * Don't do anything when the request is initialized
     */
    public void requestInitialized(ServletRequestEvent event) {

    }
}

Here is the Metadata object for good measure:

public class Metadata {

    private String  idTransaccion;
    //Other properties removed for brevity


    public Metadata(String idTransaccion) {
        super();
        this.idTransaccion = idTransaccion;
    }

    public String getIdTransaccion() {
        return idTransaccion;
    }
    public void setIdTransaccion(String idTransaccion) {
        this.idTransaccion = idTransaccion;
    }   

}

Upvotes: 1

Jeff Storey
Jeff Storey

Reputation: 57202

Two parts to this answer:

I would like to avoid using the RequestContextHolder because other parts of the application below the controller shouldn't care that the values came from the request. It should just be available.

Yes, this part makes sense for exactly the reason you just said.

I also thought of using a request scoped bean, but then I would have to wire it up everywhere I wanted to use it. I which goes against "just having it available"

Auto-wiring with request scoped beans is the way to go here. When you say you just want to have it available, it's not entirely clear what that means. It sounds like you want a singleton but for it to only contain the data for the current request, no matter who is accessing it. The request scoped beans are designed to hold only the current request data (as you know already), whereas singletons are not. I would actually considering autowiring a bean via annotation as "just having it available". Additionally having it as a bean rather than a singleton makes it much easier to test/mock when needed.

Upvotes: 2

Related Questions