面了幾百人這些問題你是第一個讓我比較滿意的且超出了預期



月薪5萬,恭喜你,面了幾百人,這些問題你是第一個讓我比較滿意的,且超出了預期!

路人甲Java 今天

來看一下月薪5萬的面試題:

  1. @Import你用過麼?是做什麼的?
  2. @Import使用有幾種方式?有何區別?
  3. DeferredImportSelector是做什麼的?他和ImportSelector有什麼區別?
  4. 可以介紹介紹一下spring中哪些功能是通過@Import來實現的?
  5. 可以介紹一下spring中是如何解析@Import註解的麼?

@Import出現的背景

目前為止,註解的方式批量註冊bean,前面2篇文章中,我們介紹了2種方式:

到目前,我們知道的批量定義bean的方式有2種:

  1. @Configuration結合@Bean註解的方式
  2. @CompontentScan掃描包的方式

下面我們來看幾個問題。

問題1

如果需要註冊的類是在第三方的jar中,那麼我們如果想註冊這些bean有2種方式:

  1. 通過@Bean標註方法的方式,一個個來註冊
  2. @CompontentScan的方式:默認的@CompontentScan是無能為力的,默認情況下只會註冊@Compontent標註的類,此時只能自定義@CompontentScan中的過濾器來實現了

這2種方式都不是太好,每次有變化,調整的代碼都比較多。

問題2

通常我們的項目中有很多子模塊,可能每個模塊都是獨立開發的,最後通過jar的方式引進來,每個模塊中都有各自的@Configuration、@Bean標註的類,或者使用@CompontentScan標註的類,被@Configuration、@Bean、@CompontentScan標註的類,我們統稱為bean配置類,配置類可以用來註冊bean

,此時如果我們只想使用其中幾個模塊的配置類,怎麼辦?

@Import可以很好的解決這2個問題,下面我們來看@Import怎麼玩的。

@Import使用

先看Spring對它的註釋,總結下來作用就是和xml配置的 <import>標籤作用一樣,允許通過它引入@Configuration標註的類 , 引入ImportSelector接口和ImportBeanDefinitionRegistrar接口的實現,也包括 @Component註解的普通類。

總的來說:@Import可以用來批量導入需要註冊的各種類,如普通的類、配置類,完後完成普通類和配置類中所有bean的註冊。

@Import的源碼:

<code>@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class>[] value();

}/<code>

@Import可以使用在任何類型上,通常情況下,類和註解上用的比較多。

value:一個Class數組,設置需要導入的類,可以是@Configuration標註的列,可以是ImportSelector接口或者ImportBeanDefinitionRegistrar接口類型的,或者需要導入的普通組件類。

使用步驟

  1. 將@Import標註在類上,設置value參數
  2. 將@Import標註的類作為AnnotationConfigApplicationContext構造參數創建AnnotationConfigApplicationContext對象
  3. 使用AnnotationConfigApplicationContext對象

@Import的value常見的有5種用法

  1. value為普通的類
  2. value為@Configuration標註的類
  3. value為@CompontentScan標註的類
  4. value為ImportBeanDefinitionRegistrar接口類型
  5. value為ImportSelector接口類型
  6. value為DeferredImportSelector接口類型

下面我們通過案例來一個個詳細介紹。

value為普通的類

來2個類

Service1

<code>package com.javacode2018.lesson001.demo24.test1;

public class Service1 {
}/<code>

Service2

<code>package com.javacode2018.lesson001.demo24.test1;

public class Service2 {
}/<code>

總配置類:使用@Import標註

<code>package com.javacode2018.lesson001.demo24.test1;

import org.springframework.context.annotation.Import;

@Import({Service1.class, Service2.class})
public class MainConfig1 {
}/<code>

@Import中導入了2個普通的類:Service1、Service2,這兩個類會被自動註冊到容器中

測試用例

<code>package com.javacode2018.lesson001.demo24;

import com.javacode2018.lesson001.demo24.test1.MainConfig1;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ImportTest {
    @Test
    public void test1() {
        //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig1.class);
        //2.輸出容器中定義的所有bean信息
        for (String beanName : context.getBeanDefinitionNames()) {

            System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
        }
    }
}/<code>

運行輸出

部分輸出如下:

