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

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

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

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

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

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

public class BeanOne {
}

public class BeanTwo {
    // 注解注入 BeanOne
    @Autowired
    private BeanOne beanOne;

    @Override
    public String toString() {
        return "BeanTwo{" +
                "beanOne=" + beanOne +
                '}';
    }
}

@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}

入门案例1中简化传统的xml配置依赖注入的方式,但是bean的定义信息依旧是需要手动配置在xml中的。可以扫描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配置文件,非常简洁


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

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 注解适用于 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 属性设置器方法)。

将构造函数、字段、 Setter方法或配置方法标记为由 Spring 的依赖注入工具自动装配, required指定是否必须。这是 JSR-330 @Inject注解的替代方案。

来一个综合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 注释。

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

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/15 17:48
 * @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
 */
@Component
public class Service2 {
    @Autowired
    private List repositoryList;
    @Autowired
    private Set 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。

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,顺序符合预期。

只要 map的键类型是 String,即使是类型化的 Map 实例也可以自动装配。

定义一个类注入map并打印

@Component
public class Service3 {
    @Autowired
    private Map 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中了。

@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有个属性 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 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;
    }
}

上面提到,当容器中具有多个实例需要确定一个主要候选人时,@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配合各种注解灵活注入依赖覆盖场景。下一篇介绍在基础上介绍更多的注解和类路径的扫描。

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

Original: https://www.cnblogs.com/kongbubihai/p/15856859.html
Author: kongxubihai
Title: Spring系列9:基于注解的Spring容器配置

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572058/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 经典实验–飞机大战小游戏

    ·一、需求设计 1.为检测C语言的学习成果,根据所学的C语言知识,设计程序:飞机大战小游戏; 2.自行定义变量,函数或结构体,编写源代码并进行编译运行测试; 3.根据编写的代码,自…

    Java 2023年6月15日
    059
  • web 网页nginx反向代理【检查到弱密码套件:不支持完全向前保密 】处理

    web 项目扫描出如下问题: 处理方式如下: 打开nginx配置文件nginx.conf,添加如下内容: python;gutter:true;ssl_ciphers ‘AES12…

    Java 2023年5月30日
    077
  • 封装axios

    为了方便调用, 因此对 axios作了封装代码: import axios from "axios"; import { AxiosRequestConfig …

    Java 2023年6月7日
    0106
  • Spring使用外部属性文件

    一、在 Spring Config 文件中配置 Bean 时,有时候需要在 Bean 的配置里添加系统部署的细节信息, 如文件路径,数据源配置信息。而这些部署细节实际上需要在配置文…

    Java 2023年5月30日
    091
  • 系统性掌握SpringBoot

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/franson-2016/p/13320880.html…

    Java 2023年5月30日
    086
  • Java判定一个数值是否在指定的开闭区间范围内

    对于开闭区间,在数学中的表示方式通常为 () 和 [],小括号代表开放区间,中括号代表封闭区间,而它们的区别主要在于是否包含 = 等于号,开闭区间通常会分为以下一些情形: (1, …

    Java 2023年6月8日
    052
  • LeetCode.1103-向人们分发糖果(Distribute Candies to People)

    这是小川的第 393次更新,第 425篇原创 今天介绍的是 LeetCode算法题中 Easy级别的第 256题(顺位题号是 1103)。我们通过以下方式向一排 n = num_p…

    Java 2023年6月5日
    073
  • Floyd算法(三)之 Java详解

    和Dijkstra算法一样,弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授…

    Java 2023年5月29日
    0115
  • spring整合mybatis

    <dependencies> <dependency> <groupid>mysql</groupid> <artifacti…

    Java 2023年6月15日
    092
  • 方法理论学习

    什么是方法 方法在很多地方又称作函数,方法是为完成一个操作而组合在一起的语句组 好处:可以省略编写重复代码;可以组织和简化代码;提高代码的可读性 方法的种类 内置方法 由JDK类库…

    Java 2023年6月6日
    083
  • 运算符(1)

    算数运算符 在Java中,使用算术运算符+、-、*、/表示加、减、乘、除运算。当参与/运算的两个操作数都是整数时,表示整数除法运算;否则,表示浮点除法。整数的求余操作(取模)用%表…

    Java 2023年6月5日
    073
  • Java注解最全详解(超级详细)

    Java注解是一个很重要的知识点,掌握好Java注解有利于学习Java开发框架底层实现。@mikechen Java注解定义 Java注解又称Java标注,是在 JDK5 时引入的…

    Java 2023年6月15日
    096
  • 我把自己的java库发布到了maven中央仓库,从此可以像Jackson、Spring的jar一样使用它了

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kuberne…

    Java 2023年6月8日
    075
  • Android反编译之修改应用包名

    前言 近期看B站数码区这条视频 【大米】破处理器,它能行吗?K50电竞版评测_哔哩哔哩_bilibili 时,发现了UP主的一个比较骚的操作: 嗯?apk文件可以直接拿来使用修改包…

    Java 2023年6月7日
    094
  • Spring AOP全面详解(超级详细)

    如果说 IOC 是 Spring 的核心,那么面向切面编程AOP就是 Spring 另外一个最为重要的核心@mikechen AOP的定义 AOP (Aspect Orient P…

    Java 2023年6月15日
    093
  • idea激活码

    转载请注明出处。 作者:peachyy 出处:http://www.cnblogs.com/peachyy/ 出处:https://peachyy.gitee.io/ 出处:htt…

    Java 2023年6月16日
    081
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球