AI智能
改变未来

Spring系列9:基于注解的Spring容器配置


写在前面

前面几篇中我们说过,Spring容器支持3种方式进行bean定义信息的配置,现在具体说明下:

  • XML:bean的定义和依赖都在xml文件中配置,比较繁杂。
  • Annotation-based :通过直接的方式注入依赖,xml文件配置扫描包路径,xml简化很多。
  • Java-based: 通过配置类和注解批量扫描和注册bean,不再需要xml文件。

前面的案例都是基于XML的,这篇介绍Annotation-based方式。

本文内容

  1. 通过简单案例入门基于注解方式的容器配置使用
  2. Autowired

    的详细使用,配合

    @Primary

    @Qulifier

案例入门1

该入门案例的bean的定义信息在xml文件这个,使用注解来进行依赖注入。

@Autowired

:将构造函数、字段、设置方法或配置方法标记为由 Spring 的依赖注入工具自动装配。

定义类并通过

@Autowird

注入依赖

public class BeanOne {}public class BeanTwo {// 注解注入 BeanOne@Autowiredprivate BeanOne beanOne;@Overridepublic String toString() {return "BeanTwo{" +"beanOne=" + beanOne +\'}\';}}

xml文件启用注解配置

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"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.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!--注解配置启用--><context:annotation-config></context:annotation-config><bean class="com.crab.spring.ioc.demo06.BeanOne" id="beanOne"><!--可以通过常规的依赖注入方式注入--></bean><!--依赖是通过注解自动注入的--><bean class="com.crab.spring.ioc.demo06.BeanTwo" id="beanTwo"/></beans>

获取容器中的bean进行使用

@org.junit.Testpublic void test_annotation_config() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring2.xml");BeanTwo beanTwo = context.getBean(BeanTwo.class);System.out.println(beanTwo);context.close();}// 输出BeanTwo{beanOne=com.crab.spring.ioc.demo06.BeanOne@491666ad}

案例入门2

入门案例1中简化传统的xml配置依赖注入的方式,但是bean的定义信息依旧是需要手动配置在xml中的。可以扫描bean的方式进一步简化,增加配置节即可。

<context:component-scan base-package="org.example"/>

通过注解标记类为bean

@Component

:表示带注释的类是“组件”。在使用基于注释的配置和类路径扫描时,此类被视为自动检测的候选对象

@Componentpublic class RepositoryA implements RepositoryBase {}@Componentpublic class RepositoryB  implements RepositoryBase {}@Componentpublic class ServiceA {@Autowiredprivate RepositoryA repositoryA;@Autowiredprivate RepositoryB repositoryB;// 省略 Getter toString()}

xml配置扫描包路径

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:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!--扫描指定包下的组件bean并自动DI--><context:annotation-config/><context:component-scan base-package="com.crab.spring.ioc.demo06"/><!--不再有繁杂的bean的定义--></beans>

获取容器中bean使用

测试程序和上一篇的类似。

package com.crab.spring.ioc.demo06;/*** @author zfd* @version v1.0* @date 2022/1/13 15:11* @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列*/public class Test {@org.junit.Testpublic void test() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");ServiceA serviceA = context.getBean(ServiceA.class);RepositoryA repositoryA = context.getBean(RepositoryA.class);RepositoryB repositoryB = context.getBean(RepositoryB.class);System.out.println(serviceA);System.out.println(serviceA.getRepositoryA() == repositoryA);System.out.println(serviceA.getRepositoryB() == repositoryB);context.close();}}

运行结果

ServiceA{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@27c86f2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@197d671}truetrue

结论:

RepositoryA

RepositoryB

ServiceA

都扫描到容器中管理,

ServiceA

的依赖已经自动DI了。相比传统的XML方式,这种方式简洁省心不少。

注意:XML方式和注解配置方式可以混合用。注解注入在 XML 注入之前执行。因此,XML 配置会覆盖注解方式注入的。

@Required

使用

@Required 注解适用于 bean 属性设置方法,如果容器这个没有对应的bean则会抛出异常。

public class SimpleMovieLister {private MovieFinder movieFinder;@Requiredpublic void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}}

@Required 注解和

RequiredAnnotationBeanPostProcessor

从 Spring Framework 5.1 开始正式弃用,更好的方式是使用构造函数注入(或 InitializingBean.afterPropertiesSet() 的自定义实现或自定义 @PostConstruct 方法以及 bean 属性设置器方法)。

@Autowired

使用

将构造函数、字段、

Setter

方法或配置方法标记为由 Spring 的依赖注入工具自动装配,

required

指定是否必须。这是 JSR-330 @Inject注解的替代方案。

标记在构造函数、字段、

Setter

方法上

来一个综合3种注解位置的类

@Componentpublic class Service1 {private RepositoryA repositoryA;private RepositoryB repositoryB;// 标记field@Autowiredprivate RepositoryC repositoryC;// 标记构造函数@Autowiredpublic Service1(RepositoryA repositoryA) {this.repositoryA = repositoryA;}@Autowiredpublic void setRepositoryB(RepositoryB repositoryB) {this.repositoryB = repositoryB;}@Overridepublic String toString() {return "Service1{" +"repositoryA=" + repositoryA +", repositoryB=" + repositoryB +", repositoryC=" + repositoryC +\'}\';}}

测试方法和结果如下

@org.junit.Testpublic void test_autowired() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");Service1 bean = context.getBean(Service1.class);System.out.println(bean);context.close();}// 结果Service1{repositoryA=com.crab.spring.ioc.demo06.RepositoryA@79efed2d, repositoryB=com.crab.spring.ioc.demo06.RepositoryB@2928854b, repositoryC=com.crab.spring.ioc.demo06.RepositoryC@27ae2fd0}

从输出结果来看,三种位置注入依赖都是可以的。

从 Spring Framework 4.3 开始,如果目标 bean 仅定义一个构造函数,则不再需要在此类构造函数上使用 @Autowired 注释。

@Autowired

注入集合

默认顺序

容器中所有符合类型的都会自动注入,默认顺序是bean注册定义的顺序。

/*** @author zfd* @version v1.0* @date 2022/1/15 17:48* @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列*/@Componentpublic class Service2 {@Autowiredprivate List<RepositoryBase> repositoryList;@Autowiredprivate Set<RepositoryBase> repositorySet;@Autowiredprivate RepositoryBase[] repositoryArr;// 省略 Getter和Setter}

测试和结果

@org.junit.Testpublic void test_collection() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");Service2 service2 = context.getBean(Service2.class);System.out.println(service2.getRepositoryList());System.out.println(service2.getRepositorySet());Arrays.stream(service2.getRepositoryArr()).forEach(System.out::println);context.close();}
[com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153][com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecb, com.crab.spring.ioc.demo06.RepositoryB@229d10bd, com.crab.spring.ioc.demo06.RepositoryC@47542153]com.crab.spring.ioc.demo06.RepositoryA@2ddc8ecbcom.crab.spring.ioc.demo06.RepositoryB@229d10bdcom.crab.spring.ioc.demo06.RepositoryC@47542153

从结果看,顺序是ABC。

通过

@Ordered

指定顺序

RepositoryA
RepositoryB
RepositoryC

上加上

@Ordered

,数值越小优先级越高。

@Component@Order(0) // 指定注入集合时的顺序public class RepositoryA implements RepositoryBase {}@Component@Order(-1)public class RepositoryB  implements RepositoryBase {}@Component@Order(-2)public class RepositoryC implements RepositoryBase{}

还是运行上面的测试

test_collection

,观察输出顺序。

[com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53][com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53, com.crab.spring.ioc.demo06.RepositoryB@309e345f, com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6]com.crab.spring.ioc.demo06.RepositoryC@56a6d5a6com.crab.spring.ioc.demo06.RepositoryB@309e345fcom.crab.spring.ioc.demo06.RepositoryA@7a4ccb53

从结果看,顺序是CBA,顺序符合预期。

@Autowired

注入

Map

只要

map

的键类型是

String

,即使是类型化的 Map 实例也可以自动装配。

定义一个类注入map并打印

@Componentpublic class Service3 {@Autowiredprivate Map<String, RepositoryBase> repositoryMap;public void printMap() {this.repositoryMap.entrySet().forEach(entry -> {System.out.println(entry.getKey() + "--" + entry.getKey());});}}

测试一下输出结果

@org.junit.Testpublic void test_map() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");System.out.println("注入map的键值对如下:");Service3 service3 = context.getBean(Service3.class);service3.printMap();context.close();}
注入map的键值对如下:repositoryA--repositoryArepositoryB--repositoryBrepositoryC--repositoryC

从结果看,成功注入到map中了。

@Autowired

结合

@Primary

@Primary

指示当多个候选者有资格自动装配单值依赖项时,应优先考虑 该bean。

先看一个不加

@Primary

的案例。

@Component@Order(0) // 指定注入集合时的顺序public class RepositoryA implements RepositoryBase {}@Component@Order(-1)public class RepositoryB  implements RepositoryBase {}@Component@Order(-2)public class RepositoryC implements RepositoryBase{}@Componentpublic class Service4 {@Autowiredprivate RepositoryBase repositoryBase;@Overridepublic String toString() {return "Service4{" +"repositoryBase=" + repositoryBase +\'}\';}}

由于容器中有3个

RepositoryBase

,Spring无法决定选择哪一个,因此会抛出

UnsatisfiedDependencyException

如下。

@org.junit.Testpublic void test_require() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");Service4 service4 = context.getBean(Service4.class);System.out.println(service4);context.close();}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name \'service4\': Unsatisfied dependency expressed through field \'repositoryBase\'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type \'com.crab.spring.ioc.demo06.RepositoryBase\' available: expected single matching bean but found 3: repositoryA,repositoryB,repositoryC

RepositoryC

加上

@Primary

@Component@Order(-2)@Primarypublic class RepositoryC implements RepositoryBase{}

再运行上面的测试,成功注入了

RepositoryC

Service4{repositoryBase=com.crab.spring.ioc.demo06.RepositoryC@4df50bcc}

@Autowired

配合

Optional

@Autowired

有个属性

required

指示依赖是否是非必须即可选的,默认是

false

。可以用

java.util.Optional

包装下。

/*** @author zfd* @version v1.0* @date 2022/1/14 11:54* @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列*/@Componentpublic class Service5 {private RepositoryA repositoryA;public RepositoryA getRepositoryA() {return repositoryA;}@Autowiredpublic void setRepositoryA(Optional<RepositoryA> repositoryOptional) {RepositoryA repositoryA = repositoryOptional.orElseGet(() -> new RepositoryA());this.repositoryA = this.repositoryA;}}

从 Spring Framework 5.0 开始,还可以使用

@Nullable

注解来表达可空的概念。

@Componentpublic class Service6 {private RepositoryA repositoryA;@Autowiredpublic void setRepositoryA(@Nullable RepositoryA repositoryA) {this.repositoryA = this.repositoryA;}}

配合@Qualifier

上面提到,当容器中具有多个实例需要确定一个主要候选人时,@Primary 是一种使用类型自动装配的有效方法,具有多个实例。当需要对选择过程进行更多控制时,也可以使用 Spring 的 @Qualifier 注解。通过限定符值与特定参数相关联,缩小类型匹配的范围,以便为每个参数选择特定的 bean。来看案例。

快速使用

依赖类的配置

@Component@Order(0)public class RepositoryA implements RepositoryBase {}@Component("repositoryB") // 指定了名称@Order(-1)public class RepositoryB  implements RepositoryBase {}@Component@Order(-2)@Primary  // 标记为主要的候选者public class RepositoryC implements RepositoryBase{}

@Qualifier

在字段和构造函数上指定bean的名称

@Componentpublic class Service7 {// 指定注入repositoryB@Autowired@Qualifier("repositoryB")private RepositoryBase repository;private RepositoryBase repository2;// 在构造函数中指定注入repositoryA@Autowiredpublic Service7(@Qualifier("repositoryA") RepositoryBase repository2) {this.repository2 = repository2;}// 省略}

测试一下,观察输出结果。

@org.junit.Testpublic void test_qualifier() {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo06/spring1.xml");Service7 service7 = context.getBean(Service7.class);System.out.println(service7);context.close();}
Service1{repository=com.crab.spring.ioc.demo06.RepositoryB@222114ba, repository2=com.crab.spring.ioc.demo06.RepositoryA@16e7dcfd}

结论:

RepositoryC

标记了

@Primary

注解,正常情况下会注入该类实例。通过

@Qualifier

指定注入了

RepositoryB

RepositoryA

,验证成功。

总结

本文介绍了基于注解的Spring容器配置,简化了xml配置文件的编写,提供了2个快速入门案例。重点分析了

@Autowired

配合各种注解灵活注入依赖覆盖场景。下一篇介绍在基础上介绍更多的注解和类路径的扫描。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo06

知识分享,转载请注明出处。学无先后,达者为先!

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Spring系列9:基于注解的Spring容器配置