<code>com.javacode2018.lesson001.demo24.test1.Service1->com.javacode2018.lesson001.demo24.test1.Service1@7e0b85f9
com.javacode2018.lesson001.demo24.test1.Service2->com.javacode2018.lesson001.demo24.test1.Service2@63355449/<code>

結果分析

從輸出中可以看出:

  1. Service1和Service2成功註冊到容器了。
  2. 通過@Import導入的2個類,bean名稱為完整的類名

我們也可以指定被導入類的bean名稱,使用@Compontent註解就可以了,如下:

<code>@Component("service1")
public class Service1 {
}/<code>

再次運行test1輸出:

<code>service1->com.javacode2018.lesson001.demo24.test1.Service1@45efd90f/<code>

總結一下

按模塊的方式進行導入,需要哪個導入哪個,不需要的時候,直接修改一下總的配置類,調整一下@Import就可以了,非常方便。

value為@Configuration標註的配置類

項目比較大的情況下,會按照模塊獨立開發,每個模塊在maven中就表現為一個個的構建,然後通過座標的方式進行引入需要的模塊。

假如項目中有2個模塊,2個模塊都有各自的配置類,如下

模塊1配置類

<code>package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 模塊1配置類
 */
@Configuration
public class ConfigModule1 {
    @Bean
    public String module1() {
        return "我是模塊1配置類!";
    }
}/<code>

模塊2配置類

<code>package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 模塊2配置類
 */
@Configuration
public class ConfigModule2 {
    @Bean

    public String module2() {
        return "我是模塊2配置類!";
    }
}/<code>

總配置類:通過@Import導入2個模塊的配置類

<code>package com.javacode2018.lesson001.demo24.test2;

import org.springframework.context.annotation.Import;

/**
 * 通過Import來彙總多個@Configuration標註的配置類
 */
@Import({ConfigModule1.class, ConfigModule2.class}) //@1
public class MainConfig2 {
}/<code>

@1導入了2個模塊中的模塊配置類,可以按需導入。

測試用例

ImportTest中新增個方法

<code>@Test
public void test2() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
    //2.輸出容器中定義的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}/<code>

運行輸出

<code>mainConfig2->com.javacode2018.lesson001.demo24.test2.MainConfig2@ba2f4ec
com.javacode2018.lesson001.demo24.test2.ConfigModule1->com.javacode2018.lesson001.demo24.test2.ConfigModule1$$EnhancerBySpringCGLIB$$700e65cd@1c1bbc4e
module1->我是模塊1配置類!
com.javacode2018.lesson001.demo24.test2.ConfigModule2->com.javacode2018.lesson001.demo24.test2.ConfigModule2$$EnhancerBySpringCGLIB$$a87108ee@55fe41ea

module2->我是模塊2配置類!/<code>

value為@CompontentScan標註的類

項目中分多個模塊,每個模塊有各自獨立的包,我們在每個模塊所在的包中配置一個@CompontentScan類,然後通過@Import來導入需要啟用的模塊。

定義模塊1

2個組件和一個組件掃描類,模塊1所有類所在的包為:

<code>com.javacode2018.lesson001.demo24.test3.module1/<code>

組件1:Module1Service1

<code>package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.stereotype.Component;

@Component
public class Module1Service1 {
}/<code>

組件2:Module1Service2

<code>package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.stereotype.Component;

@Component
public class Module1Service2 {
}/<code>

組件掃描類:CompontentScanModule1

負責掃描當前模塊中的組件

<code>package com.javacode2018.lesson001.demo24.test3.module1;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.stereotype.Component;

/**
 * 模塊1的主鍵掃描
 */
@ComponentScan
public class CompontentScanModule1 {
}/<code>

同樣的方式定義模塊2

2個組件和一個組件掃描類,模塊1所有類所在的包為:

<code>com.javacode2018.lesson001.demo24.test3.module2/<code>

組件1:Module2Service1

<code>package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.stereotype.Component;

@Component
public class Module2Service1 {
}/<code>

組件2:Module2Service2

<code>package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.stereotype.Component;

@Component
public class Module2Service2 {
}/<code>

組件掃描類:CompontentScanModule1

負責掃描當前模塊中的組件

<code>package com.javacode2018.lesson001.demo24.test3.module2;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

/**
 * 模塊2的組件掃描
 */
