首页 文章资讯内容详情

Spring(3)——装配 Spring Bean 详解

2026-06-01 4 花语

本文内容纲要:

-装配Bean的概述 -通过XML配置装配Bean -通过注解装配Bean -使用@Compoent装配Bean -自动装配——@Autowired

装配Bean的概述

前面已经介绍了SpringIoC的理念和设计,这一篇文章将介绍的是如何将自己开发的Bean装配到SpringIoC容器中。

大部分场景下,我们都会使用ApplicationContext的具体实现类,因为对应的SpringIoC容器功能相对强大。

而在Spring中提供了3种方法进行配置:

在XML文件中显式配置 在Java的接口和类中实现配置 隐式Bean的发现机制和自动装配原则 方式选择的原则

在现实的工作中,这3种方式都会被用到,并且在学习和工作之中常常混合使用,所以这里给出一些关于这3种优先级的建议:

1.最优先:通过隐式Bean的发现机制和自动装配的原则。 基于约定由于配置的原则,这种方式应该是最优先的

**好处:**减少程序开发者的决定权,简单又不失灵活。

2.其次:Java接口和类中配置实现配置 在没有办法使用自动装配原则的情况下应该优先考虑此类方法

**好处:**避免XML配置的泛滥,也更为容易。 **典型场景:**一个父类有多个子类,比如学生类有两个子类,一个男学生类和女学生类,通过IoC容器初始化一个学生类,容器将无法知道使用哪个子类去初始化,这个时候可以使用Java的注解配置去指定。

3.最后:XML方式配置 在上述方法都无法使用的情况下,那么也只能选择XML配置的方式。

**好处:**简单易懂(当然,特别是对于初学者) **典型场景:**当使用第三方类的时候,有些类并不是我们开发的,我们无法修改里面的代码,这个时候就通过XML的方式配置使用了。

通过XML配置装配Bean

使用XML装配Bean需要定义对应的XML,这里需要引入对应的XML模式(XSD)文件,这些文件会定义配置SpringBean的一些元素,当我们在IDEA中创建XML文件时,会有友好的提示:

一个简单的XML配置文件如下:

<?xmlversion="1.0"encoding="UTF-8"?> <beansxmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>

这就只是一个格式文件,引入了一个beans的定义,引入了xsd文件,它是一个根元素,这样它所定义的元素将可以定义对应的SpringBean

装配简易值

先来一个最简单的装配:

<beanid="c"class="pojo.Category"> <propertyname="name"value="测试"/> </bean>

简单解释一下:

id属性是Spring能找到当前Bean的一个依赖的编号,遵守XML语法的ID唯一性约束。必须以字母开头,可以使用字母、数字、连字符、下划线、句号、冒号,不能以/开头

不过id属性
不是一个必需的属性
,name属性也可以定义bean元素的名称,能以逗号或空格隔开起多个别名,并且可以使用很多的特殊字符

,比如在Spring和SpringMVC的整合中,就得使用name属性来定义bean的名称,并且使用/开头。

注意:

从Spring3.1开始,id属性也可以是String类型了,也就是说id属性也可以使用/开头,而bean元素的id的唯一性由容器负责检查。

如果id和name属性都没有声明的话,那么Spring将会采用“全限定名#{number}”的格式生成编号。例如这里,如果没有声明“id="c"”的话,那么Spring为其生成的编号就是“pojo.Category#0”,当它第二次声明没有id属性的Bean时,编号就是“pojo.Category#1”,以此类推。 class属性显然就是一个类的全限定名 property元素是定义类的属性,其中的name属性定义的是属性的名称,而value是它的值。

这样的定义很简单,但是有时候需要注入一些自定义的类,比如之前饮品店的例子,JuickMaker需要用户提供原料信息才能完成juice的制作:

<!--配置srouce原料--> <beanname="source"class="pojo.Source"> <propertyname="fruit"value="橙子"/> <propertyname="sugar"value="多糖"/> <propertyname="size"value="超大杯"/> </bean> <beanname="juickMaker"class="pojo.JuiceMaker"> <!--注入上面配置的id为srouce的Srouce对象--> <propertyname="source"ref="source"/> </bean>

这里先定义了一个name为source的Bean,然后再制造器中通过ref属性去引用对应的Bean,而source正是之前定义的Bean的name,这样就可以相互引用了。

**注入对象:**使用ref属性 装配集合

有些时候我们需要装配一些复杂的东西,比如Set、Map、List、Array和Properties等,为此我们在Packge【pojo】下新建一个ComplexAssembly类:

packagepojo; importjava.util.List; importjava.util.Map; importjava.util.Properties; importjava.util.Set; publicclassComplexAssembly{ privateLongid; privateList<String>list; privateMap<String,String>map; privatePropertiesproperties; privateSet<String>set; privateString[]array; /*setterandgetter*/ }

这个Bean没有任何的实际意义,知识为了介绍如何装配这些常用的集合类:

<beanid="complexAssembly"class="pojo.ComplexAssembly"> <!--装配Long类型的id--> <propertyname="id"value="1"/> <!--装配List类型的list--> <propertyname="list"> <list> <value>value-list-1</value> <value>value-list-2</value> <value>value-list-3</value> </list> </property> <!--装配Map类型的map--> <propertyname="map"> <map> <entrykey="key1"value="value-key-1"/> <entrykey="key2"value="value-key-2"/> <entrykey="key3"value="value-key-2"/> </map> </property> <!--装配Properties类型的properties--> <propertyname="properties"> <props> <propkey="prop1">value-prop-1</prop> <propkey="prop2">value-prop-2</prop> <propkey="prop3">value-prop-3</prop> </props> </property> <!--装配Set类型的set--> <propertyname="set"> <set> <value>value-set-1</value> <value>value-set-2</value> <value>value-set-3</value> </set> </property> <!--装配String[]类型的array--> <propertyname="array"> <array> <value>value-array-1</value> <value>value-array-2</value> <value>value-array-3</value> </array> </property> </bean> 总结: List属性为对应的<list>元素进行装配,然后通过多个<value>元素设值 Map属性为对应的<map>元素进行装配,然后通过多个<entry>元素设值,只是entry包含一个键值对(key-value)的设置 Properties属性为对应的<properties>元素进行装配,通过多个<property>元素设值,只是properties元素有一个必填属性key,然后可以设置值 Set属性为对应的<set>元素进行装配,然后通过多个<value>元素设值 对于数组而言,可以使用<array>设置值,然后通过多个<value>元素设值。

上面看到了对简单String类型的各个集合的装载,但是有些时候可能需要更为复杂的装载,比如一个List可以是一个系列类的对象,为此需要定义注入的相关信息,其实跟上面的配置没什么两样,只不过加入了ref这一个属性而已:

集合注入总结:

List属性使用<list>元素定义注入,使用多个<ref>元素的Bean属性去引用之前定义好的Bean

Map属性使用<map>元素定义注入,使用多个<entry>元素的key-ref属性去引用之前定义好的Bean作为键,而用value-ref属性引用之前定义好的Bean作为值

Set属性使用<set>元素定义注入,使用多个<ref>元素的bean去引用之前定义好的Bean

命名空间装配

除了上述的配置之外,Spring还提供了对应的命名空间的定义,只是在使用命名空间的时候要先引入对应的命名空间和XML模式(XSD)文件。

——【①c-命名空间】——

c-命名空间是在Spring3.0中引入的,它是在XML中更为简洁地描述构造器参数的方式,要使用它的话,必须要在XML的顶部声明其模式:

注意:是通过构造器参数的方式

现在假设我们现在有这么一个类:

