Spring中除DI之外获取 BEAN 的方式
前言
在 Spring 框架的开发实践中,获取 Bean 是极为重要的基础操作。我们熟知通过依赖注入能便捷地获取 Bean,不过在许多场景下,还需要借助其他方式来获取 Bean。本文将深入探讨在 Spring 中除了依赖注入之外获取 Bean 的多种方式,并结合实际代码示例进行详细讲解。
一、ApplicationContext 获取 Bean
1.1 ApplicationContext 简介
ApplicationContext 是 BeanFactory 的子接口,它为开发者提供了更为丰富的功能支持,涵盖国际化、资源访问以及事件传播等方面。在日常开发工作里,ApplicationContext 是使用最为广泛的 Spring 容器。常见的 ApplicationContext 实现类包括 ClassPathXmlApplicationContext(用于加载类路径下的 XML 配置文件)、FileSystemXmlApplicationContext(用于加载文件系统中的 XML 配置文件)以及 AnnotationConfigApplicationContext(用于加载基于注解的配置类)。
1.2 使用 ClassPathXmlApplicationContext 获取 Bean
假设我们有一个简单的 Spring 项目,其中包含一个 UserService 类,通过 XML 配置文件将其注册为 Bean。
先定义 UserService 类:
public class UserService {public void sayHello() {System.out.println("Hello from UserService!");} }
接着在 Spring 的 XML 配置文件(如 applicationContext.xml)中配置 UserService:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userService" class="com.example.service.UserService"/> </beans>
然后通过 ClassPathXmlApplicationContext 来获取 UserService 的 Bean 实例:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = (UserService) applicationContext.getBean("userService");userService.sayHello();} }
在这段代码中,首先创建了一个 ClassPathXmlApplicationContext 对象,并加载了 applicationContext.xml 配置文件。随后,调用 applicationContext.getBean ("userService") 方法,依据 Bean 的 id 从容器中获取了 UserService 的实例,进而调用了其 sayHello 方法。
1.3 使用 AnnotationConfigApplicationContext 获取 Bean
当项目采用基于注解的配置方式时,可以利用 AnnotationConfigApplicationContext 来获取 Bean。
先定义一个配置类,以此替代 XML 配置文件:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class AppConfig {@Beanpublic UserService userService() {return new UserService();} }
接着通过 AnnotationConfigApplicationContext 来获取 UserService 的 Bean 实例:
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class Main {public static void main(String[] args) {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = applicationContext.getBean(UserService.class);userService.sayHello();} }
这里创建了一个 AnnotationConfigApplicationContext 对象,并传入了配置类 AppConfig.class。通过 applicationContext.getBean (UserService.class) 方法,依据 Bean 的类型从容器中获取了 UserService 的实例。
1.4 applicationContext.getBean () 的更多使用方式
按类型获取多个 Bean:若容器中有多个同一类型的 Bean,可以使用applicationContext.getBeansOfType(Class<T> type)方法获取一个 Map,其中键为 Bean 的名称,值为对应的 Bean 实例。例如:
import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.util.Map;public class Main {public static void main(String[] args) {ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);Map<String, UserService> userServiceMap = applicationContext.getBeansOfType(UserService.class);userServiceMap.forEach((name, service) -> service.sayHello());} }
通过名称和类型获取 Bean:可以使用applicationContext.getBean(String name, Class<T> requiredType)方法,同时指定 Bean 的名称和类型来获取 Bean,这样能确保获取到的 Bean 类型准确无误,避免类型转换异常。例如:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = applicationContext.getBean("userService", UserService.class);userService.sayHello();} }
通过 BeanFactoryPostProcessor 获取 Bean 定义:
BeanFactoryPostProcessor 允许我们在容器实例化 Bean 之前,对 Bean 的定义(BeanDefinition)进行修改。我们也可以利用它来获取 Bean 定义相关信息。首先定义一个实现了 BeanFactoryPostProcessor 接口的类:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {BeanDefinition userServiceDefinition = beanFactory.getBeanDefinition("userService");// 可以在这里对BeanDefinition进行操作,比如修改属性等System.out.println("获取到UserService的BeanDefinition,类名为:" + userServiceDefinition.getBeanClassName());} }
然后在配置文件中注册这个 BeanFactoryPostProcessor:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="userService" class="com.example.service.UserService"/><bean class="com.example.config.CustomBeanFactoryPostProcessor"/> </beans>
当 Spring 容器启动时,会调用 CustomBeanFactoryPostProcessor 的 postProcessBeanFactory 方法,从而获取到 UserService 的 BeanDefinition。
二、BeanFactory 获取 Bean
2.1 BeanFactory 简介
BeanFactory 是 Spring 框架中最基础的容器接口,它定义了 IOC 容器的基本功能规范,例如 Bean 的加载、实例化、获取等操作。尽管其功能相对简单,但却是 ApplicationContext 的基础。理解 BeanFactory 对于深入掌握 Spring 的 IOC 机制具有至关重要的意义。常见的 BeanFactory 实现类曾经是 XmlBeanFactory,但从 Spring 3.1 开始,XmlBeanFactory 已被废弃,如今推荐使用 DefaultListableBeanFactory。
2.2 使用 DefaultListableBeanFactory 获取 Bean
同样以 UserService 为例,假设使用 DefaultListableBeanFactory 来获取 Bean。
首先创建一个 BeanDefinition 对象,用于定义 UserService 的 Bean 信息:
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory;public class Main {public static void main(String[] args) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();beanFactory.registerBeanDefinition("userService", beanDefinition);UserService userService = (UserService) beanFactory.getBean("userService");userService.sayHello();} }
在这段代码中,先创建了一个 DefaultListableBeanFactory 对象。接着,通过 BeanDefinitionBuilder 创建了一个 BeanDefinition 对象,用于描述 UserService 的 Bean 信息。随后,使用 beanFactory.registerBeanDefinition ("userService", beanDefinition) 方法将 UserService 的 Bean 定义注册到容器中。最后,通过 beanFactory.getBean ("userService") 方法从容器中获取 UserService 的实例。
2.3 BeanFactory 获取 Bean 的特点
延迟加载:只有在调用 getBean 方法获取 Bean 时,才会实例化该 Bean,这种特性适合对资源消耗较大、使用频率较低的 Bean。
轻量级:与 ApplicationContext 相比,BeanFactory 的功能较为简单,资源占用少,适用于对功能要求不高的轻量级应用场景或者特定的底层框架开发。
三、在 Web 项目中高效使用这些容器获取 Bean
3.1 在 Servlet 环境中使用 ApplicationContext 获取 Bean
在基于 Servlet 的 Web 项目中,可以通过实现 ServletContextListener 接口来获取 ApplicationContext。首先在 web.xml 中配置 ContextLoaderListener:
<context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
然后在 Servlet 中获取 ApplicationContext 并获取 Bean:
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; import java.io.IOException;public class MyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ServletContext servletContext = getServletContext();WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);UserService userService = applicationContext.getBean(UserService.class);userService.sayHello();response.getWriter().println("Hello from Servlet with UserService");} }
通过这种方式,在 Servlet 中就能高效地获取到 Spring 容器中的 Bean。
3.2 在 Spring MVC 项目中使用 ApplicationContext 获取 Bean
在 Spring MVC 项目中,通常已经配置好了 ApplicationContext。可以通过 @Autowired 注解直接将 Bean 注入到 Controller 中,这种方式极为便捷高效。例如:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody;@Controller public class MyController {@Autowiredprivate UserService userService;@GetMapping("/hello")@ResponseBodypublic String hello() {userService.sayHello();return "Hello from Controller with UserService";} }
通过这种方式,Spring MVC 会自动从 ApplicationContext 中获取对应的 Bean 并注入,大大减少了手动获取 Bean 的繁琐过程。但这属于依赖注入方式,这里仅作对比说明。在 Spring MVC 中不通过注入获取 Bean 的话,也可以在 Controller 中通过如下方式获取:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody;@Controller public class MyController {@Autowiredprivate ApplicationContext applicationContext;@GetMapping("/hello")@ResponseBodypublic String hello() {UserService userService = applicationContext.getBean(UserService.class);userService.sayHello();return "Hello from Controller with UserService";} }
通过将 ApplicationContext 注入到 Controller,进而可以获取容器中的 Bean。
3.3 在 Web 项目中使用 BeanFactory 获取 Bean
在 Web 项目中使用 BeanFactory 获取 Bean 的情况相对较少,但在一些特定场景下仍有应用。可以通过创建一个自定义的 ServletContextListener,在其中初始化 DefaultListableBeanFactory,并注册 BeanDefinition。例如:
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.web.context.ServletContextAware; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener;@WebListener public class BeanFactoryInitializer implements ServletContextListener, ServletContextAware {private ServletContext servletContext;@Overridepublic void contextInitialized(ServletContextEvent sce) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();beanFactory.registerBeanDefinition("userService", beanDefinition);servletContext.setAttribute("beanFactory", beanFactory);}@Overridepublic void contextDestroyed(ServletContextEvent sce) {}@Overridepublic void setServletContext(ServletContext servletContext) {this.servletContext = servletContext;} }
然后在 Servlet 中获取 BeanFactory 并获取 Bean:
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import java.io.IOException;public class MyServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ServletContext servletContext = getServletContext();DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) servletContext.getAttribute("beanFactory");UserService userService = (UserService) beanFactory.getBean("userService");userService.sayHello();response.getWriter().println("Hello from Servlet with UserService from BeanFactory");} }
不过,这种方式相对复杂,并且在 Web 项目中,ApplicationContext 通常能更好地满足需求,所以 BeanFactory 在 Web 项目中的使用相对受限。
3.4 在测试类中获取 Bean
在进行单元测试或者集成测试时,我们也经常需要获取 Bean 来进行相关功能的测试。以 JUnit 和 Spring Boot 的测试为例:
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.assertNotNull;@SpringBootTest public class UserServiceTest {@Autowiredprivate ApplicationContext applicationContext;@Testpublic void testGetBean() {UserService userService = applicationContext.getBean(UserService.class);assertNotNull(userService);userService.sayHello();} }
在这个测试类中,通过 @SpringBootTest 注解启动 Spring 应用上下文,然后将 ApplicationContext 注入到测试类中,进而可以获取到 UserService 的 Bean 实例并进行测试。这种方式在测试场景下为我们验证 Bean 的功能提供了便利。
四、ApplicationContext 与 BeanFactory 获取 Bean 的对比
实例化时机:ApplicationContext 在容器启动时会实例化所有单例 Bean,而 BeanFactory 是在调用 getBean 方法时才实例化 Bean。
功能丰富度:ApplicationContext 提供了更多的功能,如国际化、资源访问、事件发布等,而 BeanFactory 主要专注于 Bean 的基本管理功能。
应用场景:ApplicationContext 适用于大多数企业级应用场景,尤其是对功能完整性要求较高的项目;BeanFactory 则适用于对资源消耗敏感、功能需求简单的轻量级应用或特定的底层框架开发。
五、其他获取 Bean 的方式
5.1 使用 BeanLocatorFactoryBean
BeanLocatorFactoryBean是 Spring 提供的一个特殊的工厂 Bean,它可以通过指定的名称从当前或父容器中获取 Bean。首先,在配置文件中定义BeanLocatorFactoryBean:
<bean id="beanLocator" class="org.springframework.beans.factory.config.BeanLocatorFactoryBean"><property name="mappedName" value="userService"/> </bean>
然后,在代码中获取beanLocator,实际上它返回的就是userService的 Bean 实例:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = (UserService) applicationContext.getBean("beanLocator");userService.sayHello();} }
5.2 使用 ObjectFactory 接口
ObjectFactory接口提供了一种延迟获取 Bean 的方式,特别适用于那些创建成本较高且可能不一定需要使用的 Bean。定义一个ObjectFactory:
import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;@Configuration public class AppConfig {@Beanpublic ObjectFactory<UserService> userServiceObjectFactory() {return () -> new UserService();} }
在需要使用UserService的地方,通过注入ObjectFactory<UserService>来获取 Bean 实例,只有在调用getObject()方法时才会创建 Bean:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;@Service public class AnotherService {private final ObjectFactory<UserService> userServiceObjectFactory;@Autowiredpublic AnotherService(ObjectFactory<UserService> userServiceObjectFactory) {this.userServiceObjectFactory = userServiceObjectFactory;}public void doSomething() {UserService userService = userServiceObjectFactory.getObject();userService.sayHello();} }
3.3 使用 BeanFactoryAware 和 ApplicationContextAware 接口
实现BeanFactoryAware接口可以让一个 Bean 获取到所在的BeanFactory,从而通过BeanFactory来获取其他 Bean。同样,实现ApplicationContextAware接口可以获取到ApplicationContext。
先定义一个实现BeanFactoryAware接口的类:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.stereotype.Component;@Component public class BeanFactoryAwareComponent implements BeanFactoryAware {private BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}public void doSomething() {UserService userService = (UserService) beanFactory.getBean("userService");userService.sayHello();} }
再定义一个实现ApplicationContextAware接口的类:
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component;@Component public class ApplicationContextAwareComponent implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public void doSomething() {UserService userService = applicationContext.getBean(UserService.class);userService.sayHello();} }
通过实现这些接口,在 Bean 初始化时 Spring 会自动注入对应的BeanFactory或ApplicationContext,方便在类内部获取其他 Bean。
六、总结
在 Spring 开发过程中,除了依赖注入这种常用方式外,ApplicationContext 和 BeanFactory 为我们提供了多种获取 Bean 的有效途径。ApplicationContext 凭借其丰富的功能和强大的特性,在众多场景中发挥着重要作用。而 BeanFactory 的延迟加载和轻量级特性,也在特定场景下展现出独特优势。此外,在 Web 项目以及测试场景中,我们也有相应的方式高效获取 Bean。开发者应根据项目的实际需求,合理选择合适的方式来获取 Bean,从而实现高效、稳定的应用开发。通过本文的详细讲解,希望能帮助大家更好地理解和掌握 Spring 中除注入外获取 Bean 的多种关键方式。