@ComponentScan
public class CompontentScanModule2 {
}/<code>

總配置類:通過@Import導入每個模塊中的組件掃描類

<code>package com.javacode2018.lesson001.demo24.test3;

import com.javacode2018.lesson001.demo24.test3.module1.CompontentScanModule1;
import com.javacode2018.lesson001.demo24.test3.module2.CompontentScanModule2;
import org.springframework.context.annotation.Import;

/**
 * 通過@Import導入多個@CompontentScan標註的配置類
 */
@Import({CompontentScanModule1.class, CompontentScanModule2.class}) //@1
public class MainConfig3 {
}/<code>

@1導入了2個模塊中的組件掃描類,可以按需導入。

測試用例

ImportTest中新增個方法

<code>@Test
public void test3() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
    //2.輸出容器中定義的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}/<code>

運行輸出

部分輸出如下:

<code>module1Service1->com.javacode2018.lesson001.demo24.test3.module1.Module1Service1@5b239d7d
module1Service2->com.javacode2018.lesson001.demo24.test3.module1.Module1Service2@6572421
module2Service1->com.javacode2018.lesson001.demo24.test3.module2.Module2Service1@6b81ce95
module2Service2->com.javacode2018.lesson001.demo24.test3.module2.Module2Service2@2a798d51/<code>

兩個模塊中通過@Compontent定義的4個bean都輸出了。

如果只想註冊模塊1中的bean,只需要修改一下@Import,去掉CompontentScanModule2,如下:

<code>@Import({CompontentScanModule1.class})/<code>

再次運行輸出:

<code>module1Service1->com.javacode2018.lesson001.demo24.test3.module1.Module1Service1@6379eb
module1Service2->com.javacode2018.lesson001.demo24.test3.module1.Module1Service2@294425a7/<code>

此時模塊2的bean就沒有了。

先來了解一下相關的幾個接口

ImportBeanDefinitionRegistrar接口

這個接口提供了通過spring容器api的方式直接向容器中註冊bean

接口的完整名稱:

<code>org.springframework.context.annotation.ImportBeanDefinitionRegistrar/<code>

源碼:

<code>public interface ImportBeanDefinitionRegistrar {

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
            BeanNameGenerator importBeanNameGenerator) {

        registerBeanDefinitions(importingClassMetadata, registry);
    }

    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    }

}/<code>

2個默認方法,都可以用來調用spring容器api來註冊bean。

2個方法中主要有3個參數

importingClassMetadata

AnnotationMetadata類型的,通過這個可以獲取被@Import註解標註的類所有註解的信息。

registry

BeanDefinitionRegistry類型,是一個接口,內部提供了註冊bean的各種方法。

importBeanNameGenerator

BeanNameGenerator類型,是一個接口,內部有一個方法,用來生成bean的名稱。

關於BeanDefinitionRegistry和BeanNameGenerator接口在來細說一下。

BeanDefinitionRegistry接口:bean定義註冊器

bean定義註冊器,提供了bean註冊的各種方法,來看一下源碼:

<code>public interface BeanDefinitionRegistry extends AliasRegistry {

    /**
     * 註冊一個新的bean定義
     * beanName:bean的名稱

     * beanDefinition:bean定義信息
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;

    /**
     * 通過bean名稱移除已註冊的bean
     * beanName:bean名稱
     */
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 通過名稱獲取bean的定義信息
     * beanName:bean名稱
     */
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    /**
     * 查看beanName是否註冊過
     */
    boolean containsBeanDefinition(String beanName);

    /**
     * 獲取已經定義(註冊)的bean名稱列表
     */
    String[] getBeanDefinitionNames();

    /**
     * 返回註冊器中已註冊的bean數量
     */
    int getBeanDefinitionCount();

    /**
     * 確定給定的bean名稱或者別名是否已在此註冊表中使用
     * beanName:可以是bean名稱或者bean的別名
     */
    boolean isBeanNameInUse(String beanName);

}/<code>

基本上所有bean工廠都實現了這個接口,讓bean工廠擁有bean註冊的各種能力。

上面我們用到的AnnotationConfigApplicationContext類也實現了這個接口。

BeanNameGenerator接口:bean名稱生成器

bean名稱生成器,這個接口只有一個方法,用來生成bean的名稱:

<code>public interface BeanNameGenerator {
    String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);
}/<code>

