Spring系列9:基于注解的Spring容器配置
写在前面
前面几篇中我们说过,Spring容器支持3种方式进行bean定义信息的配置,现在具体说明下:
- XML:bean的定义和依赖都在xml文件中配置,比较繁杂。
- Annotation-based :通过直接的方式注入依赖,xml文件配置扫描包路径,xml简化很多。
- Java-based: 通过配置类和注解批量扫描和注册bean,不再需要xml文件。
前面的案例都是基于XML的,这篇介绍Annotation-based方式。
本文内容
- 通过简单案例入门基于注解方式的容器配置使用
Autowired
的详细使用,配合@Primary
、@Qulifier
案例入门1
该入门案例的bean的定义信息在xml文件这个,使用注解来进行依赖注入。
@Autowired:将构造函数、字段、设置方法或配置方法标记为由 Spring 的依赖注入工具自动装配。
定义类并通过@Autowird
注入依赖
public class BeanOne { } public class BeanTwo { // 注解注入 BeanOne @Autowired private BeanOne beanOne; @Override public 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.xsd http://www.springframework.org/schema/context https://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.Test public 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:表示带注释的类是“组件”。在使用基于注释的配置和类路径扫描时,此类被视为自动检测的候选对象
@Component public class RepositoryA implements RepositoryBase { } @Component public class RepositoryB implements RepositoryBase { } @Component public class ServiceA { @Autowired private RepositoryA repositoryA; @Autowired private 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/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://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.Test public 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} true true
结论:
RepositoryA、
RepositoryB、
ServiceA都扫描到容器中管理,
ServiceA的依赖已经自动DI了。相比传统的XML方式,这种方式简洁省心不少。
注意:XML方式和注解配置方式可以混合用。注解注入在 XML 注入之前执行。因此,XML 配置会覆盖注解方式注入的。
@Required
使用
@Required 注解适用于 bean 属性设置方法,如果容器这个没有对应的bean则会抛出异常。
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public 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种注解位置的类
@Component public class Service1 { private RepositoryA repositoryA; private RepositoryB repositoryB; // 标记field @Autowired private RepositoryC repositoryC; // 标记构造函数 @Autowired public Service1(RepositoryA repositoryA) { this.repositoryA = repositoryA; } @Autowired public void setRepositoryB(RepositoryB repositoryB) { this.repositoryB = repositoryB; } @Override public String toString() { return "Service1{" + "repositoryA=" + repositoryA + ", repositoryB=" + repositoryB + ", repositoryC=" + repositoryC + '}'; } }
测试方法和结果如下
@org.junit.Test public 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笔记 获取更多技术系列 */ @Component public class Service2 { @Autowired private List<RepositoryBase> repositoryList; @Autowired private Set<RepositoryBase> repositorySet; @Autowired private RepositoryBase[] repositoryArr; // 省略 Getter和Setter }
测试和结果
@org.junit.Test public 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@2ddc8ecb com.crab.spring.ioc.demo06.RepositoryB@229d10bd com.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@56a6d5a6 com.crab.spring.ioc.demo06.RepositoryB@309e345f com.crab.spring.ioc.demo06.RepositoryA@7a4ccb53
从结果看,顺序是CBA,顺序符合预期。
@Autowired
注入Map
只要
map的键类型是
String,即使是类型化的 Map 实例也可以自动装配。
定义一个类注入map并打印
@Component public class Service3 { @Autowired private Map<String, RepositoryBase> repositoryMap; public void printMap() { this.repositoryMap.entrySet().forEach(entry -> { System.out.println(entry.getKey() + "--" + entry.getKey()); }); } }
测试一下输出结果
@org.junit.Test public 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--repositoryA repositoryB--repositoryB repositoryC--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{ }@Component public class Service4 { @Autowired private RepositoryBase repositoryBase; @Override public String toString() { return "Service4{" + "repositoryBase=" + repositoryBase + '}'; } }
由于容器中有3个
RepositoryBase,Spring无法决定选择哪一个,因此会抛出
UnsatisfiedDependencyException如下。
@org.junit.Test public 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) @Primary public 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笔记 获取更多技术系列 */ @Component public class Service5 { private RepositoryA repositoryA; public RepositoryA getRepositoryA() { return repositoryA; } @Autowired public void setRepositoryA(Optional<RepositoryA> repositoryOptional) { RepositoryA repositoryA = repositoryOptional.orElseGet(() -> new RepositoryA()); this.repositoryA = this.repositoryA; } }
从 Spring Framework 5.0 开始,还可以使用
@Nullable注解来表达可空的概念。
@Component public class Service6 { private RepositoryA repositoryA; @Autowired public 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的名称
@Component public class Service7 { // 指定注入repositoryB @Autowired @Qualifier("repositoryB") private RepositoryBase repository; private RepositoryBase repository2; // 在构造函数中指定注入repositoryA @Autowired public Service7(@Qualifier("repositoryA") RepositoryBase repository2) { this.repository2 = repository2; } // 省略 }
测试一下,观察输出结果。
@org.junit.Test public 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配合各种注解灵活注入依赖覆盖场景。下一篇介绍在基础上介绍更多的注解和类路径的扫描。
知识分享,转载请注明出处。学无先后,达者为先!
- Spring(四)基于注解配置IOC容器&基于注解实现声明式事务
- Spring之基于注解的容器配置
- 码农小汪-spring框架学习之6-spring基于注解的容器配置 @Qualifier @Autowired @Resouce @PostConstruct @PreDestroy
- Spring IOC容器装配Bean_基于注解配置方式
- Spring IOC容器装配Bean_基于注解配置方式
- Spring--基于注解的容器配置介绍
- Spring(四)基于注解配置IOC容器&基于注解实现声明式事务
- 【Spring从入门到精通系列】一、spring基于配置和注解的开发方式介绍
- Spring IOC之基于注解的容器配置
- spring基于注解的配置文件
- spring事物配置,声明式事务管理和基于@Transactional注解的使用
- Spring中基于配置XML与Annotation注解配置AOP
- spring事物配置,声明式事务管理和基于@Transactional注解的使用
- Spring 学习笔记(6)—— 基于注解的配置
- 基于java配置启动spring容器
- spring框架基于注解的容器配置
- Spring基于注解的缓存配置
- 基于注解的Spring AOP的配置和使用--转载
- spring事物配置,声明式事务管理和基于@Transactional注解的使用
- 基于注解的Spring AOP的配置和使用