问题
I'm having a spring mvc application with two contexts (as declared in my AbstractAnnotationConfigDispatcherServletInitializer
subclass)
- the root context contains models, repositories, etc
- the mvc context contains the controllers
Now, I can inject a repository into a controller, but why?
- Does the web context also include the beans from the root context (something like
@Import
??). The documentation implies they have a parent-child relationship, but by inspecting the web context I don't the repository beans inside it. - Or, does
@Autowired
work across multiple contexts? And, if so, how??
回答1:
Both contexts are stored in the same servlet context.
If you notice, AbstractDispatcherServletInitializer
,the parent class of AbstractAnnotationConfigDispatcherServletInitializer
, does the registration on the onStartup
method
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
To do that, first calls its parent onStartup method where it first adds the rootApplicationContext which is created by the createRootApplicationContext
method of the AbstractAnnotationConfigDispatcherServletInitializer
class and finally adds it to the ServletContext received on the onStartup method:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
/**
* Register a {@link ContextLoaderListener} against the given servlet context. The
* {@code ContextLoaderListener} is initialized with the application context returned
* from the {@link #createRootApplicationContext()} template method.
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
And after that, AbstractDispatcherServletInitializer
calls its registerDispatcherServlet
method where it calls the abstract method createServletApplicationContext
that is in the AbstractAnnotationConfigDispatcherServletInitializer
class and creates the dispatcherServlet
that is then added to the same ServletContext:
/**
* Register a {@link DispatcherServlet} against the given servlet context.
* <p>This method will create a {@code DispatcherServlet} with the name returned by
* {@link #getServletName()}, initializing it with the application context returned
* from {@link #createServletApplicationContext()}, and mapping it to the patterns
* returned from {@link #getServletMappings()}.
* <p>Further customization can be achieved by overriding {@link
* #customizeRegistration(ServletRegistration.Dynamic)} or
* {@link #createDispatcherServlet(WebApplicationContext)}.
* @param servletContext the context to register the servlet against
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
That's why you can inject a repository in a controller, because the 2 contexts are in the same ServletContext.
回答2:
I typically return a single context from AbstractAnnotationConfigDispatcherServletInitializer.getRootConfigClasses()
this class then has ComponentScan and/or Imports which finds @Configurations and @Components etc.
Unless you explicitly create parent-child contexts, all your beans, components, services ends up in a single ApplicationContext. By explicit I mean that you have to call setParent() on ApplicationContext before refresh() is called, so you typically know when you have done it.
This means that you can @AutoWire into an other spring bean from the same application context (the autowired bean may be from a parent context if you have nested contexts)
Edit
If you use AbstractAnnotationConfigDispatcherServletInitializer.getServletConfigClasses()
and return two contexts from you initializer, the servletConfig context will be a child and the root context will be the parent. This means that you can autowire beans from the RootConfig context into the servletConfig context beans, but not the other way around. - This is why I typically only return a context from getRootConfigClasses()
and null
from getServletConfigClasses()
.
来源:https://stackoverflow.com/questions/41550796/spring-where-does-autowired-look-for-beans