spring內置了3個實現

DefaultBeanNameGenerator

默認bean名稱生成器,xml中bean未指定名稱的時候,默認就會使用這個生成器,默認為:完整的類名#bean編號

AnnotationBeanNameGenerator

註解方式的bean名稱生成器,比如通過@Component(bean名稱)的方式指定bean名稱,如果沒有通過註解方式指定名稱,默認會將完整的類名作為bean名稱。

FullyQualifiedAnnotationBeanNameGenerator

將完整的類名作為bean的名稱

BeanDefinition接口:bean定義信息

用來表示bean定義信息的接口,我們向容器中註冊bean之前,會通過xml或者其他方式定義bean的各種配置信息,bean的所有配置信息都會被轉換為一個BeanDefinition對象,然後通過容器中BeanDefinitionRegistry接口中的方法,將BeanDefinition註冊到spring容器中,完成bean的註冊操作。

這個接口有很多實現類,有興趣的可以去看看源碼,BeanDefinition的各種用法,以後會通過專題細說。

value為ImportBeanDefinitionRegistrar接口類型

用法(4個步驟)

<code>1. 定義ImportBeanDefinitionRegistrar接口實現類,在registerBeanDefinitions方法中使用registry來註冊bean
2. 使用@Import來導入步驟1中定義的類
3. 使用步驟2中@Import標註的類作為AnnotationConfigApplicationContext構造參數創建spring容器
4. 使用AnnotationConfigApplicationContext操作bean/<code>

案例

來2個普通的類。

Service1

<code>package com.javacode2018.lesson001.demo24.test4;

public class Service1 {
}/<code>

Service2

這個類中需要注入Service1

<code>package com.javacode2018.lesson001.demo24.test4;

public class Service2 {
    private Service1 service1;

    public Service1 getService1() {
        return service1;
    }

    public void setService1(Service1 service1) {
        this.service1 = service1;

    }

    @Override
    public String toString() {
        return "Service2{" +
                "service1=" + service1 +
                '}';
    }
}/<code>

來個類實現ImportBeanDefinitionRegistrar接口,然後在裡面實現上面2個類的註冊,如下:

MyImportBeanDefinitionRegistrar

<code>package com.javacode2018.lesson001.demo24.test4;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;


public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //定義一個bean:Service1
        BeanDefinition service1BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service1.class).getBeanDefinition();
        //註冊bean
        registry.registerBeanDefinition("service1", service1BeanDinition);

        //定義一個bean:Service2,通過addPropertyReference注入service1
        BeanDefinition service2BeanDinition = BeanDefinitionBuilder.genericBeanDefinition(Service2.class).
                addPropertyReference("service1", "service1").
                getBeanDefinition();
        //註冊bean
        registry.registerBeanDefinition("service2", service2BeanDinition);
    }
}/<code>

注意上面的registerBeanDefinitions方法,內部註冊了2個bean,Service1和Service2。

上面使用了BeanDefinitionBuilder這個類,這個是BeanDefinition的構造器,內部提供了很多靜態方法方便構建BeanDefinition對象。

上面定義的2個bean,和下面xml方式效果一樣:

<code><bean>
<bean>
    <property>
/<bean>/<code>

來個測試用例

ImportTest中新增個方法

<code>@Test
public void test4() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
    //2.輸出容器中定義的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}/<code>

運行輸出

<code>service1->com.javacode2018.lesson001.demo24.test4.Service1@62150f9e
service2->Service2{service1=com.javacode2018.lesson001.demo24.test4.Service1@62150f9e}/<code>

value為ImportSelector接口類型

先來看一下ImportSelector接口

ImportSelector接口

導入選擇器,看一下源碼:

