• spring扩展点之四:Spring Aware容器感知技术,BeanNameAware和BeanFactoryAware接口,springboot中的EnvironmentAware


    aware:英 [əˈweə(r)] 美 [əˈwer] adj.意识到的;知道的;觉察到的

    XXXAware在spring里表示对XXX感知,实现XXXAware接口,并通过实现对应的set-XXX方法,然后就可以使用XXX了。

    通俗的解释:如果在某个类里面想要使用spring的一些东西,就可以通过实行XXXAware接口告诉spring,spring会到最后给你送过来,而接收的方式是通过实现接口唯一的方法set-XXX。比如,有一个类想要使用当前的ApplicationContext,那么我们只需要让它实现ApplicationContextAware接口,然后实现接口中唯一的方法void setApplicationContext(ApplicationContext applicationContext)就可以了,spring会自动调用这个方法将applicationContext传给我们,我们只需要接收就可以了。

    -----spring的回调过程分析----------------------------------------------------------

    这个setApplicationContext方法的回调是容器自动完成的,容器调用该方法的时候,我们就可以将容器传入的参数applicationContext保存起来以供使用

    setApplicationContext方法被容器的自动调用是在BeanPostProcessor接口的方法postProcessBeforeInitialization中完成的,实现是在ApplicationContextAwareProcessor类中,具体代码如下:

    spring-context-4.3.14.RELEASE-sources.jar

    package org.springframework.context.support;
    class ApplicationContextAwareProcessor implements BeanPostProcessor {
        //...
    
        //这里就是容器自动调用set方法的地方,其实就是调用自定义的类实现的setApplicationContext方法,这样类就获取到了applicationContext
        private void invokeAwareInterfaces(Object bean) {
            if (bean instanceof Aware) {
                if (bean instanceof EnvironmentAware) {
                    ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
                }
                if (bean instanceof EmbeddedValueResolverAware) {
                    ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
                }
                if (bean instanceof ResourceLoaderAware) {
                    ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
                }
                if (bean instanceof ApplicationEventPublisherAware) {
                    ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
                }
                if (bean instanceof MessageSourceAware) {
                    ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
                }
                if (bean instanceof ApplicationContextAware) {
                    ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
                }
            }
        }
        //...
    }

    -----spring的回调过程分析----------------------------------------------------------

    一. 点睛

    Spring的依赖注入的最大亮点就是你所有的BeanSpring容器的存在是没有意识的。即你可以将你的容器替换成别的容器,例如Goggle Guice,这时Bean之间的耦合度很低。

    但是在实际的项目中,我们不可避免的要用到Spring容器本身的功能资源,这时候Bean必须要意识到Spring容器的存在,才能调用Spring所提供的资源,这就是所谓的Spring Aware。其实Spring Aware本来就是Spring设计用来框架内部使用的,若使用了Spring Aware,你的Bean将会和Spring框架耦合。

    Spring提供的Aware接口如下表所示:

    Spring提供的Aware接口

    BeanNameAware获得到容器中Bean的名称
    BeanFactoryAware 获得当前bean factory,这样可以调用容器的服务
    ApplicationContextAware* 获得当前application context,这样可以调用容器的服务
    MessageSourceAware 获得message source这样可以获得文本信息
    ApplicationEventPublisherAware 应用事件发布器,可以发布事件
    ResourceLoaderAware 获得资源加载器,可以获得外部资源文件

    Spring Aware的目的是为了让Bean获得Spring容器的服务。因为ApplicationContext接口集成了MessageSource接口,ApplicationEventPublisherAware接口和ResourceLoaderAware接口,所以Bean继承ApplicationContextAware可以获得Spring容器的所有服务,但原则上我们还是用到什么接口就实现什么接口。

    二. 示例

    1. 准备

    org.light4j.sping4.senior.aware包下新建一个test.txt,内容随意,给下面的外部资源加载使用。

    2. Spring Aware演示Bean

    package org.light4j.sping4.senior.aware;
    
    import java.io.IOException;
    
    import org.apache.commons.io.IOUtils;
    import org.springframework.beans.factory.BeanNameAware;
    import org.springframework.context.ResourceLoaderAware;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.stereotype.Service;
    
    @Service
    public class AwareService implements BeanNameAware,ResourceLoaderAware{//
    
        private String beanName;
        private ResourceLoader loader;
    
        @Override
        public void setResourceLoader(ResourceLoader resourceLoader) {//
            this.loader = resourceLoader;
        }
    
        @Override
        public void setBeanName(String name) {//
            this.beanName = name;
        }
    
        public void outputResult(){
            System.out.println("Bean的名称为:" + beanName);
    
            Resource resource = 
                    loader.getResource("classpath:org/light4j/sping4/senior/aware/test.txt");
            try{
    
                System.out.println("ResourceLoader加载的文件内容为: " + IOUtils.toString(resource.getInputStream()));
    
               }catch(IOException e){
                e.printStackTrace();
               }
        }
    }

    代码解释:

    ① 实现BeanNameAware,ResourceLoaderAware接口,获得Bean名称和资源加载的服务。
    ② 实现ResourceLoaderAware需要重写setResourceLoader方法。
    ③ 实现BeanNameAware需要重写setBeanName方法。

    3. 配置类

    package org.light4j.sping4.senior.aware;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    @Configuration
    @ComponentScan("org.light4j.sping4.senior.aware")
    public class AwareConfig {
    
    }

    4. 运行

    package org.light4j.sping4.senior.aware;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class Main {
        public static void main(String[] args) {
    
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AwareConfig.class);
    
            AwareService awareService = context.getBean(AwareService.class);
            awareService.outputResult();
    
            context.close();
        }
    }

    运行结果如下图所示:

    三、Spring的BeanNameAware和BeanFactoryAware接口

    BeanNameAware

    作用:让Bean获取自己在BeanFactory配置中的名字(根据情况是id或者name)。 

    示例:(spring自动调用setBeanName的方法,但是,Bean之中一定要有个String类型变量来保存BeanName的值,这个是在编写Bean代码时有程序员手工完成的,而不是通过什么特殊的配置。)

    public class LogginBean implements BeanNameAware {
      private String beanName = null;
      public void setBeanName(String beanName) {
        this.beanName = beanName;
      }
    }

    Spring自动调用。并且会在Spring自身完成Bean配置之后,且在调用任何Bean生命周期回调(初始化或者销毁)方法之前就调用这个方法。换言之,在程序中使用BeanFactory.getBean(String beanName)之前,Bean的名字就已经设定好了。

    BeanFactoryAware

    作用:让Bean获取配置他们的BeanFactory的引用。

    这个方法可能是在根据某个配置文件创建了一个新工厂之后,Spring才调用这个方法,并把BeanFactory注入到Bean中。 
    让bean获取配置自己的工厂之后,当然可以在Bean中使用这个工厂的getBean()方法,但是,实际上非常不推荐这样做,因为结果是进一步加大Bean与Spring的耦合,而且,能通过DI注入进来的尽量通过DI来注入。 
    当然,除了查找bean,BeanFactory可以提供大量其他的功能,例如销毁singleton模式的Bean。 
    factory.preInstantiateSingletons();方法。preInstantiateSingletons()方法立即实例化所有的Bean实例,有必要对这个方法和Spring加载bean的机制做个简单说明。 
    方法本身的目的是让Spring立即处理工厂中所有Bean的定义,并且将这些Bean全部实例化。因为Spring默认实例化Bean的情况下,采用的是lazy机制,换言之,如果不通过getBean()方法(BeanFactory或者ApplicationContext的方法)获取Bean的话,那么为了节省内存将不实例话Bean,只有在Bean被调用的时候才实例化他们。

    示例:(非spring容器的对象,调用spring bean)

    @Component
    public class SpringUtil implements ApplicationContextAware {
    
        private static Logger logger = LoggerFactory.getLogger(SpringUtil.class);
        
        /**
         * 当前IOC
         */
        private static ApplicationContext applicationContext;
    
        /*
         * @param arg0
         * 
         * @throws BeansException
         * 
         * @see
         * org.springframework.context.ApplicationContextAware#setApplicationContext
         * (org.springframework.context.ApplicationContext)
         */
        @Override
        public void setApplicationContext(ApplicationContext arg0) throws BeansException {
            applicationContext = arg0;
        }
        
        /**
         * 通过name获取 Bean.
         * @param name
         * @return
         */
        public static Object getBean(String name){
            return applicationContext.getBean(name);
        }
    
        public static <T>T getBean(String id,Class<T> type){        
            return  applicationContext.getBean(id,type);
        }
    }

    四、EnvironmentAware接口的作用

    在SpringBoot中的应用

    凡注册到Spring容器内的bean,实现了EnvironmentAware接口重写setEnvironment方法后,在工程启动时可以获得application.properties的配置文件配置的属性值。

    demo演示

    直接上代码,比如我的application.properties文件有如下配置(这里说明一下SpringBoot应用默认的配置文件名就叫做application.properties,可以直接放在当前项目的根目录下,或者一个名叫config的子目录下) 
    这里写图片描述 
    再建一个类实现EnvironmentAware接口,其中@Configuration注解在SpringBoot里面相当于Spring的XML文件里的beans标签一样,而@Bean注解相当于XML文件里的bean标签,代表该类会被加载到Spring的IOC容器内。具体代码如下

     /**
      * * 
      * @ClassName MyProjectc.java
      * @author 沉鱼
      * @date 2017年11月28日 下午4:35:39
      */
     @Configuration
     public class MyProjectc implements EnvironmentAware {
    
        @Override
        public void setEnvironment(Environment environment) {
                String projectName =      environment.getProperty("project.name");
                System.out.println(projectName);
        }
     } 

    启动SpringBoot后,在控制台会打印 

    具体SpirngBoot整合Mybatis应用

    application.properties文件配置

    datasource.driverClassName=com.mysql.jdbc.Driver
    datasource.url=jdbc:mysql://localhost:3306/myproject?characterEncoding=utf8&serverTimezone=UTC
    datasource.username=chenyu
    datasource.password=123456

    具体javaConfig代码

     @Configuration 
      public class MyBatisConfig implements EnvironmentAware {
          private Environment environment;
         @Override
         public void setEnvironment(final Environment environment) {
            this.environment = environment;
        }
        /**
         * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名       称,该名称也就是数据源的名称)
         */
        @Bean
        public DataSource druidDataSource() throws Exception {
            Properties props = new Properties();
            props.put("driverClassName", environment.getProperty("datasource.driverClassName"));
            props.put("url", environment.getProperty("datasource.url"));
            props.put("username", environment.getProperty("datasource.username"));
            props.put("password", environment.getProperty("datasource.password"));
            return DruidDataSourceFactory.createDataSource(props);
        }
        /**
         * 根据数据源创建SqlSessionFactory
         */
        @Bean
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
            // 指定数据源(这个必须有,否则报错)
            fb.setDataSource(druidDataSource());
            fb.setTypeAliasesPackage("com.tf56.pushService.dal.domain");
             // 指定mapper文件          
         fb.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
    return fb.getObject(); } }

     五、从spring源码中看aware接口在bean加载时的调用情况

      if (bean instanceof Aware) {
        if (bean instanceof BeanNameAware) {
          ((BeanNameAware) bean).setBeanName(beanName);
        }
        if (bean instanceof BeanClassLoaderAware) {
          ((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
        }
        if (bean instanceof BeanFactoryAware) {
          ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
        }
      }
    }
  • 相关阅读:
    10年后编程还有意义吗?
    专访Jeffrey Richter:Windows 8是微软的重中之重
    x86汇编指令脚本虚拟机
    基于容器的持续交付管道
    NET Core 整合Autofac和Castle
    数据结构与算法1
    Redis集群
    react + iscroll5
    MongoDB
    WebComponent
  • 原文地址:https://www.cnblogs.com/duanxz/p/3724429.html
Copyright © 2020-2023  润新知