Spring中你可能不知道的事(二)

在上一節(jié)中,我介紹了Spring中極為重要的BeanPostProcessor BeanFactoryPostProcessor Import ImportSelector,還介紹了一些其他的零碎知識(shí)點(diǎn),正如我上一節(jié)所說(shuō)的,Spring實(shí)在是太龐大了,是眾多Java開發(fā)大神的結(jié)晶,很多功能,很多細(xì)節(jié),可能一輩子都不會(huì)用到,不會(huì)發(fā)現(xiàn),作為普通開發(fā)的我們,只能盡力去學(xué)習(xí),去挖掘,也許哪天可以用到呢。

讓我們進(jìn)入正題吧。

Full Lite

在上一節(jié)中的第一塊內(nèi)容,我們知道了Spring中除了可以注冊(cè)我們最常用的配置類,還可以注冊(cè)一個(gè)普通的Bean,今天我就來(lái)做一個(gè)補(bǔ)充說(shuō)明。

如果你接到一個(gè)需求,要求寫一個(gè)配置類,完成掃描,你會(huì)怎么寫?

作為經(jīng)常使用Spring的來(lái)說(shuō),這是一個(gè)入門級(jí)別的問(wèn)題,并且在20秒鐘之內(nèi)就可以完成編碼:

@Configuration
@ComponentScan
public class AppConfig {
}
public class Main {
    public static void main(String[] args) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
       context.getBean(ServiceImpl.class).query();
    }
}
@Component
public class ServiceImpl{
    public void query() {
        System.out.println("正在查詢中");
    }
}

運(yùn)行:

image.png

但是你有沒有嘗試過(guò)把AppConfig類上的@Configuration注解給去除?你在心里肯定會(huì)犯嘀咕,這不能去除啊,這個(gè)@Configuration注解申明了咱們的AppConfig是一個(gè)Spring配置類,去除了@Configuration注解,怎么可能可以呢?但是事實(shí)勝于雄辯,當(dāng)我們把@Configuration注解給刪除,再次運(yùn)行,你會(huì)見證到奇跡:

@ComponentScan
public class AppConfig {
}
public class Main {
    public static void main(String[] args) {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
       context.getBean(ServiceImpl.class).query();
    }
}
image.png

一點(diǎn)問(wèn)題都沒有?。。∈遣皇堑竭@里已經(jīng)顛覆了你對(duì)Spring的認(rèn)知。

其實(shí),在Spring內(nèi)部,把帶上了@Configuration的配置類稱之為Full配置類,把沒有帶上@Configuration,但是帶上了@Component @ComponentScan @Import @ImportResource等注解的配置類稱之為L(zhǎng)ite配置類。

原諒我,實(shí)在找不到合適的中文翻譯來(lái)表述這里的Full和Lite。

也許你會(huì)覺得這并沒什么用,只是“茴的四種寫法”而已。

別急,讓我們看下去,將會(huì)繼續(xù)刷新你的三觀:

@ComponentScan
public class AppConfig {
}

注意現(xiàn)在的AppConfig類上沒有加上@Configuration注解。

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(context.getBean(AppConfig.class).getClass().getSimpleName());
    }
}

我們注冊(cè)了Lite配置類,并且從Spring容器中取出了Lite配置類,打印出它的類名。

運(yùn)行:

image.png

可以看到從容器取出來(lái)的就是AppConfig類,各位看官肯定會(huì)想,這不是廢話嗎,難道從容器取出來(lái)會(huì)變成了一只老母雞?

別急嘛,讓我們繼續(xù)。

我們?cè)僭贏ppConfig類加上@Configuration注解,使其變成Full配置類,然后還是一樣,注冊(cè)這個(gè)配置類,取出這個(gè)配置類,打印類名:

image.png

你會(huì)驚訝的發(fā)現(xiàn),的確從容器里取出了一個(gè)老母雞,哦,不,是一個(gè)奇怪的類,從類名我們可以看到CGLIB這個(gè)關(guān)鍵字,CGLIB是動(dòng)態(tài)代理的一種實(shí)現(xiàn)方式,也就是說(shuō)我們的Full配置類被CGLIB代理了。

你是不是從來(lái)都沒有注意過(guò),竟然會(huì)有如此奇怪的設(shè)定,但是更讓人驚訝的事情還在后頭,讓我們想想,為什么好端端的類,Spring要用Cglib代理?這又不是AOP。Spring內(nèi)部肯定做了一些什么!沒錯(cuò),確實(shí)做了?。?!

下面讓我們看看Spring到底做了什么:

