likes
comments
collection
share

超详细总结Spring的配置注解

作者站长头像
站长
· 阅读数 13

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情

前言

配置注解,指@Configuration,@ComponentScan,@Scope,@Lazy,@Conditional,@Import等注解,本篇文章将对这些注解的使用进行详细总结。

Springboot版本:2.4.1

Spring版本:5.3.2

正文

一. @Configuration

@Configuration注解修饰的类就是配置类,配合@Bean注解使用,可以用于向容器注册bean。如下是示例(所有类全部放在同一包目录下)。

业务类如下所示。

public class MyService {}

由@Configuration注解修饰的业务类如下所示。

@Configuration
public class MyConfig {

    @Bean
    public MyService myService() {
        return new MyService();
    }

}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);
    }

}

运行测试程序,可以将MyServicebean注册到容器中,并且bean名称为myService,日志打印如下所示。

超详细总结Spring的配置注解

二. @ComponentScan

@ComponentScan注解用于扫描并注册bean到容器中。如下是示例(所有类全部放在同一包目录下)。

业务类如下所示。

@Component
public class MyDao {}

@Component
public class MyService {}

配置类如下所示。

@Configuration
@ComponentScan(value = "com.learn.spring.annotation.componentscan", 
        useDefaultFilters = true)
public class MyConfig {}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);
    }

}

运行测试程序后,会把com.learn.spring.annotation.componentscan包目录下的MyServiceMyDaobean注册到容器中。上面的配置类的@ComponentScan注解的value指示扫描哪个包目录及其子目录下的类,同时useDefaultFilters字段为true(不显示指定也为true)表示使用默认过滤规则,默认过滤规则就是将指定路径下所有由@Controller,@Service,@Repository和@Component注解修饰的类的bean注册到容器中

现在将useDefaultFilters配置为true,并编写一个自定义过滤器MyTypeFilter,如下所示。

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();

        // 实现功能:将类名不包含"Dao"字符串的类注册到容器中
        String className = classMetadata.getClassName();
        return !className.contains("Dao");
    }

}

MyConfig进行如下修改。

@Configuration
@ComponentScan(value = "com.learn.spring.annotation.componentscan",
        includeFilters = {@Filter(type = FilterType.CUSTOM, value = {MyTypeFilter.class})},
        useDefaultFilters = false)
public class MyConfig {}

再次运行测试程序,打印如下。

超详细总结Spring的配置注解

MyDaobean没有被注册到容器中。

三. @Scope

@Scope注解用于设置bean作用域。示例如下(所有类全部放在同一包目录下)。

业务类如下所示。

public class MyService {}

配置类如下所示。

@Configuration
public class MyConfig {

    @Bean
    @Scope(value = "prototype")
    public MyService myService() {
        return new MyService();
    }

}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);

        MyService myService1 = applicationContext.getBean(MyService.class);
        MyService myService2 = applicationContext.getBean(MyService.class);

        System.out.println(myService1 == myService2);
    }

}

运行测试程序,会打印false,这是因为将MyServicebean作用域设置为了prototype,表示原型模式(多例)。

Springbean的作用域如下所示。

作用域说明
Singleton(单例)容器中仅有一个bean实例
Prototype(多例)每次从容器获取到的都是新的bean实例,容器将bean交给获取方后,就不再管理该bean的生命周期
RequestHTTP请求)每次HTTP请求都会产生一个新的bean实例
Session(会话)同一次会话共享同一个bean实例,不同会话bean实例不同

四. @Lazy

@Lazy注解用于设置bean延迟加载,即初始化容器的时候不会初始化延迟加载的bean,等到使用时才初始化bean。示例如下(所有类全部放在同一包目录下)。

业务类如下所示。

public class MyService {

    public MyService() {
        System.out.println("MyService init.");
    }

}

配置类如下所示。

@Configuration
public class MyConfig {

    @Lazy
    @Bean
    public MyService myService() {
        return new MyService();
    }

}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);

        System.out.println("ApplicationContext initialization complete.");

        MyService myService = applicationContext
                .getBean(MyService.class);
    }

}

运行测试程序,打印如下。

超详细总结Spring的配置注解

特别注意:延时加载只针对单例生效。

五. @Conditional

@Conditional表示满足一定条件时才会注册为容器中的bean。示例如下(所有类全部放在同一包目录下)。

两个业务类如下所示。

public class MyWindowsService {}

public class MyLinuxService {}

两个条件类如下所示。

public class WindowsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String osName = environment.getProperty("os.name");

        return osName != null && osName.contains("Windows");
    }

}

