How to get access to ServletContext inside of an @Configuration or @SpringBootApplication class
Asked Answered
R

3

7

I'm attempting to update an old Spring application. Specifically, I'm trying to pull all of the beans out of the old xml-defined form and pull them into a @SpringBootApplication format (while dramatically cutting down on the overall number of beans defined, because many of them did not need to be beans). My current issue is that I can't figure out how to make the ServletContext available to the beans that need it.

My current code looks something like this:

package thing;

import stuff

@SpringBootApplication
public class MyApp {

    private BeanThing beanThing = null;

    @Autowired
    private ServletContext servletContext; 

    public MyApp() {
        // Lots of stuff goes here.
        // no reference to servletContext, though
        // beanThing gets initialized, and mostly populated.
    }

    @Bean public BeanThing getBeanThing() { return beanThing; }

    @PostConstruct
    public void populateContext() {
        // all references to servletContext go here, including the
        // bit where we call the appropriate setters in beanThing
    }
}

The error I get back: Field servletContext in thing.MyApp required a bean of type 'javax.servlet.ServletContext' that could not be found.

So... what am I missing? Is there something I'm supposed to be adding to the path? Is there some interface I need to implement? I can't provide the bean myself because the whole point is that I'm trying to access servlet context info (getContextPath() and getRealPath() strings) that I don't myself have.

Ruthie answered 26/4, 2018 at 16:4 Comment(0)
L
7

Please be aware of the best practice for accessing the ServletContext: You shouldn't do it in your main application class, but e. g. a controller.

Otherwise try the following:

Implement the ServletContextAware interface and Spring will inject it for you.

Remove @Autowired for the variable.

Add setServletContext method.

@SpringBootApplication
public class MyApp implements ServletContextAware {

    private BeanThing beanThing = null;

    private ServletContext servletContext; 

    public MyApp() {
        // Lots of stuff goes here.
        // no reference to servletContext, though
        // beanThing gets initialized, and mostly populated.
    }

    @Bean public BeanThing getBeanThing() { return beanThing; }

    @PostConstruct
    public void populateContext() {
        // all references to servletContext go here, including the
        // bit where we call the appropriate setters in beanThing
    }

    public void setServletContext(ServletContext servletContext) {
        this.context = servletContext;
    }


}
Legged answered 26/4, 2018 at 16:14 Comment(7)
So... I'm currently working in Intellij IDEA, and it complains if I try to implement ServletContextAware without implementing abstract method setServletContext(ServletContext). I could probably leverage that, and I'll try, but could you address that one way or another in your answer?Ruthie
Yes, if you implement that interface, you need to add the setServletContext method. Updated my answer.Legged
Cool. Seems to be working. As it turns out, I managed to fit the logic under setServletContext itself, and was able to get rid of populateContext() and the @PostConstruct entirely. Accepted for now, and will stay accepted as long as it doesn't generate some other bug later in the process.Ruthie
As far as "best practice"... there are a lot of design decisions in this mess of code that I myself would not have made if I were the one making them for the first time. Hopefully I'll have an opportunity to fix them later.Ruthie
So... I managed to get to the point where it was actually compiling and running... and for reasons I don't understand, setServletContext isn't ever being called. Do you have any knowledge of this?Ruthie
setServletContext is not called for me as well, not working in Spring Boot Starter 2.2.5Finley
I can't get access to ServletContextAware. What namespace is it imported from? Or package?Invulnerable
S
0

Make the bean lazy and use a parameter:

@Lazy
@Bean
public BeanThing getBeanThing(ServletContext servletContext) {
    return new BeanThing(servletContext);
}

It needs to be lazy because the ServletContext won't exist when the instance of the MyApp is created. When the ServletContext becomes available, Spring will remember it somewhere and it will be able to fill in the parameter.

To make this work, you just have to make sure the bean isn't requested before that.

Now you might have to create beans which need BeanThing earlier than that. The solution then is to inject a Provider<BeanThing> and make sure the provider is only used after the ServletContext exists (i.e. not in @PostConstruct or similar).

Stclair answered 15/2, 2022 at 12:38 Comment(0)
C
0

The accepted answer does not work in spring boot (at least in version 3). However, if you are simply trying to obtain the servlet context path, you can inject the spring boot application property server.servlet.context-path, since you are probably going to set it anyways.

So you could have something like this in your @Configuration:

@Bean
public Object createBean(@Value("${server.servlet.context-path}") String contextPath) {
    ...
}
Contraposition answered 29/2 at 14:30 Comment(1)
Not sure why you say that the accepted answer doesn't work in Spring Boot 3. I've just used it successfully. I didn't do anything particular either, my service class is annotated with @Service and thus not registered manually as a bean, and it implements org.springframework.web.context.ServletContextAware. Also your answer is for the servlet context path so it's not an answer to the question. I'm sure there are plenty of (duplicate) questions asking how to get the context path, with many answers.Plourde

© 2022 - 2024 — McMap. All rights reserved.