Spring原理:如何实现模块装配

在我们使用springboot编码的过程中,会遇见使用@EnableXXX开启某个模块的情况。比如说@EnableAsync开启异步,@EnableCache开启缓存……

这篇笔记来学习,如何自定义一个@EnableXXX注解,当这个注解被使用的时候,模块就自动装配了。

假设,我们自己定义了一个美颜模块,希望当启动类标上@EnableBeauty注解的时候,能够开启美颜模块。

我们可以设想,美颜功能包括:瘦脸、美白、大眼……

我们可以新建一个maven项目,引入以下依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
    </dependencies>

我们可以在site.nemo.features包下面定义三个类,分别表示上述三个特性。

// Brightening.java
public class Brightening {

}

// BigEyes.java
public class BigEyes {

}

// SlimFace.java
public class SlimFace {

}

我们在site.nemo.annotations包下定义一个@EnableBeauty注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Brightening.class, BigEyes.class, SlimFace.class})
public @interface EnableBeauty{
}

site.nemo.configurations包里面定义一个配置类,给配置类加上@EnableBeauty注解,启用美颜功能。

@Configuration
@EnableBeauty
public class MyConfiguration1 {

}

然后在site.nemo包里面定义一个MyApplication1,在它的main方法里面判断美颜功能的一些类是否被注入到容器中去了。

public class MyApplication1 {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration1.class);
        // 判断美白bean是否已被注入
        Brightening brightening = ctx.getBean(Brightening.class);
        System.out.println(brightening);

    }
}

从打印结果可以看出美颜模块的美白特性已经被成功注入到容器里面去了。


上面的例子最关键的地方是@Import注解,我们来看下它的源码:

@Import源码

它的value是Class<?>[]即是一个类数组,里面可以放普通的类、标注了@Configuration的类,实现了ImportSelector接口的类、实现了ImportBeanDefinitionRegistrar接口的类。


我们可以试验一下@Import的value里面放非普通类的时候的情况。首先看一下放置标注了@Configuration的类的情况。

site.nemo.configuration包里面新建一个FeautreConfiguration.java

@Configuration
public class FeautreConfiguration {

	@Bean
	public Brightening brightening() {
		return new Brightening();
	}

	@Bean
	public SlimFace slimFace() {
		return new SlimFace();
	}

	@Bean
	public BigEyes bigEyes() {
		return new BigEyes();
	}
}

然后在site.nemo.annotations包里面新建一个@EnableBeauty2注解,在@Import里面只引入上面定义的FeautreConfiguration

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({FeautreConfiguration.class})
public @interface EnableBeauty2{
}

然后在site.nemo.configuration包里面新建一个MyConfiguration2配置类,并且在它上面写上@EnableBeauty2注解

@Configuration
@EnableBeauty2
public class MyConfiguration2 {

}

最后在site.nemo包里面定义一个MyApplication2,使用MyConfiguration2来初始化容器,在它的main方法里面判断美颜功能的一些类是否被注入到容器中去了。

public class MyApplication2 {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration2.class);
        // 判断美白bean是否已被注入
        Brightening brightening = ctx.getBean(Brightening.class);
        System.out.println(brightening);

    }
}

从打印结果可以看出美颜模块的美白特性已经被成功注入到容器里面去了。


上面演示了使用@Import引入一个配置类的情况,下面演示使用@Import引入实现了ImportSelector接口的类的情况。

可以看下ImportSelector这个接口的源码。里面最重要的就是selectImports这个方法,它返回的是一个字符串数组。从注释可以看到,字符串数组里面存的是类的名字

这个接口的源码

我们定义一个BeautyImportSelector类,它实现了ImportSelector接口。然后我们实现selectImports方法,将上述的三个美颜特性的类名返回。

public class BeautyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {Brightening.class.getName(), BigEyes.class.getName(), SlimFace.class.getName()};
    }
}

然后在site.nemo.annotations包里面新建一个@EnableBeauty3注解,在@Import里面只引入上面定义的BeautyImportSelector

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({BeautyImportSelector.class})
public @interface EnableBeauty3{
}

然后在site.nemo.configuration包里面新建一个MyConfiguration3配置类,并且在它上面写上@EnableBeauty3注解

@Configuration
@EnableBeauty3
public class MyConfiguration3 {

}

最后在site.nemo包里面定义一个MyApplication3,使用MyConfiguration3来初始化容器,在它的main方法里面判断美颜功能的一些类是否被注入到容器中去了。

public class MyApplication3 {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration3.class);
        // 判断美白bean是否已被注入
        Brightening brightening = ctx.getBean(Brightening.class);
        System.out.println(brightening);

    }
}

从打印结果可以看出美颜模块的美白特性已经被成功注入到容器里面去了。


下面再演示一下@Import引入实现了ImportBeanDefinitionRegistrar接口的类的情况。

看下ImportBeanDefinitionRegistrar这个接口的源码。

这个接口的源码

我们定义一个BeautyImportBeanDefinitionRegister类,它实现了ImportBeanDefinitionRegistrar接口。然后我们实现registerBeanDefinitions方法,将三个美颜特性的bean注册进去。

public class BeautyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        /**
         * 使用这种方式的话,需要注意被注入的类必须有 无参构造函数
         */
        registry.registerBeanDefinition("brightening", new RootBeanDefinition(Brightening.class));
        registry.registerBeanDefinition("bigEyes", new RootBeanDefinition(BigEyes.class));
        registry.registerBeanDefinition("slimFace", new RootBeanDefinition(SlimFace.class));

    }
}

然后在site.nemo.annotations包里面新建一个@EnableBeauty4注解,在@Import里面只引入上面定义的BeautyImportBeanDefinitionRegister

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({BeautyImportBeanDefinitionRegister.class})
public @interface EnableBeauty4{
}

然后在site.nemo.configuration包里面新建一个MyConfiguration4配置类,并且在它上面写上@EnableBeauty4注解

@Configuration
@EnableBeauty4
public class MyConfiguration4 {

}

最后在site.nemo包里面定义一个MyApplication4,使用MyConfiguration4来初始化容器,在它的main方法里面判断美颜功能的一些类是否被注入到容器中去了。

public class MyApplication3 {

    public static void main(String[] args) {

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfiguration4.class);
        // 判断美白bean是否已被注入
        Brightening brightening = ctx.getBean(Brightening.class);
        System.out.println(brightening);

    }
}

从打印结果可以看出美颜模块的美白特性已经被成功注入到容器里面去了。

Show Comments