<code>public interface ImportSelector {

    /**
     * 返回需要導入的類名的數組,可以是任何普通類,配置類(@Configuration、@Bean、@CompontentScan等標註的類)

     * @importingClassMetadata:用來獲取被@Import標註的類上面所有的註解信息
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}/<code>

用法(4個步驟)

<code>1. 定義ImportSelector接口實現類,在selectImports返回需要導入的類的名稱數組
2. 使用@Import來導入步驟1中定義的類
3. 使用步驟2中@Import標註的類作為AnnotationConfigApplicationContext構造參數創建spring容器
4. 使用AnnotationConfigApplicationContext操作bean/<code>

案例

來個普通類:Service1

<code>package com.javacode2018.lesson001.demo24.test5;

public class Service1 {
}/<code>

來個@Configuration標註的配置類:Module1Config

<code>package com.javacode2018.lesson001.demo24.test5;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Module1Config {
    @Bean
    public String name() {
        return "公眾號:路人甲java";
    }

    @Bean
    public String address() {
        return "上海市";

    }
}/<code>

上面定義了兩個string類型的bean:name和address

下面自定義一個ImportSelector,然後返回上面2個類的名稱

<code>package com.javacode2018.lesson001.demo24.test5;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                Service1.class.getName(),
                Module1Config.class.getName()
        };
    }
}/<code>

來個@Import標註的類,導入MyImportSelector

<code>package com.javacode2018.lesson001.demo24.test5;

import com.javacode2018.lesson001.demo24.test4.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;

/**
 * 通過@Import導入MyImportSelector接口實現類
 */
@Import({MyImportSelector.class})
public class MainConfig5 {
}/<code>

新增測試用例

ImportTest中新增個方法

<code>@Test
public void test5() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類

    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
    //2.輸出容器中定義的所有bean信息
    for (String beanName : context.getBeanDefinitionNames()) {
        System.out.println(String.format("%s->%s", beanName, context.getBean(beanName)));
    }
}/<code>

運行輸出

部分輸出如下:

<code>com.javacode2018.lesson001.demo24.test5.Service1->com.javacode2018.lesson001.demo24.test5.Service1@45b4c3a9
name->公眾號:路人甲java
address->上海市/<code>

輸出中可以看到Service1以及Module1Config中定義的2個bean都有了。

來一個牛逼的案例

需求

凡是類名中包含service的,調用他們內部任何方法,我們希望調用之後能夠輸出這些方法的耗時。

實現分析

之前我們講過代理, 此處我們就可以通過代理來實現,bean實例創建的過程中,我們可以給這些bean生成一個代理,在代理中統計方法的耗時,這裡面有2點:

  1. 創建一個代理類,通過代理來間接訪問需要統計耗時的bean對象
  2. 攔截bean的創建,給bean實例生成代理生成代理

具體實現

先來兩個Service類

Service1
<code>package com.javacode2018.lesson001.demo24.test6;

import org.springframework.stereotype.Component;

@Component
public class Service1 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}/<code>
Service2
<code>package com.javacode2018.lesson001.demo24.test6;

import org.springframework.stereotype.Component;

@Component
public class Service2 {
    public void m1() {
        System.out.println(this.getClass() + ".m1()");
    }
}/<code>

創建統計耗時的代理類

下面我們使用cglib來實現一個代理類,如下:

<code>package com.javacode2018.lesson001.demo24.test6;


import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CostTimeProxy implements MethodInterceptor {
    //目標對象

    private Object target;

    public CostTimeProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        //調用被代理對象(即target)的方法,獲取結果
        Object result = method.invoke(target, objects); //@1
        long endTime = System.nanoTime();
        System.out.println(method + ",耗時(納秒):" + (endTime - starTime));
        return result;
    }

    /**
     * 創建任意類的代理對象
     *
     * @param target
     * @param 
     * @return
     */
    public static  T createProxy(T target) {
        CostTimeProxy costTimeProxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}
/<code>

createProxy方法可以用來給某個對象生成代理對象

需要了解cglib的可以看:代理詳解(Java動態代理&cglib代理)

攔截bean實例的創建,返回代理對象

這裡我們需要用到spring中的一個接口:

<code>org.springframework.beans.factory.config.BeanPostProcessor

public interface BeanPostProcessor {

    /**
     * bean初始化之後會調用的方法
     */
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * bean初始化之後會調用的方法
     */
    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}/<code>

這個接口是bean處理器,內部有2個方法,分別在bean初始化前後會進行調用,以後講聲明週期的時候還會細說的,這裡你只需要知道bean初始化之後會調用postProcessAfterInitialization方法就行,這個方法中我們會給bean創建一個代理對象。

下面我們創建一個BeanPostProcessor實現類:

<code>package com.javacode2018.lesson001.demo24.test6;

import com.javacode2018.lesson001.demo23.test4.CostTimeProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

public class MethodCostTimeProxyBeanPostProcessor implements BeanPostProcessor {
    @Nullable

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean.getClass().getName().toLowerCase().contains("service")) {
            return CostTimeProxy.createProxy(bean); //@1
        } else {
            return bean;
        }
    }
}/<code>

@1:使用上面創建代理類來給當前bean對象創建一個代理

需要將MethodCostTimeProxyBeanPostProcessor註冊到容器中才會起作用,下面我們通過@Import結合ImportSelector的方式來導入這個類,將其註冊到容器中。

MethodCostTimeImportSelector

<code>package com.javacode2018.lesson001.demo24.test6;


import com.javacode2018.lesson001.demo23.test4.MethodCostTimeProxyBeanPostProcessor;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MethodCostTimeImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{MethodCostTimeProxyBeanPostProcessor.class.getName()};
    }
}/<code>

來一個@Import來導入MethodCostTimeImportSelector

下面我們使用註解的方式,在註解上使用@Import,如下:

<code>package com.javacode2018.lesson001.demo24.test6;


import com.javacode2018.lesson001.demo23.test4.MethodCostTimeImportSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MethodCostTimeImportSelector.class)
public @interface EnableMethodCostTime {
}/<code>

來一個總的配置類

<code>package com.javacode2018.lesson001.demo24.test6;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableMethodCostTime //@1
public class MainConfig6 {
}/<code>

上面使用了@CompontentScan註解,此時會將Servce1和Service2這兩個類註冊到容器中。

@1:此處使用了@EnableMethodCostTime註解,而@EnableMethodCostTime註解上使用了@Import(MethodCostTimeImportSelector.class),此時MethodCostTimeImportSelector類中的MethodCostTimeProxyBeanPostProcessor會被註冊到容器,會攔截bean的創建,創建耗時代理對象。

來個測試用例

ImportTest中新增個方法

<code>@Test
public void test6() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig6.class);
    Service1 service1 = context.getBean(Service1.class);
    Service2 service2 = context.getBean(Service2.class);
    service1.m1();
    service2.m1();
}/<code>

上面會調用service1和service2的方法

運行輸出

<code>class com.javacode2018.lesson001.demo24.test6.Service1.m1()
public void com.javacode2018.lesson001.demo24.test6.Service1.m1(),耗時(納秒):74200
class com.javacode2018.lesson001.demo24.test6.Service2.m1()
public void com.javacode2018.lesson001.demo24.test6.Service2.m1(),耗時(納秒):33800/<code>

太牛逼了,需求實現了。

如果我們不想開啟方法耗時統計,只需要將MainConfig6上的@EnableMethodCostTime去掉就可以了,用起來是不是特別爽。

spring中有很多類似的註解,以@EnableXXX開頭的註解,基本上都是通過上面這種方式實現的,如:

<code>@EnableAspectJAutoProxy
@EnableCaching
@EnableAsync/<code>

繼續向下看,還有一個更牛逼的接口DeferredImportSelector。

DeferredImportSelector接口

先給你透露一下,springboot中的核心功能@EnableAutoConfiguration就是靠DeferredImportSelector來實現的。

DeferredImportSelector是ImportSelector的子接口,既然是ImportSelector的子接口,所以也可以通過@Import進行導入,這個接口和ImportSelector不同地方有兩點:

  1. 延遲導入
  2. 指定導入的類的處理順序

延遲導入

比如@Import的value包含了多個普通類、多個@Configuration標註的配置類、多個ImportSelector接口的實現類,多個ImportBeanDefinitionRegistrar接口的實現類,還有DeferredImportSelector接口實現類,此時spring處理這些被導入的類的時候,會將DeferredImportSelector類型的放在最後處理,會先處理其他被導入的類,其他類會按照value所在的前後順序進行處理

那麼我們是可以做很多事情的,比如我們可以在DeferredImportSelector導入的類中判斷一下容器中是否已經註冊了某個bean,如果沒有註冊過,那麼再來註冊。

以後我們會講到另外一個註解@Conditional,這個註解可以按條件來註冊bean,比如可以判斷某個bean不存在的時候才進行註冊,某個類存在的時候才進行註冊等等各種條件判斷,通過@Conditional來結合DeferredImportSelector可以做很多事情。

來個延遲導入的案例