packagepojo; publicclassStudent{ intid; Stringname; publicStudent(intid,Stringname){ this.id=id; this.name=name; } //setterandgetter }

在c-命名空间和模式声明之后,我们就可以使用它来声明构造器参数了:

<!--引入c-命名空间之前--> <beanname="student1"class="pojo.Student"> <constructor-argname="id"value="1"/> <constructor-argname="name"value="学生1"/> </bean> <!--引入c-命名空间之后--> <beanname="student2"class="pojo.Student" c:id="2"c:name="学生2"/>

c-命名空间属性名以“c:”开头,也就是命名空间的前缀。接下来就是要装配的构造器参数名,在此之后如果需要注入对象的话则要跟上-ref(如c:card-ref="idCard1",则对card这个构造器参数注入之前配置的名为idCard1的bean)

很显然,使用c-命名空间属性要比使用<constructor-arg>元素精简,并且会直接引用构造器之中参数的名称,这有利于我们使用的安全性。

我们有另外一种替代方式:

<beanname="student2"class="pojo.Student" c:_0="3"c:_1="学生3"/>

我们将参数的名称替换成了“0”和“1”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下划线来作为前缀。

——【②p-命名空间】——

c-命名空间通过构造器注入的方式来配置bean,p-命名空间则是用setter的注入方式来配置bean,同样的,我们需要引入声明:

然后我们就可以通过p-命名空间来设置属性:

<!--引入p-命名空间之前--> <beanname="student1"class="pojo.Student"> <propertyname="id"value="1"/> <propertyname="name"value="学生1"/> </bean> <!--引入p-命名空间之后--> <beanname="student2"class="pojo.Student" p:id="2"p:name="学生2"/>

我们需要先删掉Student类中的构造函数,不然XML约束会提示我们配置<constructor-arg>元素。

同样的,如果属性需要注入其他Bean的话也可以在后面跟上-ref:

<beanname="student2"class="pojo.Student" p:id="2"p:name="学生2"p:cdCard-ref="cdCard1"/> ——【③util-命名空间】——

工具类的命名空间,可以简化集合类元素的配置,同样的我们需要引入其声明(无需担心怎么声明的问题,IDEA会有很友好的提示):

我们来看看引入前后的变化:

<!--引入util-命名空间之前--> <propertyname="list"> <list> <refbean="bean1"/> <refbean="bean2"/> </list> </property> <!--引入util-命名空间之后--> <util:listid="list"> <refbean="bean1"/> <refbean="bean2"/> </util:list>

<util:list>只是util-命名空间中的多个元素之一,下表提供了util-命名空间提供的所有元素:

元素 描述 <util:constant> 引用某个类型的publicstatic域,并将其暴露为bean <util:list> 创建一个java.util.List类型的bean,其中包含值或引用 <util:map> 创建一个java.util.map类型的bean,其中包含值或引用 <util:properties> 创建一个java.util.Properties类型的bean <util:property-path> 引用一个bean的属性(或内嵌属性),并将其暴露为bean <util:set> 创建一个java.util.Set类型的bean,其中包含值或引用 引入其他配置文件

在实际开发中,随着应用程序规模的增加,系统中<bean>元素配置的数量也会大大增加,导致applicationContext.xml配置文件变得非常臃肿难以维护。

**解决方案:**让applicationContext.xml文件包含其他配置文件即可

使用<import>元素引入其他配置文件

1.在【src】文件下新建一个bean.xml文件,写好基础的约束,把applicationContext.xml文件中配置的<bean>元素复制进去

2.在applicationContext.xml文件中写入:

<importresource="bean.xml"/>

3.运行测试代码,仍然能正确获取到bean:

通过注解装配Bean

上面,我们已经了解了如何使用XML的方式去装配Bean,但是更多的时候已经不再推荐使用XML的方式去装配Bean,更多的时候回考虑使用注解(annotation)的方式去装配Bean。

优势:

1.可以减少XML的配置,当配置项多的时候,臃肿难以维护

2.功能更加强大,既能实现XML的功能,也提供了自动装配的功能,采用了自动装配后,程序猿所需要做的决断就少了,更加有利于对程序的开发,这就是“约定由于配置”的开发原则

在Spring中,它提供了两种方式来让SpringIoC容器发现bean:

**组件扫描:**通过定义资源的方式,让SpringIoC容器扫描对应的包,从而把bean装配进来。 **自动装配:**通过注解定义,使得一些依赖关系可以通过注解完成。

使用@Compoent装配Bean

我们把之前创建的Student类改一下:

packagepojo; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.stereotype.Component; @Component(value="student1") publicclassStudent{ @Value("1") intid; @Value("student_name_1") Stringname; //getterandsetter }

解释一下:

@Component注解: 表示SpringIoC会把这个类扫描成一个bean实例,而其中的value属性代表这个类在Spring中的id,这就相当于在XML中定义的Bean的id:<beanid="student1"class="pojo.Student"/>,也可以简写成@Component("student1"),甚至直接写成@Component,对于不写的,SpringIoC容器就默认以类名来命名作为id,只不过首字母小写,配置到容器中。 @Value注解: 表示值的注入,跟在XML中写value属性是一样的。

这样我们就声明好了我们要创建的一个Bean,就像在XML中写下了这样一句话:

<beanname="student1"class="pojo.Student"> <propertyname="id"value="1"/> <propertyname="name"value="student_name_1"/> </bean>

但是现在我们声明了这个类,并不能进行任何的测试,因为SpringIoC并不知道这个Bean的存在,这个时候我们可以使用一个StudentConfig类去告诉SpringIoC:

packagepojo; importorg.springframework.context.annotation.ComponentScan; @ComponentScan publicclassStudentConfig{ }

这个类十分简单,没有任何逻辑,但是需要说明两点:

该类和Student类位于同一包名下 @ComponentScan注解: 代表进行扫描,默认是扫描当前包的路径,扫描所有带有@Component注解的POJO。

这样一来,我们就可以通过Spring定义好的SpringIoC容器的实现类——AnnotationConfigApplicationContext去生成IoC容器了:

ApplicationContextcontext=newAnnotationConfigApplicationContext(StudentConfig.class); Studentstudent=(Student)context.getBean("student1",Student.class); student.printInformation();

这里可以看到使用了AnnotationConfigApplicationContext类去初始化SpringIoC容器,它的配置项是StudentConfig类,这样SpringIoC就会根据注解的配置去解析对应的资源,来生成IoC容器了。

明显的弊端: 对于@ComponentScan注解,它只是扫描所在包的Java类,但是更多的时候我们希望的是可以扫描我们指定的类 上面的例子只是注入了一些简单的值,测试发现,通过@Value注解并不能注入对象

@Component注解存在着两个配置项:

basePackages:它是由base和package两个单词组成的,而package还是用了复数,意味着它可以配置一个Java包的数组,Spring会根据它的配置扫描对应的包和子包,将配置好的Bean装配进来 basePackageClasses:它由base、package和class三个单词组成,采用复数,意味着它可以配置多个类,Spring会根据配置的类所在的包,为包和子包进行扫描装配对应配置的Bean

我们来试着重构之前写的StudentConfig类来验证上面两个配置项:

packagepojo; importorg.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages="pojo") publicclassStudentConfig{ } //——————————————————【宇宙超级无敌分割线】—————————————————— packagepojo; importorg.springframework.context.annotation.ComponentScan; @ComponentScan(basePackageClasses=pojo.Student.class) publicclassStudentConfig{ }

验证都能通过,bingo!

对于【basePackages】和【basePackageClasses】的选择问题:

【basePackages】的可读性会更好一些,所以在项目中会优先选择使用它,但是在需要大量重构的工程中,尽量不要使用【basePackages】,因为很多时候重构修改包名需要反复地配置,而IDE不会给你任何的提示,而采用【basePackageClasses】会有错误提示。

自动装配——@Autowired

上面提到的两个弊端之一就是没有办法注入对象,通过自动装配我们将解决这个问题。

所谓自动装配技术是一种**由Spring自己发现对应的Bean,自动完成装配工作的方式,**它会应用到一个十分常用的注解@Autowired上,这个时候Spring会根据类型去寻找定义的Bean然后将其注入,听起来很神奇,让我们实际来看一看:

1.先在Package【service】下创建一个StudentService接口:

packageservice; publicinterfaceStudentService{ publicvoidprintStudentInfo(); }

使用接口是Spring推荐的方式,这样可以更为灵活,可以将定义和实现分离

2.为上面的接口创建一个StudentServiceImp实现类:

packageservice; importorg.springframework.beans.factory.annotation.Autowired; importpojo.Student; @Component("studentService") publicclassStudentServiceImpimplementsStudentService{ @Autowired privateStudentstudent=null; //getterandsetter publicvoidprintStudentInfo(){ System.out.println("学生的id为:"+student.getName()); System.out.println("学生的name为:"+student.getName()); } }

该实现类实现了接口的printStudentInfo()方法,打印出成员对象student的相关信息,这里的@Autowired注解,表示在SpringIoC定位所有的Bean后,这个字段需要按类型注入,这样IoC容器就会寻找资源,然后将其注入。

3.编写测试类:

//第一步:修改StudentConfig类,告诉SpringIoC在哪里去扫描它: packagepojo; importorg.springframework.context.annotation.ComponentScan; @ComponentScan(basePackages={"pojo","service"}) publicclassStudentConfig{ } //或者也可以在XML文件中声明去哪里做扫描 <context:component-scanbase-package="pojo"/> <context:component-scanbase-package="service"/> //第二步:编写测试类: packagetest; importorg.springframework.context.ApplicationContext; importorg.springframework.context.annotation.AnnotationConfigApplicationContext; importpojo.StudentConfig; importservice.StudentService; importservice.StudentServiceImp; publicclassTestSpring{ publicstaticvoidmain(String[]args){ //通过注解的方式初始化SpringIoC容器 ApplicationContextcontext=newAnnotationConfigApplicationContext(StudentConfig.class); StudentServicestudentService=context.getBean("studentService",StudentServiceImp.class); studentService.printStudentInfo(); } }

运行代码:

再次理解:@Autowired注解表示在SpringIoC定位所有的Bean后,再根据类型寻找资源,然后将其注入。 过程:定义Bean——》初始化Bean(扫描)——》根据属性需要从SpringIoC容器中搜寻满足要求的Bean——》满足要求则注入 问题:IoC容器可能会寻找失败,此时会抛出异常(默认情况下,SpringIoC容器会认为一定要找到对应的Bean来注入到这个字段,但有些时候并不是一定需要,比如日志) 解决:通过配置项required来改变,比如@Autowired(required=false)

@Autowired注解不仅仅能配置在属性之上,还允许方法配置,常见的Bean的setter方法也可以使用它来完成注入,总之一切需要SpringIoC去寻找Bean资源的地方都可以用到,例如:

/*包名和import*/ publicclassJuiceMaker{ ...... @Autowired publicvoidsetSource(Sourcesource){ this.source=source; } }

在大部分的配置中都推荐使用这样的自动注入来完成,这是SpringIoC帮助我们自动装配完成的,这样使得配置大幅度减少,满足约定优于配置的原则,增强程序的健壮性。

自动装配的歧义性(@Primary和@Qualifier)

在上面的例子中我们使用@Autowired注解来自动注入一个Source类型的Bean资源,但如果我们现在有两个Srouce类型的资源,SpringIoC就会不知所措,不知道究竟该引入哪一个Bean:

<beanname="source1"class="pojo.Source"> <propertyname="fruit"value="橙子"/> <propertyname="sugar"value="多糖"/> <propertyname="size"value="超大杯"/> </bean> <beanname="source2"class="pojo.Source"> <propertyname="fruit"value="橙子"/> <propertyname="sugar"value="少糖"/> <propertyname="size"value="小杯"/> </bean>

我们可以会想到SpringIoC最底层的容器接口——BeanFactory的定义,它存在一个按照类型获取Bean的方法,显然通过Source.class作为参数无法判断使用哪个类实例进行返回,这就是自动装配的歧义性。

为了消除歧义性,Spring提供了两个注解:

@Primary注解: 代表首要的,当SpringIoC检测到有多个相同类型的Bean资源的时候,会优先注入使用该注解的类。

**问题:**该注解只是解决了首要的问题,但是并没有选择性的问题

@Qualifier注解: 上面所谈及的歧义性,一个重要的原因是Spring在寻找依赖注入的时候是按照类型注入引起的。除了按类型查找Bean,SpringIoC容器最底层的接口BeanFactory还提供了按名字查找的方法,如果按照名字来查找和注入不就能消除歧义性了吗?

使用方法:指定注入名称为"source1"的Bean资源

/*包名和import*/ publicclassJuiceMaker{ ...... @Autowired @Qualifier("source1") publicvoidsetSource(Sourcesource){ this.source=source; } }

使用@Bean装配Bean 问题:以上都是通过@Component注解来装配Bean,并且只能注解在类上,当你需要引用第三方包的(jar文件),而且往往并没有这些包的源码,这时候将无法为这些包的类加入@Component注解,让它们变成开发环境中的Bean资源。 解决方案: 1.自己创建一个新的类来扩展包里的类,然后再新类上使用@Component注解,但这样很low 2.使用@Bean注解,注解到方法之上,使其成为Spring中返回对象为Spring的Bean资源。

我们在Package【pojo】下新建一个用来测试@Bean注解的类:

packagepojo; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; @Configuration publicclassBeanTester{ @Bean(name="testBean") publicStringtest(){ Stringstr="测试@Bean注解"; returnstr; } } 注意:@Configuration注解相当于XML文件的根元素,必须要,有了才能解析其中的@Bean注解

然后我们在测试类中编写代码,从SpringIoC容器中获取到这个Bean:

//在pojo包下扫描 ApplicationContextcontext=newAnnotationConfigApplicationContext("pojo"); //因为这里获取到的Bean就是String类型所以直接输出 System.out.println(context.getBean("testBean"));

@Bean的配置项中包含4个配置项:

name:是一个字符串数组,允许配置多个BeanName autowire:标志是否是一个引用的Bean对象,默认值是Autowire.NO initMethod:自定义初始化方法 destroyMethod:自定义销毁方法

使用@Bean注解的好处就是能够动态获取一个Bean对象,能够根据环境不同得到不同的Bean对象。或者说将Spring和其他组件分离(其他组件不依赖Spring,但是又想Spring管理生成的Bean)

Bean的作用域

在默认的情况下,SpringIoC容器只会对一个Bean创建一个实例,但有时候,我们希望能够通过SpringIoC容器获取多个实例,我们可以通过@Scope注解或者<bean>元素中的scope属性来设置,例如:

//XML中设置作用域 <beanid=""class=""scope="prototype"/> //使用注解设置作用域 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

Spring提供了5种作用域,它会根据情况来决定是否生成新的对象:

作用域类别 描述 singleton(单例) 在SpringIoC容器中仅存在一个Bean实例(默认的scope) prototype(多例) 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean():不会在容器启动时创建对象 request(请求) 用于web开发,将Bean放入request范围,request.setAttribute("xxx"),在同一个request获得同一个Bean session(会话) 用于web开发,将Bean放入Session范围,在同一个Session获得同一个Bean globalSession(全局会话) 一般用于Porlet应用环境,分布式系统存在全局session概念(单点登录),如果不是porlet环境,globalSession等同于Session

在开发中主要使用scope="singleton"、scope="prototype",对于MVC中的Action使用prototype类型,其他使用singleton,Spring容器会管理Action对象的创建,此时把Action的作用域设置为prototype.

扩展阅读:@Profile注解、条件化装配Bean

Spring表达式语言简要说明

Spring还提供了更灵活的注入方式,那就是Spring表达式,实际上SpringEL远比以上注入方式都要强大,它拥有很多功能:

使用Bean的id来引用Bean 调用指定对象的方法和访问对象的属性 进行运算 提供正则表达式进行匹配 集合配置

我们来看一个简单的使用Spring表达式的例子:

packagepojo; importorg.springframework.beans.factory.annotation.Value; importorg.springframework.stereotype.Component; @Component("elBean") publicclassElBean{ //通过beanName获取bean,然后注入 @Value("#{role}") privateRolerole; //获取bean的属性id @Value("#{role.id}") privateLongid; //调用bean的getNote方法 @Value("#{role.getNote().toString()}") privateStringnote; /*getterandsetter*/ }

与属性文件中读取使用的“$”不同,在SpringEL中则使用“#”

扩展阅读:Spring表达式语言

参考资料: 《JavaEE互联网轻量级框架整合开发》 《Java实战(第四版)》 万能的百度and万能的大脑

欢迎转载,转载请注明出处!

简书ID:@我没有三颗心脏

github:wmyskxz

欢迎关注公众微信号:wmyskxz_javaweb

分享自己的JavaWeb学习之路以及各种Java学习资料

本文内容总结:装配Bean的概述,通过XML配置装配Bean,通过注解装配Bean,使用@Compoent装配Bean,自动装配——@Autowired,

原文链接:https://www.cnblogs.com/wmyskxz/p/8830632.html