Spring(十七):获取ApplicationContext方式,获取所有Bean和RequestMapping

Spring 项目经常需要拿到上下文,通过上下文来获取 IoC 容器中注册的 Bean。

获取上下文有好几种方式,可以实现 ApplicationContextAware 接口,可以通过 ServletContext 获取,而通过 ContextLoader 来获取上下文在 Spring Boot 中并不适用,适用于 Spring 项目。

查看容器中所有Bean

要查看所有 Bean 必须先拿到上下文:ApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}

public static ApplicationContext getApplicationContext(){
return applicationContext;
}
}

拿到上下文调用getBeanDefinitionNames()方法,返回一个装有 beanId 的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class Test01 {

@Test
public void printAllBeanFromIoC() {
ApplicationContext context = ApplicationContextHolder.getApplicationContext();
String[] beanDefinitionNames = context.getBeanDefinitionNames();
for (String beanName : beanDefinitionNames) {
System.out.println(beanName);
}
}
}

获取所有RequestMapping

WebApplicationContext 可以获得请求处理映射器 RequestMappingHandlerMapping,再可以获得所有映射的 URL。

1
2
RequestMappingHandlerMapping mapping = webApplicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();

获取ApplicationContext的几种方式

SpringApplication.run返回

Spirng Boot 应用启动入口 main 方法中的 SpringApplication.run 返回的就是 ApplicationContext,可以存起来用。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
public class DemoApplication {

private static ApplicationContext applicationContext;

public static void main(String[] args) {
applicationContext = SpringApplication.run(DemoApplication.class, args);
}
}

注入ApplicationContext直接使用

1
2
@Autowired
private ApplicationContext applicationContext;

该方法说明 ApplicationContext 已经是 Spring 管理的 Bean 了,也可以直接在构造方法中注入,所以在自定义 starter、工具或配置时,可以在构造方法中注入来使用。

通过ApplicationContextAware实现

实现ApplicationContextAware接口

实现 ApplicationContextAware 接口,重写setApplicationContext(ApplicationContext context) 方法,并保存 ApplicationContext 对象。
Spring 初始化时,会通过该方法将 ApplicationContext 对象注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}

public static ApplicationContext getApplicationContext(){
return applicationContext;
}
}

备注:实现 ApplicationContextAware 的类必须在XMLJavaConfig里配置为 Bean

继承 ApplicationObjectSupport

ApplicationObjectSupport 也是实现 ApplicationContextAware 接口的抽象类。

继承自抽象类来 ApplicationObjectSupport,该类提供 getApplicationContext() 方法获取 ApplicationContext
Spring 初始化时,会通过该抽象类的 setApplicationContext(ApplicationContext context) 方法将 ApplicationContext 对象注入。

继承 WebApplicationObjectSupport

WebApplicationObjectSupport 是继承 ApplicationObjectSupport 的抽象类。Web 应用中使用,调用 getWebApplicationContext() 获取 WebApplicationContext

通过 DispatcherServlet 获取

Spring MVC 可以通过 DispatcherServlet 直接获取 WebApplicationContext。DispatcherServlet 通过父类 FrameworkServlet 实现了 ApplicationContextAware 接口。

1
2
3
4
@Autowired
private DispatcherServlet dispatcherServlet;

WebApplicationContext webApplicationContext = dispatcherServlet.getWebApplicationContext();

通过 WebApplicationContextUtils 获取

先获取 ServletContext

通过 Controller 接口入参 HttpServletRequest 获取:

1
ServletContext servletContext = httpServletRequest.getServletContext();

通过 RequestContextHolder 获取 HttpServletRequest:

1
2
3
4
5
6
// 通过 RequestContextHolder 获取 HttpServletRequest
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getServletContext();

// 指定WebApplicationContext
WebApplicationContext webApplicationContext = (WebApplicationContext) request.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);

Spring MVC 通过 DispatcherServlet 来获取:

1
2
3
4
5
6
7
8
@Autowired
private DispatcherServlet dispatcherServlet;