public class ServiceImpl {
    public ServiceImpl() {
        System.out.println("ServiceImpl類的構(gòu)造方法");
    }
}

ServiceImpl類中有一個(gè)構(gòu)造方法,打印了一句話。

public class OtherImpl {
}

再定義一個(gè)OtherImpl類,里面什么都沒有。

public class AppConfig {
    @Bean
    public ServiceImpl getServiceImpl() {
        return new ServiceImpl();
    }

    @Bean
    public OtherImpl getOtherImpl() {
        getServiceImpl();
        return new OtherImpl();
    }
}

這個(gè)AppConfig沒有加上@Configuration注解,是一個(gè)Lite配置類,里面定義了兩個(gè)@Bean方法,其中g(shù)etServiceImpl方法創(chuàng)建并且返回了ServiceImpl類的對(duì)象,getOtherImpl方法再次調(diào)用了getServiceImpl方法。

然后我們注冊(cè)這個(gè)配置類:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
    }
}

運(yùn)行:

image.png

發(fā)現(xiàn)打印了兩次"ServiceImpl類的構(gòu)造方法",這也很好理解,因?yàn)閚ew了兩次ServiceImpl嘛,肯定會(huì)執(zhí)行兩次ServiceImpl構(gòu)造方法呀。

我們?cè)诎袬Configuration注解給加上,讓AppConfig稱為一個(gè)Full配置類,再次運(yùn)行:

image.png

你會(huì)驚訝的發(fā)現(xiàn)只打印了一次"ServiceImpl類的構(gòu)造方法",說(shuō)明只調(diào)用了一次ServiceImpl類的構(gòu)造方法,其實(shí)這也說(shuō)的通啊,因?yàn)锽ean默認(rèn)是Singleton的,所以只會(huì)創(chuàng)建一次對(duì)象嘛。

但是問(wèn)題來(lái)了,為什么我們明明new了兩次ServiceImpl類,但是真正只new了一次?結(jié)合上面的內(nèi)容,很容易知道答案,因?yàn)镕ull配置類被Cglib代理了,它已經(jīng)不是我們?cè)榷x的AppConfig類了,最終執(zhí)行的是代理對(duì)象。

好了,這個(gè)問(wèn)題就討論到這里,至于為什么說(shuō)(如何證明)帶上@Configuration注解的配置類稱之為Full配置類,不帶的稱之為L(zhǎng)ite配置類,Cglib是怎么代理Full配置類的,代理的規(guī)則又是什么,這就涉及到Spring的源碼解析了,就不在今天的討論內(nèi)容之中了。

ImportBeanDefinitionRegistrar

大家一定使用過(guò)Mybatis,甚至使用過(guò)Mybatis的擴(kuò)展,我在使用的時(shí)候,覺得太特么的神奇了,只要在配置類上打一個(gè)MapperScan注解,指定需要掃描哪些包,然后這些包里面只有接口,根本沒有實(shí)現(xiàn)類,為什么可以完成數(shù)據(jù)庫(kù)的一系列操作,不知道大家有沒有和我一樣的疑惑,直到我知道了ImportBeanDefinitionRegistrar這個(gè)神奇的接口,關(guān)于這個(gè)接口,我不知道該怎么去描述這個(gè)接口的作用,因?yàn)檫@個(gè)接口實(shí)在是太強(qiáng)大了,實(shí)在不是用簡(jiǎn)單的文字可以描述清楚的。下面我就利用這個(gè)接口來(lái)完成一個(gè)假的MapperScan,從中慢慢體驗(yàn)這個(gè)接口的強(qiáng)大,對(duì)了,這個(gè)接口要和Import注解配合使用。

首先需要定義一個(gè)注解:

@Import(CodeBearMapperScannerRegistrar.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearMapperScanner {
    String value();
}

其中value就是需要掃描的包名,在這個(gè)注解類中又打了一個(gè)Import注解,來(lái)引ImportBeanDefinitionRegistrar類。

再定義一個(gè)注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearSql {
    String value();
}

這個(gè)注解是打在方法上的,接收的是一個(gè)sql語(yǔ)句。

然后要定義一個(gè)類,去實(shí)現(xiàn)ImportBeanDefinitionRegistrar接口,重寫提供的方法。

public class CodeBearMapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        try {
            AnnotationAttributes annoAttrs =
                    AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(CodeBearMapperScanner.class.getName()));
            String packageValue = annoAttrs.getString("value");
            String pathValue = packageValue.replace(".", "/");

            File[] files = resourceLoader.getResource(pathValue).getFile().listFiles();
            for (File file : files) {
                String name = file.getName().replace(".class", "");

                Class<?> aClass = Class.forName(packageValue + "." + name);
                if (aClass.isInterface()&&!aClass.isAnnotation()) {
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
                    AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
                    beanDefinition.setBeanClass(CodeBeanFactoryBean.class);
                    beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packageValue + "." + name);
                    registry.registerBeanDefinition(name, beanDefinition);
                }
            }
        } catch (Exception ex) {
        }
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

