简单了解下Spring中的各种Aware接口实现依赖注入
你好,这里是codetrend专栏“Spring6全攻略”。
在Spring框架中,Aware接口是一组用于提供特定资源或环境信息的回调接口。这些接口被设计用来允许Bean获取对Spring容器或其他相关资源的引用,并在需要时进行适当的处理。
Aware接口的设计是为了让Bean能够感知到其所处的环境并与之交互。通过实现这些接口,Bean可以获取对Spring容器或其他相关资源的引用,从而能够更好地适应和利用所处的环境。
使用场景
- 获取Spring容器的引用:ApplicationContextAware接口可以让Bean获取对Spring容器的引用,从而能够访问容器中的其他Bean或执行一些特定的操作。
- 资源加载和处理:BeanClassLoaderAware和ResourceLoaderAware接口可以让Bean获取类加载器和资源加载器的引用,用于加载资源文件或进行类加载操作。
- 事件发布和消息处理:ApplicationEventPublisherAware和MessageSourceAware接口可以让Bean获取事件发布器和消息源的引用,用于发布事件或处理国际化消息。
- Web环境处理:ServletConfigAware和ServletContextAware接口可以让Bean获取对Servlet配置和上下文的引用,在Web应用中进行特定的处理。
Aware接口
名称 | 注入的依赖 |
---|---|
ApplicationContextAware(常用) | 声明的 ApplicationContext。 |
ApplicationEventPublisherAware | 包含的 ApplicationContext 的事件发布器。 |
BeanClassLoaderAware | 用于加载 bean 类的类加载器。 |
BeanFactoryAware(常用) | 声明的 BeanFactory。 |
BeanNameAware | 声明 bean 的名称。 |
LoadTimeWeaverAware | 在加载时处理类定义的已定义织入器。 |
MessageSourceAware | 配置用于解析消息的策略(支持参数化和国际化)。 |
NotificationPublisherAware | Spring JMX 通知发布器。 |
ResourceLoaderAware | 配置的加载器,用于低级别访问资源。 |
ServletConfigAware | 容器运行的当前 ServletConfig。仅在 web 感知的 Spring ApplicationContext 中有效。 |
ServletContextAware | 容器运行的当前 ServletContext。仅在 web 感知的 Spring ApplicationContext 中有效。 |
ApplicationContextAware 接口
实现这个接口的Bean可以在其初始化时获取到ApplicationContext,从而能够访问Spring容器中的所有Bean及其配置。具体来说,这个接口主要用于需要与Spring上下文进行交互的场景。
例子代码如下:
class ContextAwareBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void showBeanDetails() {
String[] beanNames = applicationContext.getBeanDefinitionNames();
System.out.println("Bean names in ApplicationContext:");
for (String beanName : beanNames) {
System.out.println(beanName);
}
}
}
注意事项:
- 避免过度依赖ApplicationContext:虽然ApplicationContextAware提供了强大的功能,但应谨慎使用,以避免过度依赖Spring上下文,导致代码难以测试和维护。
- Bean生命周期:确保在Bean初始化完成后再调用依赖ApplicationContext的方法,否则可能会遇到空指针异常(NullPointerException)。
- 测试困难:由于ApplicationContextAware直接依赖于Spring容器,在单元测试中模拟这些依赖可能会比较复杂。
ApplicationEventPublisherAware 接口
ApplicationEventPublisherAware接口允许Bean获取到ApplicationEventPublisher实例,以便能够发布事件。通过实现该接口,Bean可以在运行时向应用程序上下文发布自定义事件或标准Spring事件。
例子代码如下:
@Getter
class CustomEvent extends ApplicationEvent {
private final String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
}
/**
* 事件发布
*/
@Component
class EventPublisherBean implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void publishCustomEvent(final String message) {
CustomEvent customEvent = new CustomEvent(this, message);
applicationEventPublisher.publishEvent(customEvent);
}
}
/**
* 事件监听
*/
@Component
class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("Received custom event - " + event.getMessage());
}
}
测试代码输出如下:
Received custom event - Hello, Spring Events!
注意事项:
- 避免过度依赖:尽量将事件发布逻辑与业务逻辑分离,遵循单一职责原则,避免过度依赖Spring的事件机制,从而保持代码的可维护性和可测试性。
- 异步事件处理:在需要异步处理事件的场景下,可以结合Spring的异步支持(如@Async注解)来提高性能。
- 事件传播:注意事件的传播范围和生命周期,确保事件发布和处理的顺序和时机符合业务需求。
BeanClassLoaderAware 接口
通过实现这个接口,Bean可以在其生命周期内访问加载它的类加载器,从而进行一些需要类加载器操作的任务。
实现demo如下:
/**
* 说明:BeanClassLoaderAware demo
* @since 2024/6/13
* @author sunz
*/
public class ClassLoaderAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
ClassLoaderAwareBean classLoaderAwareBean = context.getBean(ClassLoaderAwareBean.class);
classLoaderAwareBean.performClassLoadingTask("org.springframework.beans.factory.BeanClassLoaderAware");
// 销毁容器
context.close();
}
}
@Component
class ClassLoaderAwareBean implements BeanClassLoaderAware {
private ClassLoader classLoader;
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
System.out.println("ClassLoader has been set.");
}
public void performClassLoadingTask(String clsName) {
try {
// 例如动态加载一个类
Class<?> loadedClass = classLoader.loadClass(clsName);
System.out.println("Class loaded: " + loadedClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果如下:
ClassLoader has been set.
Class loaded: org.springframework.beans.factory.BeanClassLoaderAware
注意事项:
- 避免过度使用:虽然BeanClassLoaderAware接口提供了类加载器的访问能力,但应谨慎使用,避免在业务逻辑中过度依赖类加载器,保持代码的简洁和可维护性。
- 类加载器隔离:在复杂的应用场景中,特别是涉及模块化或插件化的系统中,不同模块可能会使用不同的类加载器。确保正确理解和管理类加载器的隔离和作用范围。
- 错误处理:在动态加载类或资源时,应注意处理可能的异常情况,例如类未找到(ClassNotFoundException)或资源不存在等。
BeanFactoryAware 接口
通过实现这个接口,Bean 可以在自身的生命周期中访问 Spring 容器,从而动态地获取其他 Bean 或者进行一些容器级别的操作。
比如写个策略模式,通过策略动态获取bean就可能用到。
一般场景如下:
- 动态获取 Bean: 虽然依赖注入已经非常强大,但在某些情况下,可能需要动态获取 Bean。例如,根据运行时条件选择性地创建或获取不同类型的 Bean。
- 容器级别操作: 有时需要执行一些高级操作,如检查 Bean 的存在性、获取 Bean 的定义信息等。
Demo代码如下:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
/**
* 说明:BeanFactoryAware 例子
* @since 2024/6/13
* @author sunz
*/
public class BeanFactoryAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
MyBeanFactoryAware bean = context.getBean(MyBeanFactoryAware.class);
bean.doSomething();
// 销毁容器
context.close();
}
}
@Component
class MyBeanFactoryAware implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void doSomething() {
// 现在可以使用 beanFactory 来获取其他的 Bean
MyService myService = beanFactory.getBean(MyService.class);
myService.performAction();
}
}
@Component
class MyService {
public void performAction() {
System.out.println("hello world");
}
}
注意事项:
- 避免滥用: 过度依赖 BeanFactoryAware 可能导致代码与 Spring 框架紧密耦合,降低代码的可测试性和灵活性。应该尽量使用依赖注入来代替直接访问 BeanFactory。
- 单例模式: 如果 Bean 是单例的,那么它所持有的 BeanFactory 也是单例的。这意味着同一个 BeanFactory 实例会被多个单例 Bean 共享。
BeanNameAware 接口
实现这个接口的 Bean 对象在被 Spring 容器实例化后,能够获取到自己在容器中的名称。
这在某些情况下可能会非常有用,例如在调试、日志记录或需要根据 Bean 名称执行特定逻辑时。
一般应用场景:
- 调试和日志记录: 在开发和维护过程中,知道 Bean 的名称可以帮助调试和记录日志。
- 动态处理逻辑: 某些情况下,业务逻辑可能需要根据 Bean 的名称来进行不同的处理。
demo代码如下:
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;
/**
* 说明: BeanNameAware 例子
*
* @author sunz
* @since 2024/6/13
*/
public class BeanNameAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
MyBeanNameAware bean = context.getBean(MyBeanNameAware.class);
bean.setBeanName("hello,jack");
bean.printBeanName();
// 销毁容器
context.close();
}
}
@Component
class MyBeanNameAware implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
public void printBeanName() {
System.out.println("Bean name is: " + beanName);
}
}
注意事项:
- 避免过度依赖: 类似于其他 Aware 接口,过度依赖 BeanNameAware 可能导致代码与 Spring 框架紧密耦合,降低代码的可测试性和灵活性。应尽可能使用依赖注入和其他更解耦的设计模式。
LoadTimeWeaverAware 接口
实现这个接口的 Bean 在被 Spring 容器实例化后,能够获取到一个 LoadTimeWeaver
实例。LoadTimeWeaver 是用于在类加载时进行字节码增强的重要机制之一,比如在 AOP(面向切面编程)中动态地织入横切关注点。
代码没什么实际使用场景,开源项目搜索也没发现使用例子,实际使用还是比较少。
不推荐使用,使用AOP还是直接用spring aop即可。
实际使用代码和之前的Aware是一致的,只要实现接口即可。
class MyLoadTimeWeaverAware implements LoadTimeWeaverAware {
private LoadTimeWeaver loadTimeWeaver;
@Override
public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
this.loadTimeWeaver = loadTimeWeaver;
}
public void doSomething() {
// 使用 loadTimeWeaver 进行字节码增强等操作
// 这里仅作为示例,不进行实际操作
System.out.println("LoadTimeWeaver is set and can be used now.");
}
}
MessageSourceAware 接口
MessageSourceAware 是 Spring 框架中的一个接口,用于实现国际化(i18n)的功能。它允许一个 bean 接收 MessageSource 对象,从而能够在应用程序中访问国际化的消息资源。
实现 MessageSourceAware 接口的类可以直接使用 MessageSource 来获取国际化的消息,而不必显式地在其配置中注入 MessageSource bean。
这对于需要频繁访问国际化消息的 bean 非常有用。
例子代码如下:
/**
* 说明:MessageSourceAware demo
* @since 2024/6/13
* @author sunz
*/
public class MessageSourceAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppMessageConfig.class);
GreetingService bean = context.getBean(GreetingService.class);
String greetingEn = bean.getGreeting("John", Locale.ENGLISH);
System.out.println(greetingEn); // 输出: Hello, John!
String greetingZh = bean.getGreeting("李雷", Locale.CHINESE);
System.out.println(greetingZh); // 输出: 你好, 李雷!
// 销毁容器
context.close();
}
}
/**
* 配置类
*/
@Configuration
@ComponentScan(basePackages = "io.yulin.learn.spring.s108")
class AppMessageConfig {
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
/**
* 服务bean
*/
@Component
class GreetingService implements MessageSourceAware {
private MessageSource messageSource;
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getGreeting(String name, Locale locale) {
return messageSource.getMessage("greeting", new Object[]{name}, locale);
}
}
其中资源文件如下:
<!-- messages_en.properties -->
greeting=Hello, {0}!
<!-- messages_zh.properties -->
greeting=你好, {0}!
一般情况下使用多语言工具可以进行二次封装,使得使用起来更简洁方便,而不是每次进行注入。
以下是一个封装的例子:
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import java.util.Locale;
@Component
public class MessageUtils {
private static MessageSource messageSource;
// Spring 会自动注入该方法中的参数 messageSource
public MessageUtils(MessageSource messageSource) {
MessageUtils.messageSource = messageSource;
}
/**
* 获取国际化消息
*
* @param code 消息代码
* @param args 消息参数
* @return 国际化消息
*/
public static String getMessage(String code, Object... args) {
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(code, args, locale);
}
}
NotificationPublisherAware 接口
NotificationPublisherAware 是 Spring 框架中的一个接口,用于与 JMX(Java Management Extensions)相关的通知机制进行集成。实现该接口的 bean 可以通过 Spring 容器获得一个 NotificationPublisher 实例,从而能够发布 JMX 通知。
在需要发布 JMX 通知的 Spring 管理的 bean 中,实现 NotificationPublisherAware 接口可以方便地获取 NotificationPublisher 并发布通知。这通常用于监控和管理应用程序状态变化,如资源使用情况、性能指标等。
例子代码如下:
/**
* 说明:NotificationPublisherAware demo
* @since 2024/6/13
* @author sunz
*/
public class NotificationPublisherAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppNotifyConfig.class);
MyManagedBean bean = context.getBean(MyManagedBean.class);
bean.doSomething();
// 销毁容器
context.close();
}
}
@Configuration
@ComponentScan(basePackages = "io.yulin.learn.spring.s108")
class AppNotifyConfig{
@Bean
public MBeanExporter mBeanExporter() {
MBeanExporter exporter = new MBeanExporter();
exporter.setRegistrationPolicy(RegistrationPolicy.REPLACE_EXISTING);
Map<String, Object> beans = new HashMap<>();
beans.put("bean:name=myManagedBean", myManagedBean());
exporter.setBeans(beans);
return exporter;
}
@Bean
public MyManagedBean myManagedBean() {
return new MyManagedBean();
}
}
/**
* 服务bean
*/
@Component
class MyManagedBean extends NotificationBroadcasterSupport implements MyManagedBeanMBean,NotificationPublisherAware {
private NotificationPublisher notificationPublisher;
@Override
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.notificationPublisher = notificationPublisher;
}
@Override
public void doSomething() {
// 执行某些操作
System.out.println("Doing something...");
// 发布通知
Notification notification = new Notification(
"myNotificationType",
this,
System.currentTimeMillis(),
"Something happened!"
);
if (this.notificationPublisher != null) {
notificationPublisher.sendNotification(notification);
} else {
// 使用 NotificationBroadcasterSupport 自带的方法
sendNotification(notification);
}
}
}
JMX接收和监听比较复杂,这里只是使用了NotificationPublisher进行推送通知消息。
ResourceLoaderAware 接口
实现 ResourceLoaderAware 接口的类会在其被 Spring 容器管理时自动获得一个 ResourceLoader 实例。通过这个实例,类可以方便地加载各种类型的资源(如文件系统、类路径、URL 等)。
通常在需要访问外部资源(例如文件、配置文件、图片等)的类中,可以实现 ResourceLoaderAware 接口。这比直接依赖 File 或其他资源加载机制更灵活,因为 ResourceLoader 可以处理多种类型的资源路径(如类路径、文件系统路径、URL 等)。
以下是一个简单的示例,展示了如何实现 ResourceLoaderAware 并使用 ResourceLoader 加载文本文件。
/**
* 说明:ResourceLoaderAware demo
* @since 2024/6/14
* @author sunz
*/
public class ResourceLoaderAwareDemo {
public static void main(String[] args) {
// 创建一个基于 Java Config 的应用上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppScanConfig.class);
MyResourceLoaderAwareBean bean = context.getBean(MyResourceLoaderAwareBean.class);
// 假设 "test.txt" 位于 classpath 路径下
bean.loadResource("classpath:application.yml");
// 销毁容器
context.close();
}
}
/**
* 服务bean
*/
@Component
class MyResourceLoaderAwareBean implements ResourceLoaderAware {
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void loadResource(String location) {
try {
Resource resource = resourceLoader.getResource(location);
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
ServletConfigAware 接口
实现 ServletConfigAware 接口的类会在其被 Spring 容器管理时自动获得一个 ServletConfig 实例。通过这个实例,类可以方便地获取关于当前 Servlet 的配置信息。
使用demo如下:
/**
* 服务bean
*/
@RestController
@RequestMapping("/test")
public class MyServletConfigAwareBean implements ServletConfigAware {
private ServletConfig servletConfig;
@Override
public void setServletConfig(ServletConfig servletConfig) {
this.servletConfig = servletConfig;
}
@GetMapping("/demo")
public void printServletInfo() {
String servletName = servletConfig.getServletName();
String initParamValue = servletConfig.getInitParameter("myInitParam");
String contextPath = servletConfig.getServletContext().getContextPath();
System.out.println("Servlet Name: " + servletName);
System.out.println("Init Param Value: " + initParamValue);
System.out.println("Context Path: " + contextPath);
}
}
实际验证会发现servletConfig
报空指针异常,这块是因为没初始化相关的配置导致的。
ServletContextAware 接口
ServletContextAware 接口是 Spring 框架中的一个接口,用于让实现它的类获取当前 Servlet 上下文(ServletContext)的引用。通过实现这个接口,类可以在 Spring 容器初始化时自动获取 Servlet 上下文对象,从而进行一些与 Servlet 相关的操作。
使用demo如下:
@RestController
@RequestMapping("/test2")
public class MyServletContextAwareBean implements ServletContextAware {
private ServletContext servletContext;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
// 可以添加其他方法来使用 servletContext 对象
@GetMapping("/demo")
public void printServletInfo() {
System.out.println("ServletContext: " + servletContext);
System.out.println("ServletContext name: " + servletContext.getServletContextName());
System.out.println("ServletContext context path: " + servletContext.getContextPath());
System.out.println("ServletContext server info: " + servletContext.getServerInfo());
System.out.println("ServletContext major version: " + servletContext.getMajorVersion());
System.out.println("ServletContext minor version: " + servletContext.getMinorVersion());
}
}
ServletContext 常用的场景如下:
- 获取上下文路径和真实路径:可以获取当前 web 应用的上下文路径和部署后的真实路径。
- 执行一些 Servlet 上下文管理的操作:如获取当前已经初始化的 Servlet、获取 Servlet 的名称等。
- 与会话管理相关的操作:可以通过 ServletContext 获取应用的会话管理器,并进行相关的会话操作。
点击话题和专栏可以看更多往期文章。
关于作者
来自一线全栈程序员nine的探索与实践,持续迭代中。
欢迎关注、评论、点赞。