ServletContext servletContext = dispatcherServlet.getServletContext();

// ServletConfig可以获取
ServletConfig servletConfig = dispatcherServlet.getServletConfig();
ServletContext servletContext = servletConfig.getServletContext();

通过 WebApplicationContextUtils 获取

1
2
3
4
// 1 找不到时会抛出异常
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
// 2 找不到会返回null
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);

getRequiredWebApplicationContext 方法实际调的是 getWebApplicationContext 方法,但多了一个入参

1
2
3
4
5
6
7
8
9
10
11
12
13
public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) {
WebApplicationContext wac = getWebApplicationContext(sc);
// 加了空判断抛异常
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
}
return wac;
}

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

ContextLoader上下文加载器

ContextLoader:Spring 上下文加载器,为根应用上下文执行实际的初始化操作,由子类ContextLoaderListener 调用。

ContextLoaderListener 的本质是个监听器,继承了 ContextLoader,实现了 ServletContextListener,也就是说在监听到 Servlet 应用启动事件时,会为 Servlet 上下文初始化 Spring 的 Web 应用上下文。

1
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();

ContextLoaderListener

1
2
3
4
5
@Override
public void contextInitialized(ServletContextEvent event) {
// initWebApplicationContext 是父类ContextLoader的方法
initWebApplicationContext(event.getServletContext());
}

ContextLoader:

1
2
3
4
5
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//.......省略
this.context = createWebApplicationContext(servletContext);
//......省略
}

注意:Spring Boot 使用内置的 Tomcat 走的不是这个逻辑,这样拿到的 webApplicationContext 是空的。

跟踪源码后发现启动过程并没有调用initWebApplicationContext(),也就是ContextLoaderListener监听器类根本就没执行,而在 SSM 项目是在web.xml文件配置该监听器。
返回到Spring Boot 入口类,跟踪启动过程SpringApplication.run()的源码,可以看到在启动初始化了 14 个监听器中,确实没有ContextLoaderListener,启动过程中也没有执行入口类继承的父类SpringBootServletInitializer里的onStartup()方法。
所以此方式不适合使用 Spring Boot 内置的Tomcat启动的项目,在外部Tomcat的使用待验证。

手动创建ApplicationContext

AbstractRefreshableWebApplicationContext:可刷新Web应用上下文抽象,相当于重新加载一次。

注意,Spring 上下文在服务器启动时就会初始化,这里再重新加载,是非常耗资源且低效的,通常只在 Spring 独立应用中使用,或在测试时需要加载外部文件创建 Spring 上下文时使用。

Spring ApplicationContext

  • AnnotationConfigApplicationContext:从Java的配置类中加载上下文定义,适用于Java注解的方式
  • ClassPathXmlApplicationContext:从类路径下XML配置文件中加载上下文定义,适用于 XML 配置的方式
  • FileSystemXmlApplicationContext:从文件系统下的 XML 配置文件中加载上下文定义,适用于指定系统盘符的文件路径中加载XML配置文件
  • AnnotationConfigWebApplicationContext:适用于 Web 应用,使用注解方式加载上下文
  • XmlWebApplicationContext:适用于 Web 应用,从Web应用下的XML配置文件加载上下文定义
1
2
3
4
5
6
7
8
9
10
11
public class ApplicationContext {
public static void main(String[] args) {
//加载项目中的spring配置文件到容器
// ApplicationContext context = new ClassPathXmlApplicationContext("resouces/application-context.xml");
//加载系统盘中的配置文件到容器
ApplicationContext context = new FileSystemXmlApplicationContext("E:/Spring/application-context.xml");
//从容器中获取对象实例
Man man = context.getBean(User.class);
man.driveCar();
}
}

Spring(十七):获取ApplicationContext方式,获取所有Bean和RequestMapping

http://blog.gxitsky.com/2018/05/02/Spring-17-ApplicationContext/

作者

光星

发布于

2018-05-02

更新于

2022-06-17

许可协议

评论