實力總結四類Bean注入Spring的方式

原創:微信公衆號 【阿Q說代碼】,歡迎分享,轉載請保留出處。

一提到Spring,大家最先想到的是啥?是AOPIOC的兩大特性?是SpringBean的初始化流程?還是基於SpringSpring Cloud全家桶呢?

今天我們就從SpringIOC特性入手,聊一聊Spring中把Bean注入Spring容器的幾種方式。

我們先來簡單瞭解下IOC的概念:IOC控制反轉,也稱爲依賴注入,是指將對象的創建或者依賴關係的引用從具體的對象控制轉爲框架或者IOC容器來完成,也就是依賴對象的獲得被反轉了。

可以簡單理解爲原來由我們來創建對象,現在由Spring來創建並控制對象。

xml 方式

依稀記得最早接觸Spring的時候,用的還是SSH框架,不知道大家對這個還有印象嗎?所有的bean的注入得依靠xml文件來完成。

它的注入方式分爲:set方法注入、構造方法注入、字段注入,而注入類型分爲值類型注入(8種基本數據類型)和引用類型注入(將依賴對象注入)。

以下是set方法注入的簡單樣例

<bean name="teacher" class="org.springframework.demo.model.Teacher">
    <property name="name" value="阿Q"></property>
</bean>

對應的實體類代碼

public class Teacher {

    private String name;

    public void setName(String name) {
        this.name = name;
    }
}

xml方式存在的缺點如下:

  1. xml文件配置起來比較麻煩,既要維護代碼又要維護配置文件,開發效率低;
  2. 項目中配置文件過多,維護起來比較困難;
  3. 程序編譯期間無法對配置項的正確性進行驗證,只能在運行期發現並且出錯之後不易排查;
  4. 解析xml時,無論是將xml一次性裝進內存,還是一行一行解析,都會佔用內存資源,影響性能。

註解方式

隨着Spring的發展,Spring 2.5開始出現了一系列註解,除了我們經常使用的@Controller、@Service、@Repository、@Component 之外,還有一些比較常用的方式,接下來我們簡單瞭解下。

@Configuration + @Bean

當我們需要引入第三方的jar包時,可以用@Bean註解來標註,同時需要搭配@Configuration來使用。

  • @Configuration用來聲明一個配置類,可以理解爲xml<beans>標籤

  • @Bean 用來聲明一個bean,將其加入到Spring容器中,可以理解爲xml<bean>標籤

簡單樣例:將 RedisTemplate 注入 Spring

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        ......
        return redisTemplate;
    }
}

@Import

我們在翻看Spring源碼的過程中,經常會看到@Import註解,它也可以用來將第三方jar包注入Spring,但是它只可以作用在上。

例如在註解EnableSpringConfigured上就包含了@Import註解,用於將SpringConfiguredConfiguration配置文件加載進Spring容器。

@Import(SpringConfiguredConfiguration.class)
public @interface EnableSpringConfigured {}

@Importvalue值是一個數組,一個一個注入比較繁瑣,因此我們可以搭配ImportSelector接口來使用,用法如下:

@Configuration
@Import(MyImportSelector.class)
public class MyConfig {}

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"org.springframework.demo.model.Teacher","org.springframework.demo.model.Student"};
    }
}

其中selectImports方法返回的數組就會通過@Import註解注入到Spring容器中。

無獨有偶,ImportBeanDefinitionRegistrar接口也爲我們提供了注入bean的方法。

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    ......
}

我們點擊AspectJAutoProxyRegistrar類,發現它實現了ImportBeanDefinitionRegistrar接口,它的registerBeanDefinitions方法便是注入bean的過程,可以參考下。

如果覺得源代碼比較難懂,可以看一下我們自定義的類

@Configuration
@Import(value = {MyImportBeanDefinitionRegistrar.class})
public class MyConfig {}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
            RootBeanDefinition tDefinition = new RootBeanDefinition(Teacher.class);
            // 註冊 Bean,並指定bean的名稱和類型
            registry.registerBeanDefinition("teacher", tDefinition);
        }
    }
}

這樣我們就把Teacher類注入到Spring容器中了。

FactoryBean

提到FactoryBean,就不得不與BeanFactory比較一番。

  • BeanFactory : 是 FactoryIOC容器或者對象工廠,所有的Bean都由它進行管理
  • FactoryBean : 是Bean ,是一個能產生或者修飾對象生成的工廠 Bean,實現與工廠模式和修飾器模式類似

那麼FactoryBean是如何實現bean注入的呢?

先定義實現了FactoryBean接口的類

public class TeacherFactoryBean implements FactoryBean<Teacher> {

    /**
     * 返回此工廠管理的對象實例
     **/
    @Override
    public Teacher getObject() throws Exception {
        return new Teacher();
    }

    /**
     * 返回此 FactoryBean 創建的對象的類型
     **/
    @Override
    public Class<?> getObjectType() {
        return Teacher.class;
    }

}

然後通過 @Configuration + @Bean的方式將TeacherFactoryBean加入到容器中

@Configuration
public class MyConfig {
    @Bean
    public TeacherFactoryBean teacherFactoryBean(){
        return new TeacherFactoryBean();
    }
}

注意:我們沒有向容器中注入Teacher, 而是直接注入的TeacherFactoryBean,然後從容器中拿Teacher這個類型的bean,成功運行。

BDRegistryPostProcessor

看到這個接口,不知道對於翻看過Spring源碼的你來說熟不熟悉。如果不熟悉的話請往下看,要是熟悉的話就再看一遍吧😃。

源碼

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    // 註冊bean到spring容器中
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor接口是BeanFactory的後置處理器,方法postProcessBeanFactorybean的定義進行控制。今天我們重點來看看postProcessBeanDefinitionRegistry方法:它的參數是BeanDefinitionRegistry,顧名思義就是與BeanDefinition註冊相關的。

通過觀察該類,我們發現它裏邊包含了registerBeanDefinition方法,這個不就是我們想要的嗎?爲了能更好的使用該接口來達到注入bean的目的,我們先來看看Spring是如何操作此接口的。

看下invokeBeanFactoryPostProcessors方法,會發現沒有實現PriorityOrderedOrderedbean(這種跟我們自定義的實現類有關)會執行以下代碼。

while (reiterate) {
    ......
    invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
    ......
}

進入該方法

private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, 
    BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

會發現實現了BeanDefinitionRegistryPostProcessor接口的bean,其postProcessBeanDefinitionRegistry方法會被調用,也就是說如果我們自定義接口實現該接口,它的postProcessBeanDefinitionRegistry方法也會被執行。

實戰

話不多說,直接上代碼。自定義接口實現類

public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    /**
     * 初始化過程中先執行
     **/
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Teacher.class);
        //Teacher 的定義註冊到spring容器中
        registry.registerBeanDefinition("teacher", rootBeanDefinition);
    }

    /**
     * 初始化過程中後執行
     **/
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

啓動類代碼

public static void main(String[] args) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    MyBeanDefinitionRegistryPostProcessor postProcessor = new MyBeanDefinitionRegistryPostProcessor();
    //將自定義實現類加入 Spring 容器
    context.addBeanFactoryPostProcessor(postProcessor);
    context.refresh();
    Teacher bean = context.getBean(Teacher.class);
    System.out.println(bean);
}

啓動並打印結果

org.springframework.demo.model.Teacher@2473d930

發現已經注入到Spring容器中了。

以上就是我們總結的幾種將bean注入Spring容器的方式,趕快行動起來實戰演練一下吧!

阿Q將持續更新java實戰方面的文章,感興趣的可以關注下,也可以來技術羣討論問題呦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章