首页 文章详情

Spring注解:容器注册组件的方式,通俗易懂!

Java技术迷 | 307 2021-06-23 08:52 0 0 0
UniSMS (合一短信)

汪伟俊 作者

Java技术迷 | 出品


本篇文章将介绍Spring注解中的组件注册方式。我觉得写的还算通俗易懂,希望对你入门有帮助吧!如有错误或未考虑完全的地方,望不吝赐教。

@Configuration 和 @Bean 注解

先来讲讲 @Configuration 和 @Bean 这两个注解,现在有如下一个 Bean 类:

public class Dog {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog [name=" + name + ", age=" + age + "]";
    }
}

在之前的 Spring 框架中,我们需要在配置文件中作如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="dog" class="com.wwj.bean.Dog">
        <property name="name" value="Tom"></property>
        <property name="age" value="5"></property>
    </bean>

</beans>

然后通过 ApplicationContext 获得容器中的对象:

    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationcontext.xml");
        Dog dog = ctx.getBean(Dog.class);
        System.out.println(dog);
    }

以上是通过 XML 配置文件实现的,那么这些操作如何通过注解实现呢?我们需要通过一个配置类来实现:

@Configuration // 声明一个配置类
public class MyConfig {

    @Bean // 将该类放入到容器中,类型为返回值类型,默认以方法名为id
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("Tom");
        dog.setAge(5);
        return dog;
    }
}

其中 @Configuration 注解用于声明该类是一个配置类,配置类的作用等价于配置文件。

@Bean 注解用于将方法返回值的对象放入到容器中,默认以方法名为对象 id。如果想修改 id,可以修改方法名,如果不想通过修改方法名对 id 进行修改,还可以通过 @Bean 注解的 value 属性指定 id。

@Bean("tomDog")

在从容器中获取对象的过程也和之前不太一样,不再是使用 ClassPathXmlApplicationContext 类了,而是使用 AnnotationConfigApplicationContext 类获得容器对象:

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class);
        Dog dog = (Dog) ctx.getBean("tomDog");
        System.out.println(dog);
    }

@ComponentScan 注解

在实际开发中,通过 bean 标签配置 Bean 类显然很繁琐,所以通常会使用包扫描将要扫描的包下的组件自动放入容器中,我们同样回顾一下在之前的 Spring 中是如何进行包扫描的:

<context:component-scan base-package="com.wwj"></context:component-scan>

在配置文件中进行这样的配置,Spring 将会在 com.wwj 包及其子包下自动扫描所有的类,将 @Controller、@Service、@Repository、@Component 注解的类自动放入容器中。那么如何通过注解实现同样的功能呢?这就需要用到 @ComponentScan 注解:

@Configuration // 声明一个配置类
@ComponentScan("com.wwj")
public class MyConfig {

}

在配置类上使用 @ComponentScan 注解,并通过 value 属性指定需要扫描的包。

我们还知道,在配置文件中,可以通过 <context:exclude-filter> 和 <context:include-filter> 子节点对扫描的类进行控制。那么同样地,在 @ComponentScan 注解中,我们通过 excludeFilters 和 includeFilters 属性进行控制。但需要注意的是,这两个属性的值均为 Filter 类型的数组,所以它们需要借助 @Filter 注解进行控制,而 @Filter 需要指定 type 属性值作为控制方式:例如通过注解控制、通过给定类型控制、通过正则控制、自定义控制等等,这里以通过注解控制举例:

@Configuration // 声明一个配置类
@ComponentScan(value = "com.wwj", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = { Controller.classService.class }) })
public class MyConfig 
{

}

该注解即排除了 @Controller 和 @Service 注解标注的类,这些类将不会被放入容器中。

excludeFilters 属性的用法与其类似,但还是有必要讲一下,不知道大家还记不记得,在之前的 Spring 框架中,若是想要 <context:include-filter> 节点生效,则需要将 Spring 默认的控制规则禁用,即:将 use-defalut-filters 属性置为 false 才行。那么同样地,在注解中,我们也需要将默认的控制规则禁用:

@Configuration // 声明一个配置类
@ComponentScan(value = "com.wwj", includeFilters = { @Filter(type = FilterType.ANNOTATION, classes = { Controller.class,
        Service.class }) }, useDefaultFilters 
false)
public class MyConfig {

}

Java 8 及以上的版本可以通过配置多个 @ComponentScan 注解来搭配控制规则,而 Java8 以下则需要通过 @ComponentScans 进行配置,该注解只有一个属性 value,值类型即为 @ComponentScan 类型。

其它的几种控制方式也都很简单,例如通过类型控制:

@Configuration // 声明一个配置类
@ComponentScan(value = "com.wwj", includeFilters = {
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { DemoController.class }) }, useDefaultFilters false)
public class MyConfig {

}

其它方式不做过多解释,重点说一下自定义规则:

public class MyTypeFilter implements TypeFilter {

    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException 
{
        return false;
    }
}

自定义类实现 TypeFilter 接口,通过重写方法 match() 进行处理即可,然后将其配置到配置类中:

@Configuration // 声明一个配置类
@ComponentScan(value = "com.wwj", includeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = { MyTypeFilter.class }) })
public class MyConfig 
{

}

@Scope 注解

