Spring注解驱动开发系列(一)Spring容器组件的注册

简介: Spring注解驱动开发系列(一)Spring容器组件的注册

引言

用过SpringBoot的同学都知道,SpringBoot框架使用注解来代替繁琐的XML配置文件用以管理对象的生命周期,相信大家都被大量的XML配置文件折磨过,但在SpringBoot中,开发人员只需要进行极少量的配置就可以构建出一个优秀的应用。
当然,这一切都建立在大量的注解上,虽然注解的使用相对于XML配置文件来说非常方便,但也因为如此,使得SpringBoot入门简单精通难,因为想要精通就需要熟悉注解的功能和底层的实现。
从本篇文章开始将连载Spring注解驱动开发系列,带大家理解SpringBoot底层的注解并灵活使用,这对后续SpringBoot的学习将会大有裨益。

@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.class, Service.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 {
   

}

Java8及以上的版本可以通过配置多个@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才能注册,否则将全部无法注册,该注解在SpringBoot底层中的使用十分频繁。

@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()返回的对象。

相关文章
|
11天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
554 6
|
9天前
|
Java Spring 容器
Spring使用异步注解@Async正确姿势
Spring使用异步注解@Async正确姿势,异步任务,spring boot
|
8天前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean
|
9天前
|
XML 前端开发 Java
控制spring框架注解介绍
控制spring框架注解介绍
|
22天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
21天前
|
弹性计算 运维 持续交付
探索Docker容器化技术及其在生产环境中的应用
探索Docker容器化技术及其在生产环境中的应用
70 5
|
14天前
|
Linux iOS开发 Docker
Docker:容器化技术的领航者 —— 从基础到实践的全面解析
在云计算与微服务架构日益盛行的今天,Docker作为容器化技术的佼佼者,正引领着一场软件开发与部署的革命。它不仅极大地提升了应用部署的灵活性与效率,还为持续集成/持续部署(CI/CD)提供了强有力的支撑。
192 69
|
1天前
|
Kubernetes Cloud Native 持续交付
云原生之旅:Docker容器化与Kubernetes集群管理
【9月更文挑战第33天】在数字化转型的浪潮中,云原生技术如同一艘航船,带领企业乘风破浪。本篇文章将作为你的航海指南,从Docker容器化的基础讲起,直至Kubernetes集群的高级管理,我们将一起探索云原生的奥秘。你将学习到如何封装应用、实现环境隔离,以及如何在Kubernetes集群中部署、监控和扩展你的服务。让我们启航,驶向灵活、可伸缩的云原生未来。
|
4天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker与Kubernetes入门
【9月更文挑战第30天】在云计算的浪潮中,云原生技术正以前所未有的速度重塑着软件开发和运维领域。本文将通过深入浅出的方式,带你了解云原生的核心组件——Docker容器和Kubernetes集群,并探索它们如何助力现代应用的构建、部署和管理。从Docker的基本命令到Kubernetes的资源调度,我们将一起开启云原生技术的奇妙之旅。
|
14天前
|
运维 Cloud Native Docker
云原生技术入门:Docker容器化实战
【9月更文挑战第20天】本文将引导你走进云原生技术的世界,通过Docker容器化技术的实战演练,深入理解其背后的原理和应用。我们将一起探索如何在云平台上利用Docker简化部署、扩展和管理应用程序的过程,并揭示这一技术如何改变现代软件的开发和运维模式。
下一篇
无影云桌面