重点了解@Import实现的三种子方式
@Component、@Bean的区别:
@ComponentScan、@Componet是通过组件声明和包扫描的方式去注册Bean的,是最常用的方式之一,它提供了包扫描,批量声明组件的方式。可以让我们把所有被扫描的组件都注册到Spring容器中。@Component常用来修饰类,让其成为一个组件,一般是本工程自己定义的类,一般都会通过这种方式声明,比较简单
而@Bean则通常是搭配@Configuration(跟@Component没区别)使用的,一般是在被@Component方式声明的类中去修饰方法,提供单一Bean的注册,通常用来修饰方法,把该方法的返回对象注册到Spring容器中。如果要声明的Bean的类是在第三方Jar包声明的,则该类的路径一般都不在@ComponentScan的扫描范围内,所以@Component是不会生效的,此时我们则可以通过@Bean或@Import的方式注册到Spring容器
@Import与其他的区别:
@Import方式还有三种子实现:
- 直接导入配置类
- 导入ImportSelector实现类
- 导入ImportBeanDefiniteRegistrar实现类
区别:
第一种直接导入配置类的方式就比较直接,直接将某个类就注册到容器了,是Spring 3.0 提供的。
第二种和第三种相比第一种方式就更加的灵活,是Spring 3.1之后提供的。
第二种方式可以在Selector实现类中按条件注册不同的组件,不如根据传入参数的,而注册不同的组件。它不像第一种方式,直接写死了一个组件。
第三种方式的话,就更加的灵活了,支持手动注册Bean,支持在BeanDefinite阶段注册组件,可以让我们在程序跑着的时候动态注册Bean, 因为它是基于BeanDefinite来操作的,可以动态代理该组件,将代理结果注册到Spring容器中。
即第一种是直接导入,第二种可以根据条件注册不同的Bean,第三种不仅可以根据条件注册不同的Bean,还可以修改Bean元数据从而达到动态的修改Bean,将被修改过的Bean注册到容器中
场景:
@EnableWebMvc
注解中体现@EnableAutoConfiguration
注解中体现@EnableFeignClients
注解中体现通过包扫描和组件声明注解的方式,是我们开发中最常见的一种Bean注册的方式:
原理就是:
常用属性名 | 类型 | 说明 |
---|---|---|
includeFilters | Filter[] | 指定扫描导入类型的过滤规则 |
excludeFilters | Filter[] | 指定扫描排除类型的过滤规则 |
也许你还会问,通常我们不是也使用了@Service,@Configuration,@Controller
等模式注解,这些也算是这种方式注册的吗?
答案是肯定,因为这些注解都仅仅是@Component组件的派生,仅仅是名称不同的@Component , 名字是起标识性作用的,真正发挥作用的还是@Component注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
}
比如@Configuration注解,我们可以看到,里面就声明了一个@Componet注解,然后就没有什么特别的了
通过@Bean方式注册Bean也是我们开发中常用的一种开发方式:
最常见的用法就是:
比如
@Configuration
public class MyConfig(){
@Bean
public String hello(){
return "hello world";
}
}
这里会生成一个id为hello, 值为hello world的String对象,这个对象被声明为Bean并注册到Spring容器中
@Import方式的使用也比较频繁,更多的情况下,我们会在阅读Spring框架源码的时候遇到
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
从源码的中,我们可以看到:
value
从注释中,我们可以看出来,value通常有三种选择:
ImportSelector
接口实现类ImportBeanDefinitionRegistrar
实现类小结:
第一步,自定义注解EnableHello
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyConfiguration.class)
public @interface EnableHello{
}
第二步,自定义配置类
public class MyConfig{
}
第三步,声明注解
@EnableHello
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
通过在某个组件,比如SpringBoot启动引导类中声明@EnableHello注解,那么效果就是MyConfig普通配置类将直接被注册到Spring容器中
第一步,自定义注解EnableHello
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyConfiguration.class)
public @interface EnableHello{
}
第二步,自定义配置类
public class MyConfig{
}
第三步,自定义xxxSelector实现类
public class HelloImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{
MyConfig.class.getName()};
//返回所需配置类的全限定名称 ,比如com.example.demo.config.MyConfig
}
}
第三步,声明注解
@EnableHello
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们这里并没有根据条件来注册不同的Bean,但是可以知道,自定义注解中可以定义一个value,在selectImports()方法中,我们可以拿到注解的元数据,从而得到value是什么,根据这个value就可以返回对应要注册的Bean名称数组
第一步,声明注解,@Import执行我们的ImportBeanDifinitionRegistrar实现类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportBeanDefinitionRegistrar .class)
public @interface EnableHello{
}
第二步,声明一个类,准备作为Bean,但没有声明注册, 属性name默认没有值
@Data
public class Hello {
private String name;
}
第三步,自定义ImportBeanDifinitionRegistrar实现类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//生成Hello类的BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition(Hello.class);
//获取Hello元数据中的属性参数
PropertyValues propertyValues = beanDefinition.getPropertyValues();
//因为name默认为空,所以赋予值
((MutablePropertyValues) propertyValues).add("name","snailmann");
//将修改过的Bean元数据注册到BeanDefinitionRegistry中
registry.registerBeanDefinition("hello",beanDefinition);
}
}
第四步,启动类声明自定义注解
@EnableHello
@SpringBootApplication
public class Demo4Application {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Demo4Application.class, args);
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello);
}
}
输出结果为:
Hello(name=jerry)
从这里,我们就可以知道,ImportBeanDifinitionRegistrar的功能是最牛逼的,不仅仅可以实现Selector的功能,还可以在运行期间动态的修改Bean的元数据,从而动态注册Bean;
就像我们知道@EnableFeignClients注解就是通过这样的方式实现的。我得猜想就是,@FeignClient声明API , 然后@EnableFeignClients指定的register会寻找带有@FeignClient的类。并修改这个类的Bean元数据,有点像动态代理的感觉。而因为这些api就是空壳方法,所以register就是去为其实现http请求的功能