该注解用于指定 Bean 的作用范围,它有四种取值:

  • prototype:多实例,每次从容器中获取都创建对象
  • singleton:单实例,整个容器中只有一个实例,默认为单实例
  • request:同一个请求创建一个实例
  • session:同一次会话创建一个实例

使用方式如下:

@Configuration
public class MyConfig {

    @Scope("prototype")
    @Bean 
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("Tom");
        dog.setAge(5);
        return dog;
    }
}

@Lazy 注解

我们知道,单实例的 Bean 实在 Spring 容器初始化的时候就被创建了的,之后使用只需从容器中获取即可,那么 @Lazy 注解的作用就是懒加载。也就是说,容器在初始化的时候并不会去创建 Bean 对象,而是当使用该 Bean 对象的时候才去创建,用法如下:

@Configuration
public class MyConfig {

    @Lazy
    @Bean 
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("Tom");
        dog.setAge(5);
        return dog;
    }
}

@Conditional 注解

该注解可以用于制定一些条件在注册 Bean 之前,你可以自定义一个类实现 Condition 接口,并重写 matches() 方法:

public class MyCondition implements Condition {

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

在该方法中进行一些条件的判断,然后配置即可,用法如下:

@Configuration
public class MyConfig {

    @Conditional({ MyCondition.class })
    @Bean 
    public Dog dog() 
{
        Dog dog = new Dog();
        dog.setName("Tom");
        dog.setAge(5);
        return dog;
    }
}

该注解还可以标注在类上,当标注在类上时,如果满足自定义的条件,类中的所有 Bean 才能注册,否则将全部无法注册,该注解在 Spring Boot 底层中的使用十分频繁。

@Import 注解

到目前为止,我们将 Bean 放入容器中的方式有两种:

  • 通过 @Controller、@Service、@Repository、@Component 注解和包扫描搭配使用
  • 通过 @Bean 注解

当然,除了这两种,Spring 还提供了一种方式 @Import,用法如下:

@Configuration
@Import(Dog.class)
public class MyConfig 
{

}

这种方式的特点就是简单快速、无需方法、无需扫描,一个注解就完成了 Bean 的注册操作。

@Import 支持 ImportSelector 导入 Bean 类,具体用法如下:自定义一个类实现 ImportSelector 接口,并重写 selectImports() 方法,该方法的返回值即为需要放入容器中的 Bean,返回值需为 Bean 的全路径。

public class MyImportSelector implements ImportSelector {

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {};
    }
}

需要注意的是,即便是你不想注册任何一个 Bean,你也应该返回一个空数组而不是 null,因为返回 null 会抛出空指针异常。

MyImportSelector 定义好后,在配置类中进行配置即可,用法如下:

@Configuration
@Import(MyImportSelector.class)
public class MyConfig 
{

}

@Import 还支持 ImportBeanDefinitionRegistrar 导入 Bean 类,具体用法如下。

自定义一个类实现 ImportBeanDefinitionRegistrar 接口,并重写 registerBeanDefinitions() 方法,该方法有一个 BeanDefinitionRegistry 类型的参数,只需调用该对象的 registerBeanDefinition() 方法即可,该方法将传入 Bean 的名称和 Bean 的定制信息。

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Dog.class);
        registry.registerBeanDefinition("dog", beanDefinition);
    }
}

MyImportBeanDefinitionRegistrar 定义好后,在配置类中进行配置,配置方式和前面相同:

@Configuration
@Import(MyImportBeanDefinitionRegistrar .class)
public class MyConfig 
{

}

@FactoryBean 注解

该注解同样可以将 Bean 放入容器中,但它有点特殊,直接来看看用法。

自定义一个类实现 FactoryBean 接口,并重写方法:

public class DogFactoryBean implements FactoryBean<Dog>{

    public Dog getObject() throws Exception {
        return new Dog();
    }

    public Class<?> getObjectType() {
        return Dog.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

泛型即为需要注册的 Bean 类型,第一个方法返回需要注册的对象,第二个方法返回注册的对象类型,第三个方法设置该对象是否为单例。

接下来在配置类中编写方法,通过 @Bean 注解将 DogFactoryBean 放入容器中:

@Configuration
public class MyConfig {

    @Bean
    public DogFactoryBean dogFactoryBean() {
        return new DogFactoryBean();
    }
}

很神奇的事情是,当你从容器中获取这个 DogFactoryBean 对象的时候,得到的却是 Dog 对象,所以 FactoryBean 获取的其实是 getObject() 返回的对象。

好了,以上就是本篇文章的全部内容了。我觉得写的还算通俗易懂,希望对你入门有帮助吧!

本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载。

点击下方公众号

回复关键字【电子书】领取资料

1、Intellij IDEA这样 配置注释模板,让你瞬间高出一个逼格!

2、Spring+SpringMVC+Mybatis实现校园二手交易平台【实战项目】

3、基于SpringBoot的迷你商城系统,附源码!

4、把Redis当作队列来用,真的合适吗?

5、惊呆了,Spring Boot居然这么耗内存!你知道吗?

6、全网最全 Java 日志框架适配方案!还有谁不会?

7、Spring中毒太深,离开Spring我居然连最基本的接口都不会写了

点分享

点收藏

点点赞

点在看

good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter