Spring - 组件(Beans)注册(到 IoC 容器)的几种方式

概述

从 Spring - 从官方文档中认识 IoC 容器 这篇文章中我们知道 IoC 容器 管理着所有的 Beans。我们需要把实例的创建交给 Spring 管理,也就是把 Bean 加入到 IoC 容器 中进行管理,才能使用 IoC 容器控制反转依赖注入 功能。

这里我们使用 Java-based 的方式 来配置 IoC 容器:

  • 首先创建一个配置类 MyConfig,并且标注 @Configuration 注解:
@Configuration
public class MyConfig {
}
  • 接着以配置类的方式创建一个 IoC 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);

这样就简单的创建了一个 IoC 容器,我们可以输出 IoC 容器 中的 Beans

@Test
void initContainer() {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);

    String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

输出:

Spring - 组件(Beans)注册(到 IoC 容器)的几种方式_第1张图片


也可以使用 加载类路径下配置文件的方式 创建 IoC 容器

  • 首先在类路径下创建配置文件 services.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <content:component-scan base-package="com.keke.web"/>
</beans>
  • 使用 ClassPathXmlApplicationContext 创建 IoC 容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("services.xml");

组件注册的几种方式

1、@Configuration + @Bean

  • 首先定义一个普通类
public class Blue {
}
  • 接着配合 @Bean 注解注入到容器中
@Configuration
public class MyConfig {

	// 方法名就是 Bean 的名称,返回值的类型就是 Bean 的类型
    @Bean
    public Blue blue() {
        return new Blue();
    }
}

# 输出 IoC 容器中的 Bean 名称
myConfig
blue

自定义 Bean 的名称:

  1. 修改方法名
  2. @Bean 中指定名称

2、@ComponentScan

作用: 扫描指定路径下标注了 @Controller@Service@Repository@Component 注解的类并加入到 IoC 容器

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

    ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

    String resourcePattern() default "**/*.class";

    boolean useDefaultFilters() default true;

    ComponentScan.Filter[] includeFilters() default {};

    ComponentScan.Filter[] excludeFilters() default {};

    boolean lazyInit() default false;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Filter {
        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

有几种常用写法,目录结构如下,类都是新建的类,里面什么都没有,分别标注了 @Controller@Service@Repository@Component 四个注解:

Spring - 组件(Beans)注册(到 IoC 容器)的几种方式_第2张图片
以下几种扫描方式:

// 扫描 com.keke.web 下的组件
@ComponentScan(basePackages ="com.keke.web")
// 源码中可以看到:value 同 basePackages 互为别名
@ComponentScan(value ="com.keke.web")
// 不指定属性的话默认为 value 属性的值,value 是一个 string[] 类型
@ComponentScan({"com.keke.web.controller", "com.keke.web.service", "com.keke.web.dao"})
// 扫描指定类以及指定类所在目录及其子目录下的组件
@ComponentScan(basePackageClasses = Application.class)
// 可以指定多个,规则同上
@ComponentScan(basePackageClasses = {Application.class, BookController.class})

上面的几种方式也可以使用 @ComponentScans 组合起来:

@Configuration
@ComponentScan(basePackages ="com.keke.web")
@ComponentScan(value ="com.keke.web")
@ComponentScan({"com.keke.web.controller", "com.keke.web.service", "com.keke.web.dao"})
@ComponentScan(basePackageClasses = Application.class)
@ComponentScan(basePackageClasses = {Application.class, BookController.class})
@ComponentScans({
        @ComponentScan("com.keke.web.controller"),
        @ComponentScan("com.keke.web.service")
})
public class MyConfig {

    @Bean()
    public Blue blue() {
        return new Blue();
    }
}

# 输出如下:
myConfig
application
bookController
bookDao
bookService
blue

2.1、excludeFilters

指定排除过滤器:排除某些 Bean 不扫描

// 排除 @Controller 注解标注的组件
@ComponentScan(basePackages ="com.keke.web", excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
})

2.2、includeFilters

指定包含过滤器:只扫描包含符合过滤器规则的 Bean。注意:必须设置 useDefaultFilters 属性为 false

// 只包含 @Controller 注解标注的组件
@ComponentScan(basePackages ="com.keke.web", includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class})
}, useDefaultFilters = false)

2.3、Filter

Filter@ComponentScan 注解的内部类形式的注解。对于 内部类,可以参考 Java - 内部类 这篇文章

@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
    FilterType type() default FilterType.ANNOTATION;

    @AliasFor("classes")
    Class<?>[] value() default {};

    @AliasFor("value")
    Class<?>[] classes() default {};

    String[] pattern() default {};
}

FilterType 指定过滤的类型,有以下几种类型:

public enum FilterType {
	// 注解
    ANNOTATION,
    // 指定类
    ASSIGNABLE_TYPE,
    // ASPECTJ 表达式
    ASPECTJ,
    // 正则表达式
    REGEX,
    // 自定义规则,需要实现 TypeFilter 接口
    CUSTOM;

    private FilterType() {
    }
}

前四种类型都比较好理解,这里我们尝试一下 CUSTOM 类型,需要实现 TypeFilter 接口:

@FunctionalInterface
public interface TypeFilter {
    boolean match(MetadataReader var1, MetadataReaderFactory var2) throws IOException;
}

具体我们需要扫描 类名中包含 Book 的组件,我们可以这么定义:

public class MyTypeFilter implements TypeFilter {

    /**
     *
     * @param metadataReader 读取当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取到任何类的信息(工厂类)
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类注解的信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类资源(类路径等)
        Resource resource = metadataReader.getResource();

        String className = classMetadata.getClassName();

        // 如果返回true,则符合匹配,会注入到Ioc容器中
        return className.contains("Book");
    }
}

并且在 @ComponentScan 注解中指定扫描类型:

@ComponentScan(basePackages = "com.keke.web", includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
}, useDefaultFilters = false)

3、@Import

@Import 注解中的 value 可以填三种值:1、要导入的类 2、ImportSelector 3、ImportBeanDefinitionRegistrar

3.1、@Import(要导入容器的组件)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

快速的给容器中导入一个组件,Bean 名称 为 要导入的组件的 全类名

// Bean 名称为:com.keke.bean.Red
@Import(Red.class)
@Import({Red.class, Blue.class})

3.2、ImportSelector

selectImports 方法的返回值就是要导入到容器中的组件的全类名

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);

    @Nullable
    default Predicate<String> getExclusionFilter() {
        return null;
    }
}

举例,自定义 MyImportSelector 实现 ImportSelector,返回值为需要导入的组件的 全类名

public class MyImportSelector implements ImportSelector {

    /**
     *
     * @param importingClassMetadata 当前标注@Import注解的类的所有注解信息
     * @return 返回值就是要导入到容器中的组件全类名
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.keke.bean.Red", "com.keke.bean.Blue"};
    }
}

3.3、ImportBeanDefinitionRegistar

public interface ImportBeanDefinitionRegistrar {
    default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        this.registerBeanDefinitions(importingClassMetadata, registry);
    }

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

举例,使用 registerBeanDefinition 方法注册 Bean

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata:当前类的注解信息
     * @param registry  BeanDefinition注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean definition = registry.containsBeanDefinition("com.keke.bean.Red");
        boolean definition1 = registry.containsBeanDefinition("com.keke.bean.Blue");
        if (definition && definition1) {
            // 定义bean信息
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
            // 注册bean,指定bean名
            registry.registerBeanDefinition("rainBow", rootBeanDefinition);
        }
    }
}

4、FactoryBean

Spring 官方文档 中有介绍,实现了这个接口的 Bean,作为一个 工厂 Bean,并不是为了注入他自己到 IoC 容器 中,而是会注入 getObject 方法返回的对象的到 IoC 容器 中。常用于框架中,比如 AOP 中:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

举例,FactoryBean 中的泛型就是要注入到容器中的 Bean 的类型:

public class ColorFactoryBean implements FactoryBean<Color> {
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }
}

加入到 IoC 容器 中:

@Bean
public ColorFactoryBean colorFactoryBean() {
    return new ColorFactoryBean();
}

上面注入到 IoC 容器中的 Bean 的名称应该为:colorFactoryBean,我们获取这个 Bean 的类型看一下:

Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
System.out.println(colorFactoryBean.getClass());	// class com.keke.bean.Color

输出的是 getObject 方法返回的类型。如果我们需要获取 FactoryBean 本身,需要在 Bean 名称 前面加 &

Object bean = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean.getClass());	// class com.keke.bean.ColorFactoryBean

对于这一点,我们可以在 BeanFactoryApplicationContext 继承 BeanFactory,并实现了一些高级功能)中看到:

Spring - 组件(Beans)注册(到 IoC 容器)的几种方式_第3张图片

你可能感兴趣的:(spring,学习,spring,java,IoC,容器,Bean)