简单了解下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 常用的场景如下:

  1. 获取上下文路径和真实路径:可以获取当前 web 应用的上下文路径和部署后的真实路径。
  2. 执行一些 Servlet 上下文管理的操作:如获取当前已经初始化的 Servlet、获取 Servlet 的名称等。
  3. 与会话管理相关的操作:可以通过 ServletContext 获取应用的会话管理器,并进行相关的会话操作。

点击话题和专栏可以看更多往期文章。

关于作者

来自一线全栈程序员nine的探索与实践,持续迭代中。

欢迎关注、评论、点赞。

posted @ 2024-08-03 17:07  落叶微风  阅读(45)  评论(0编辑  收藏  举报