public class LinuxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment environment = context.getEnvironment();
        String osName = environment.getProperty("os.name");

        return osName != null && osName.contains("Linux");
    }

}

配置类如下所示。

@Configuration
public class MyConfig {

    @Bean
    @Conditional(WindowsCondition.class)
    public MyWindowsService myWindowsService() {
        return new MyWindowsService();
    }

    @Bean
    @Conditional(LinuxCondition.class)
    public MyLinuxService myLinuxService() {
        return new MyLinuxService();
    }

}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);
    }

}

运行测试程序,打印如下。

超详细总结Spring的配置注解

可见由于MyWindowsService满足条件,所以容器初始化时将MyWindowsServicebean注入到了容器中。

六. @Import

@Import注解可以向容器注册bean,有如下三种方式(所有类全部放在同一包目录下)。

1. 直接注册bean

业务类如下所示。

public class MyService {}

配置类如下所示。

@Configuration
@Import({MyService.class})
public class MyConfig {}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);
    }

}

运行测试程序,打印如下。

超详细总结Spring的配置注解

可见直接将MyService注册为了容器中的bean

2. 通过ImportSelector注册bean

新增业务类如下所示。

public class MyDao {}

自定义一个ImportSelector的实现类,如下所示。

public class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {
                "com.learn.spring.annotation.importt.MyDao"
        };
    }

}

配置类修改如下所示。

@Configuration
@Import({MyService.class, MyImportSelector.class})
public class MyConfig {}

运行测试程序,打印结果如下。

超详细总结Spring的配置注解

可见通过MyImportSelectorMyDao注册到了容器中。

3. 通过ImportBeanDefinitionRegistrar注册bean

新增业务类如下所示。

public class MyController {}

自定义一个ImportBeanDefinitionRegistrar的实现类,如下所示。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry 可以注册BeanDefinition
     *
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        BeanDefinition beanDefinition = new RootBeanDefinition(MyController.class);
        registry.registerBeanDefinition("myController", beanDefinition);
    }

}

配置类修改如下所示。

@Configuration
@Import({MyService.class, MyImportSelector.class,
        MyImportBeanDefinitionRegistrar.class})
public class MyConfig {}

运行测试程序,打印如下。

超详细总结Spring的配置注解

可见将MyController注册到了容器中。

七. FactoryBean

FactoryBean并不是配置注解,但是通过FactoryBean可以帮助创建复杂的bean,例如像MyBatis整合Spring中,就大量使用到了FactoryBean。示例如下(所有类全部放在同一包目录下)。

业务类如下所示。

public class MyService {}

定义一个FactoryBean的实现类,如下所示。

public class MyFactoryBean implements FactoryBean<MyService> {

    @Override
    public MyService getObject() {
        return new MyService();
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }

}

配置类如下所示。

@Configuration
public class MyConfig {

    @Bean
    public MyFactoryBean myFactoryBean() {
        return new MyFactoryBean();
    }

}

测试类如下所示。

public class MyTest {

    public static void main(String[] args) {
        ApplicationContext applicationContext
                = new AnnotationConfigApplicationContext(MyConfig.class);

        Object myService = applicationContext.getBean("myFactoryBean");
        System.out.println(myService);

        Object myFactoryBean = applicationContext.getBean("&myFactoryBean");
        System.out.println(myFactoryBean);
    }

}

运行测试程序,打印如下。

超详细总结Spring的配置注解

在配置类中,将MyFactoryBean注册成了容器中的bean,但是通过名字myFactoryBean无法将MyFactoryBeanbean从容器中获取出来,而获取出来的是MyServicebean。如果需要将MyFactoryBeanbean从容器中获取出来,需要在bean的名字前面加&符号。

通过FactoryBean向容器注册的bean实际上是保存在FactoryBeanRegistrySupportfactoryBeanObjectCache字段中,factoryBeanObjectCache字段是一个MapkeyFactoryBean自己的bean的名字,valueFactoryBean创建的bean的实例,以此来实现单例。

总结

Spring的配置注解,更多的是为如何向容器注册bean服务,那么这里给出容器注册bean的总结。

  1. 配置类中使用@Bean注解向容器注册bean
  2. 使用@ComponentScan注解扫描指定包路径下的类并注册,默认扫描@Controller,@Service,@Component和@Repository注解修饰的类;
  3. 使用@Import注解,有如下三种方式
    1. 使用@Import(类.class)直接向容器注册bean
    2. 通过定义ImportSelector接口的实现类;
    3. 通过定义ImportBeanDefinitionRegistrar接口的实现类.
  4. 使用BeanFactory封装需要注册的bean的类。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 29 天,点击查看活动详情