來3個配置類,每個配置類中都通過@Bean定一個string類型的bean,內部輸出一句文字。

Configuration1

<code>package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}/<code>

Configuration2

<code>package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}/<code>

Configuration3

<code>package com.javacode2018.lesson001.demo24.test7;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration3 {
    @Bean
    public String name3() {
        System.out.println("name3");
        return "name3";
    }

}/<code>

來一個ImportSelector實現類,導入Configuration1

<code>package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ImportSelector1 implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{
                Configuration1.class.getName()
        };
    }
}/<code>

來一個DeferredImportSelector實現類,導入Configuration2

<code>package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration2.class.getName()};
    }
}/<code>

來一個總的配置類

<code>package com.javacode2018.lesson001.demo24.test7;

import org.springframework.context.annotation.Import;

@Import({
        DeferredImportSelector1.class,
        Configuration3.class,
        ImportSelector1.class,
})
public class MainConfig7 {
}/<code>

注意上面的@Import中被導入類的順序:

DeferredImportSelector1->Configuration3->ImportSelector1

下面來個測試用例,看一下3個配置文件中@Bean標註的方法被執行的先後順序。

測試用例

ImportTest中新增個方法

<code>@Test
public void test7() {
    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
}/<code>

運行輸出

<code>name3
name1
name2/<code>

輸出的結果結合一下@Import中被導入的3個類的順序,可以看出DeferredImportSelector1是被最後處理的,其他2個是按照value中所在的先後順序處理的。

指定導入的類的處理順序

當@Import中有多個DeferredImportSelector接口的實現類時候,可以指定他們的順序,指定順序常見2種方式

實現Ordered接口的方式

<code>org.springframework.core.Ordered

public interface Ordered {


    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    int getOrder();

}/<code>

value的值越小,優先級越高。

實現Order註解的方式

<code>org.springframework.core.annotation.Order

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface Order {

    int value() default Ordered.LOWEST_PRECEDENCE;

}/<code>

value的值越小,優先級越高。

下面我們來個案例感受一下。

來個指定導入類處理順序的案例

來2個配置類,內部都有一個@Bean標註的方法,用來註冊一個bean,方法內部輸出一行文字

Configuration1

<code>package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration

public class Configuration1 {
    @Bean
    public String name1() {
        System.out.println("name1");
        return "name1";
    }
}/<code>

Configuration2

<code>package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Configuration2 {
    @Bean
    public String name2() {
        System.out.println("name2");
        return "name2";
    }
}/<code>

來2個DeferredImportSelector實現類,分別來導入上面2個配置文件,順便通過Ordered接口指定一下順序

DeferredImportSelector1

<code>package com.javacode2018.lesson001.demo24.test8;

import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration1.class.getName()};
    }

    @Override
    public int getOrder() {
        return 2;
    }
}/<code>

DeferredImportSelector2

<code>package com.javacode2018.lesson001.demo24.test8;

import com.javacode2018.lesson001.demo24.test7.Configuration2;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;

public class DeferredImportSelector2 implements DeferredImportSelector, Ordered {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{Configuration2.class.getName()};
    }

    @Override
    public int getOrder() {
        return 1;
    }
}/<code>

DeferredImportSelector1的order為2,DeferredImportSelector2的order為1,order值越小優先級越高。

來個總的配置類,引入上面兩個ImportSelector

MainConfig8

<code>package com.javacode2018.lesson001.demo24.test8;


import org.springframework.context.annotation.Import;

@Import({
        DeferredImportSelector1.class,
        DeferredImportSelector2.class,
})
public class MainConfig8 {
}/<code>

測試用例

ImportTest中新增個方法

<code>@Test
public void test8() {

    //1.通過AnnotationConfigApplicationContext創建spring容器,參數為@Import標註的類
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
}/<code>

運行輸出

<code>name2
name1/<code>

結果配合order的值,按照order從小到大來處理,可以看出DeferredImportSelector2先被處理的。

Spring中這塊的源碼

@Import註解是被下面這個類處理的

<code>org.springframework.context.annotation.ConfigurationClassPostProcessor/<code>

前面介紹的@Configuration、@Bean、@CompontentScan、@CompontentScans都是被這個類處理的,這個類是高手必經之路,建議花點時間研究研究。


分享到:


相關文章: