写在前面

前面几篇中我们说过,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
@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

结论:RepositoryARepositoryBServiceA 都扫描到容器中管理,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指定注入了RepositoryBRepositoryA,验证成功。

总结

本文介绍了基于注解的Spring容器配置,简化了xml配置文件的编写,提供了2个快速入门案例。重点分析了@Autowired配合各种注解灵活注入依赖覆盖场景。下一篇介绍在基础上介绍更多的注解和类路径的扫描。

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

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

Spring系列9:基于注解的Spring容器配置的更多相关文章

  1. 一步一步深入spring(5)--使用基于注解的spring实现 AOP

    1.要利用spring aop,至少需要添加以下jar包 使用spring需要的jarspring.jar .commons-logging.jar 使用切面编程(AOP)需要的jar aspectj ...

  2. Spring系列20:注解详解和Spring注解增强(基础内功)

    有部分小伙伴反馈说前面基于注解的Spring中大量使用注解,由于对Java的注解不熟悉,有点难受.建议总结一篇的Java注解的基础知识,那么,它来了! 本文内容 什么是注解? 如何定义注解 如何使用注 ...

  3. Spring6:基于注解的Spring MVC(上篇)

    什么是Spring MVC Spring MVC框架是一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC ...

  4. 基于注解的Spring AOP的配置和使用

    摘要: 基于注解的Spring AOP的配置和使用 AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程.可以通过预编译方式和运行期动态代理实现在不 ...

  5. Spring MVC中基于注解的 Controller

         终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法以响 ...

  6. 使用 Spring 2.5 基于注解驱动的 Spring MVC

    http://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/ 概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Sp ...

  7. 使用 Spring 2.5 基于注解驱动的 Spring MVC--转

    概述 继 Spring 2.0 对 Spring MVC 进行重大升级后,Spring 2.5 又为 Spring MVC 引入了注解驱动功能.现在你无须让 Controller 继承任何接口,无需在 ...

  8. Spring:基于注解的Spring MVC

    什么是Spring MVC Spring MVC框架是一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC ...

  9. 基于注解的 Spring MVC(上)

    什么是Spring MVC Spring MVC框架是一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC ...

随机推荐

  1. hdu-3833 YY's new problem(数组标记)

    http://acm.hdu.edu.cn/showproblem.php?pid=3833 做这题时是因为我在网上找杭电的数论题然后看到说这道题是数论题就点开看了以下. 然后去杭电上做,暴力,超时了 ...

  2. HDC2021技术分论坛:“积木拼装”,HarmonyOS弹性部署大揭秘!

    作者:peitaiyi,华为终端OS产品交付专家 HarmonyOS是一款面向万物互联时代的.全新的分布式操作系统.在传统的单设备系统能力基础上,HarmonyOS提出了基于同一套系统能力.适配多种终 ...

  3. 基于React和Node.JS的表单录入系统的设计与实现

    一.写在前面 这是一个真实的项目,项目已经过去好久了,虽然很简单,但还是有很多思考点,跟随着笔者的脚步,一起来看看吧.本文纯属虚构,涉及到的相关信息均已做虚构处理, 二.背景 人活着一定要有信仰,没有 ...

  4. Redis 实现了自己的 VM

    Redis的VM(虚拟内存)机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据). Redis提高数据库容量的办法有两种: 1.一种是可以 ...

  5. Augmentation For GAN

    目录 概 主要内容 Differentiable Augmentation Adaptive Augmentation 代码 Zhao S., Liu Z., Lin J., Zhu J. and H ...

  6. Gradient-based Hyperparameter Optimization through Reversible Learning

    目录 概 主要内容 算法 finite precision arithmic 实验 Maclaurin D, Duvenaud D, Adams R P, et al. Gradient-based ...

  7. <数据结构>图的构建与基本遍历方法

    目录 建立一个图 邻接矩阵 邻接表 深度优先遍历(DFS) 具体步骤: 第一部分:给定结点u,遍历u所在的连通块的所有结点 第二部分:对图G所有结点进行第一部分的操作,即遍历了图的所有连通分量 伪代码 ...

  8. Java基础周测一、二(50题)

    一.单选题 (共50题,250分) 1.下列选项不可作为Java语言变量名的是(    ). A. a1 B. $1 C. _1 D. 21 正确答案: D 2.有一段Java应用程序,它的类名是a1 ...

  9. windows环境下node安装教程(超详细)

    安装node.js 1.下载node: 下载地址:http://nodejs.cn/download/ node.js的zip包安装时是直接解压缩后就可以了, node.js的msi包是傻瓜式一路ne ...

  10. Windows下安装配置jdk

    1.jdk安装 从官网获取jdk安装包后, 双击图形化安装,一路next即可. 2.配置JavaHome 打开计算机->系统属性->高级系统设置->环境变量 在系统变量下面添JAVA ...