其中ResourceLoaderAware接口的作用不大,我只是利用這個(gè)接口,獲得了ResourceLoader ,然后通過(guò)ResourceLoader去獲得包下面的類而已。這方法的核心就是循環(huán)文件列表,根據(jù)包名和文件名,反射獲得Class,接著判斷Class是不是接口,如果是接口的話,動(dòng)態(tài)注冊(cè)Bean。如何動(dòng)態(tài)去注冊(cè)Bean呢?我在這里利用的是BeanDefinitionBuilder,通過(guò)BeanDefinitionBuilder獲得一個(gè)BeanDefinition,此時(shí)BeanDefinition是一個(gè)很純凈的BeanDefinition,經(jīng)過(guò)一些處理,再把最終的BeanDefinition注冊(cè)到Spring容器。

關(guān)鍵就在于處理的這兩行代碼了,這里可能還看不懂,我們繼續(xù)看下去。

我們需要再定義一個(gè)類,去實(shí)現(xiàn)FactoryBean,InvocationHandler兩個(gè)接口:

public class CodeBeanFactoryBean implements FactoryBean, InvocationHandler {
    private Class clazz;

    public CodeBeanFactoryBean(Class clazz) {
        this.clazz = clazz;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        CodeBearSql annotation = method.getAnnotation(CodeBearSql.class);
        String sql= annotation.value();
        System.out.println(sql);
        return sql;
    }

    @Override
    public Object getObject() throws Exception {
        Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, this);
        return o;
    }

    @Override
    public Class<?> getObjectType() {
        return clazz;
    }
}

關(guān)于FactoryBean接口,在上一節(jié)中有介紹,這里就不再闡述了。

這個(gè)類有一個(gè)構(gòu)造方法,接收的是一個(gè)Class,這里接收的就是用來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作的接口。getObject方法中,就利用傳進(jìn)來(lái)的接口和動(dòng)態(tài)代理來(lái)創(chuàng)建一個(gè)代理對(duì)象,此時(shí)這個(gè)代理對(duì)象就是FactoryBean生產(chǎn)的一個(gè)Bean了,只要對(duì)JDK動(dòng)態(tài)代理有一定了解的人都知道,返回出來(lái)的代理對(duì)象實(shí)現(xiàn)了我們用來(lái)進(jìn)行數(shù)據(jù)庫(kù)操作的接口。

我們需要把這個(gè)Bean交給Spring去管理,所以就有了CodeBearMapperScannerRegistrar中的這行代碼:

beanDefinition.setBeanClass(CodeBeanFactoryBean.class);

因?yàn)閯?chuàng)建CodeBeanFactoryBean對(duì)象需要一個(gè)Class參數(shù)。所以就有了CodeBearMapperScannerRegistrar中的這行代碼:

//packageValue + "." +name  就是接口的全名稱
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(packageValue + "." + name);

invoke方法比較簡(jiǎn)單,就是獲得CodeBearSql注解上的sql語(yǔ)句,然后打印一下,當(dāng)然這里只是模擬下,所以并沒有去查詢數(shù)據(jù)庫(kù)。

下面讓我們測(cè)試一下吧:

public interface UserRepo {
    @CodeBearSql(value = "select * from user")
    void get();
}
@Configuration
@CodeBearMapperScanner("com.codebear")
@ComponentScan
public class AppConfig {
}
@Service
public class Test {

    @Autowired
    UserRepo userRepo;

    public  void get(){
        userRepo.get();
    }
}

運(yùn)行結(jié)果:


image.png

可以看到我們的功能已經(jīng)實(shí)現(xiàn)了。其實(shí)Mybatis的MapperScan注解也是利用了ImportBeanDefinitionRegistrar接口去實(shí)現(xiàn)的。

可以看到第二塊內(nèi)容,其實(shí)已經(jīng)比較復(fù)雜了,不光光有ImportBeanDefinitionRegistrar,還整合FactoryBean,還融入了動(dòng)態(tài)代理。如果我們不知道FactoryBean,可能這個(gè)需求就很難實(shí)現(xiàn)了。所以每一塊知識(shí)點(diǎn)都很重要。

這一節(jié)的內(nèi)容到這里就結(jié)束了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容