• Spring基础(2):放弃XML,走向注解


    上一篇并没有实际地带大家去看源码,而是介绍了两个概念:

    • BeanDefinition
    • BeanPostProcessor

    当然,我介绍得非常笼统,不论是BeanDefinition还是BeanPostProcessor其实都有着较为复杂的继承体系,种类也很多。作为Spring系列第一篇,主要目的还是帮大家摆脱对Spring的刻板认知,刷新你们的三观,毕竟太多人对Spring的理解仅限于所谓的IOC和AOP。现在Spring5都出来了,好多人还停留在Spring2.5、Spring3的年代,还在使用XML。

    今天我将会带大家复习Spring的基础,大致流程是:

    • 复习XML方式开发
    • 通过逐步暴露XML的弊端,引出Spring注解
    • 最终完全舍弃XML,采用Spring注解开发

    之所以推荐注解开发,原因有两点:

    • XML配置太繁琐了
    • 掌握Spring注解开发有助于后期学习SpringBoot

    只有熟练使用Spring后,看源码时才能把应用和原理联系起来。

    文章篇幅较长,建议看的时候先把下方目录截图,放在一旁做引导,防止自己看着看着不知道看到哪了。

    主要内容:

    • IOC与DI
    • Spring的3种编程风格与2种注入方式
    • 1️⃣XML配置开发:描述依赖关系
    • 自动装配:让职责单一化
    • 2️⃣XML+注解:XML++@Component
    • @Autowired的小秘密
    • 2️⃣JavaConfig+注解:@Configuration+@ComponentScan+@Component
    • 3️⃣JavaConfig方式:@Configuration+@Bean
    • 大乱斗:@ImportResource、@Component、@Bean

    IOC与DI

    关于IOC的好处,推荐一篇文章,个人觉得写得很好:Spring IoC有什么好处呢?

    大家不妨将IOC理解成一种思想,而DI是实现该思想的一种具体方式。Spring被称为IOC容器,它实现IOC的方式除了DI(Dependency Inject,依赖注入),其实还有DL(Dependency Look,依赖查找)。由于我们平时很少用到DL,所以这里只讨论DI(依赖注入)。

    IOC与DI

    Spring依赖注入的做法

    首先,提供一些配置信息(比如XML)来描述类与类之间的关系,然后由IOC容器(Spring Context)去解析这些配置信息,继而维护好对象之间的关系。

    1. <!-- 配置信息:在XML中定义Bean -->
    2. <bean id="person" class="com.bravo.annotation.Person">
    3. <property name="car" ref="car"></property>
    4. </bean>
    5. <bean id="car" class="com.bravo.annotation.Car"></bean>

    其次,还有一个很重要的前提是,除了配置信息,对象之间也要体现依赖关系。

    1. public class Person {
    2. // Person类中声明了Car,表示Person依赖Car
    3. private Car car;
    4. // 由于上面XML使用了标签,表示setter方法注入,所以必须提供setter方法
    5. public void setCar(Car car) {
    6. this.car = car;
    7. }
    8. }

    总结起来就是:

    • 编写配置信息描述类与类之间的关系(XML/注解/Configuration配置类均可)
    • 对象之间的依赖关系必须在类中定义好(一般是把依赖的对象作为成员变量)
    • Spring会按照配置信息的指示,通过构造方法或者setter方法完成依赖注入

    XML中bean标签的职责:1.定义bean 2.维护bean依赖关系,指导Spring完成依赖注入


    Spring的3种编程风格与2种注入方式

    按照Spring官方文档的说法,Spring的容器配置方式可以分为3种:

    • Schema-based Container Configuration(XML配置)
    • Annotation-based Container Configuration(注解)
    • Java-based Container Configuration(@Configuration配置类)

    Spring支持的2种注入方式:

    • 构造方法注入
    • setter方法注入

    在Spring4之前,Spring还支持接口注入(很少用),这里不提及。

    (这个分类还是有问题,后面分析源码时再解释)

    大家必须要明确,所谓3种编程风格和2种注入方式到底指什么,之间又有什么联系?

    我们从2种注入方式开始分析。

    Q:Spring注入的是什么?
    A:是Bean。
    Q:这些Bean怎么来的?
    A:IOC容器里的。

    所以,所谓的3种编程风格其实指的是“将Bean交给Spring管理的3种方式”,可以理解为IOC,而2种注入方式即DI,是建立在IOC的基础上的。也就是说Spring的DI(依赖注入)其实是以IOC容器为前提。

    3种编程风格其实指的是3种把Bean交给Spring管理的方式,而DI有2种方式:setter方法注入/构造方法注入

    接下来,我们把3种编程风格分别用代码实验一下。

    Spring系列文章我都会贴出完整、可运行的代码,所以建议大家一边看一边复制到本地调试,这样学得更快。


    1️⃣XML配置开发:描述依赖关系

    setter方法注入

    pom.xml

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework</groupId>
    4. <artifactId>spring-context</artifactId>
    5. <version>4.3.12.RELEASE</version>
    6. </dependency>
    7. </dependencies>

    配置信息(setter方法注入)

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <!-- 在xml中描述类与类的配置信息 -->
    7. <bean id="person" class="com.bravo.xml.Person">
    8. <!-- property标签表示,让Spring通过setter方法注入-->
    9. <property name="car" ref="car"></property>
    10. </bean>
    11. <bean id="car" class="com.bravo.xml.Car"></bean>
    12. </bean

    Person(这里偷懒,把后面要讲的构造器注入的准备工作也做了,对运行结果不影响)

    1. public class Person {
    2. // Person依赖Car
    3. private Car car;
    4. // 无参构造
    5. public Person(){}
    6. // 有参构造
    7. public Person(Car car){
    8. this.car = car;
    9. System.out.println("通过构造方法注入...");
    10. }
    11. // setter方法
    12. public void setCar(Car car) {
    13. this.car = car;
    14. System.out.println("通过setter方法注入...");
    15. }
    16. @Override
    17. public String toString() {
    18. return "Person{" +
    19. "car=" + car +
    20. '}';
    21. }

    Car

    1. public class Car {
    2. }

    Test

    1. public class Test {
    2. public static void main(String[] args) {
    3. // 由于是XML配置方式,对应的Spring容器是ClassPathXmlApplicationContext,传入配置文件告知Spring去哪读取配置信息
    4. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
    5. // 从容器中获取Person
    6. Person person = (Person) applicationContext.getBean("person");
    7. System.out.println(person);
    8. }
    9. }

    目录结构

    测试结果

    由于XML中配置依赖信息时,使用了property标签,所以Spring会调用setter方法注入

    构造方法注入

    接下来,我们试一下构造方法注入:

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <!-- 在xml中描述类与类的配置信息 -->
    7. <bean id="person" class="com.bravo.xml.Person">
    8. <!-- constructor-arg标签表示,让Spring通过构造方法注入-->
    9. <constructor-arg ref="car"></constructor-arg>
    10. </bean>
    11. <bean id="car" class="com.bravo.xml.Car"></bean>
    12. </beans>

    测试结果

    其他代码都没变,仅仅改变配置信息,由property标签变为constructor-arg标签,Spring就知道要改为构造器注入了

    至此,我们把XML配置下2种注入方式都实验过了,它们的区别是:

    • XML配置 + 对象提供对应的setter方法
    • XML配置 + 对象提供对应的构造方法

    改变XML配置的同时,需要对象提供对应的方法支持。如果你用了,却没有在类中提供setter方法,则会报错。


    自动装配:让职责单一化

    我们会发现这个标签,其实承载着两个作用:

    • 定义bean,告诉Spring哪个Bean需要交给它管理(放入容器)
    • 维护bean与bean之间的依赖关系

    接下来我们思考这样一个问题:

    对于Person类

    1. public class Person {
    2. // Person依赖Car
    3. private Car car;
    4. public void setCar(Car car) {
    5. this.car = car;
    6. }
    7. }

    上面代码其实已经很好地描述了Person和Car的依赖关系,此时在XML中继续用或者反而成了累赘:

    • 既然类结构本身包含了依赖信息,再用等去描述就显得多余了
    • 如果类结构变动,我们还需要额外维护的依赖信息,很麻烦。比如Person新增了一个shoes字段,那么又要写一个表示shoes

    所以,最好的做法是把让标签职责单一化,让它只负责定义bean,把bean与bean的依赖关系转交给类自身维护(有这个字段就说明有依赖)。

    既然菜鸡的我们能想到,那么Spring肯定也想到了,于是它提出了“自动装配”的概念。很多人一听到自动装配,脑子里只有@Autowired。不算错,但其实XML也支持自动装配,而且真要论先来后到的话,肯定还是XML的自动装配在前。

    XML实现自动装配可以分为两种:全局、局部。

    全局自动装配(XML根标签末尾加default-autowire配置)

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd"
    6. default-autowire="byName">
    7. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    8. <bean id="person" class="com.bravo.xml.Person"></bean>
    9. <bean id="car" class="com.bravo.xml.Car"></bean>
    10. </beans>

    所谓全局,就是在XML根标签末尾再加一个配置default-autowire="byName",那么在此XML中配置的每一个都遵守这个自动装配模式,可选值有4个:

    • byName
    • byType
    • constructor
    • no

    default其实就是no

    测试结果

    我们会发现改用自动装配后,虽然没有了property标签,但是默认是调用setter方法

    局部自动装配(每一个单独设置autowire)

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    7. <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    8. <bean id="car" class="com.bravo.xml.Car"></bean>
    9. </bean

    测试结果

    小结:

    • Spring支持自动装配(全局/局部),把原先标签的职责单一化,只定义bean,而依赖关系交给类本身维护
    • 自动装配共4种,除了no,其他3种各自对应两种注入方式:byName/byType对应setter方法注入,constructor对应构造方法注入 (请自己动手证明)


    1、XML+注解:XML++@Component

    原本标签有两个职责:

    • 定义bean
    • 描述依赖信息

    上面通过自动装配,把依赖信息交给类本身维护,从此只负责bean定义。

    现在,我们想想办法,能不能干脆把bean定义也剥离出来?这样就不需要在XML中写任何标签了。我早就看标签不爽了,这么一大坨,要是bean多了,就很臃肿。

    怎么做呢?

    我们先来回顾一下手上的牌面:

    至此,我们已经成功调教Spring帮我们做了自动装配,也就是说IOC和DI中,DI已经实现自动化。我们接下来要考虑的是如何减少IOC配置的工作量。

    原先是把写在XML中,再把XML喂给Spring:

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");

    既然现在打算消灭XML中的,则说明即使把XML喂给Spring,它也吃不到bean定义了。所以,必须要告诉Spring去哪可以吃到bean。

    我们来看一下,当Spring吃下时,到底吃了什么:

    1. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    2. <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    3. <bean id="car" class="com.bravo.xml.Car"></bean>

    是的,只指定了类名和自动装配的模式。也就是说,要定义一个bean,只需要最基本的两样东西:

    • 类名
    • 装配模式(其实这个也不是必须的,默认no,不自动装配)

    而类名其实很好得到,我们自己写的类不就有吗?至于自动装配的模式,也完全可以在类中通过注解指定。于是,我们找到了改造的方向:用带注解的类代替标签。

    之前:XML中写好bean标签后,把XML喂给Spring,Spring就会把bean实例化加到容器

    现在:消灭bean标签后,XML中已经没有bean,Spring必须自己去找bean定义

    Spring2.5开始提供了一系列注解,比如@Component、@Service等,这些注解都是用来表示bean的。而@Service等注解底层其实还是@Component:

    之所以做一层封装,是为了赋予它特殊的语义:定义Service层的bean。其余的这里不再赘述。总之我们暂时理解为,如果要使用注解表示bean定义,我们能用的只有@Component。

    新建annotation包,把Car和Person移过去:

    Person

    1. @Component //带注解的类,我们希望用这种方式定义bean,并让Spring把它吃进去
    2. public class Person {
    3. // Person依赖Car
    4. private Car car;
    5. @Override
    6. public String toString() {
    7. return "Person{" +
    8. "car=" + car +
    9. '}';
    10. }
    11. }

    Car

    1. @Component
    2. public class Car {
    3. }

    XML(什么都没有配置,连自动装配模式也没指定,因为不在这里定义bean了)

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd">
    6. beans>

    Test(不变)

    1. public class Test {
    2. public static void main(String[] args) {
    3. // 由于是XML配置方式,对应的Spring容器是ClassPathXmlApplicationContext,传入配置文件告知Spring去哪读取配置信息
    4. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
    5. // 从容器中获取Person
    6. Person person = (Person) applicationContext.getBean("person");
    7. System.out.println(person);
    8. }
    9. }

    测试结果

    在Spring容器中找不到person

    其实很好理解,我们传入了spring-context.xml告诉Spring去哪读取bean定义,但是实际上XML却没有配置任何,它是不可能把类实例化加入到容器的。

    然而我们新定义的bean(@Component)Spring也没吃,怎么回事?

    其实主要是因为我们的改变太突然了,Spring以前吃惯了XML中的,现在突然换成@Component这种注解类,它吃不惯,甚至不知道它能吃!

    所以,必须通知Spring:

    老哥,我们改用注解了,有@Component注解的类就是bean,和以前一样一样的。

    如何通知?只要在XML中配置:

     <context:component-scan base-package="com.bravo.annotation"/>

    官方文档对这个标签的解释是:

    The use of implicitly enables the functionality of . There is usually no need to include the element when using .

    翻译过来就是:

    使用隐式地启用了的功能。的作用是让Spring具备解析@Component等注解的功能。当使用时,通常不需要包含元素。

    这个标签的作用相当于什么呢?Spring一口吃下去,发现没有吃到,却吃出了一张小纸条,上面写着:赶紧去找标了@Component注解的类,那是新菜式!

    所以,最终标签的作用有两个:

    • 扫描:原先我们把写有bean定义的XML文件喂给Spring,现在则让Spring自己去指定路径下扫描bean定义
    • 解析:让Spring具备解析注解的功能

    所以,XML虽然不用配置标签,却要配置扫描(需要配置额外的名称空间):

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans
    6. http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context
    8. http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    9. <context:component-scan base-package="com.bravo.annotation"/>
    10. </beans

    测试结果:

    虽然能找到Person了,但是Car并没有被注入

    又出幺蛾子了,怎么回事呢?我们回想一下XML的bean定义:

    1. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    2. <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    3. <bean id="car" class="com.bravo.xml.Car"></bean>

    我们设置了autowire属性,告诉Spring按什么方式自动装配。

    现在我们改用注解了,@Component只是相当于标签,却没有指明自动装配的模式。如何在类中告诉Spring我们需要的装配方式呢?

    方法有很多种:

    • @Autowired(Spring提供的)
    • @Resource(JSR-250定义)
    • @Inject(JSR-330定义)

    这里我们以@Autowired为例:

    1. @Component
    2. public class Person {
    3. // 用@Autowired告知Spring:请把Car装配进来
    4. @Autowired
    5. private Car car;
    6. @Override
    7. public String toString() {
    8. return "Person{" +
    9. "car=" + car +
    10. '}';
    11. }
    12. }

    测试结果

    自动装配成功!


    @Autowired的小秘密

    上面我们有惊无险地从用@Component替换了,并且结识了@Autowired这个超棒的注解,用来完成自动装配。即:

    • +@Component彻底解放IOC配置
    • @Autowired完成自动装配

    但是细心的小伙伴会发现,相较于中的autowire="byName",@Autowired虽然装配成功了,却没有显式地指定自动装配的模式。

    只有一种解释:它有默认的装配方式。

    在探究@Autowire默认的装配模式之前,关于bean的名称,要和大家先交代一下:

    1. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    2. <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    3. <bean id="car" class="com.bravo.xml.Car"></bean>

    中,id即为最终bean在Spring容器的名字。

    同样的,@Component也提供了给bean命名的方法:

    1. @Component("bravo")
    2. public class Person {
    3. // 用@Autowired告知Spring:请把Car装配进来
    4. @Autowired
    5. private Car car;
    6. @Override
    7. public String toString() {
    8. return "Person{" +
    9. "car=" + car +
    10. '}';
    11. }
    12. }

    如果不指定,则默认会把类名首字母小写后作为beanName。

    铺垫结束,我们开始探究@Autowired到底默认是哪种装配模式:

    • byName
    • byType
    • constructor
    • no(已经装配成功,排除)

    先来看看是不是byName

    1. @Component
    2. public class Person {
    3. // 用@Autowired告知Spring:请把Car装配进来
    4. @Autowired
    5. private Car myCar;
    6. @Override
    7. public String toString() {
    8. return "Person{" +
    9. "car=" + myCar +
    10. '}';
    11. }
    12. }

    测试结果

    Car在Spring中bean的名字应该是car,而我把Person中的Car变量名改为myCar,仍旧注入成功,说明不是byName。

    再来看看是不是byType。

    这个稍微有点麻烦,因为我需要弄出至少两个同类型的bean。所以我打算把Car变成接口,然后创建Bmw和Benz两个实现类。这个接口只是为了试验,没有实际意义:

    Car

    1. //接口
    2. public interface Car {
    3. }
    4. //实现类Bmw
    5. @Component
    6. public class Bmw implements Car {
    7. }
    8. //实现类Benz
    9. @Component
    10. public class Benz implements Car {
    11. }

    Person

    1. @Component
    2. public class Person {
    3. // 用@Autowired告知Spring:请把Car装配进来
    4. @Autowired
    5. private Car car;
    6. @Override
    7. public String toString() {
    8. return "Person{" +
    9. "car=" + car +
    10. '}';
    11. }
    12. }

    测试结果

    熟悉的配方、熟悉的味道:expected single matching bean but found 2: BMW,benz

    很明显,@Autowired默认采用byType的方式注入,由于当前Spring容器中存在两个Car类型的bean,所以注入时报错了,因为Spring无法替我们决定注入哪一个。

    但是,有个神奇的现象是,你如果把变量名改为bmw或者benz,就会注入对应的bean:

    1. @Component
    2. public class Person {
    3. // 把变量名改为bmw
    4. @Autowired
    5. private Car bmw;
    6. @Override
    7. public String toString() {
    8. return "Person{" +
    9. "car=" + bmw +
    10. '}';
    11. }
    12. }

    也就是说,@Autowired默认采用byType模式自动装配,如果找到多个同类型的,会根据名字匹配。都不匹配,则会报错。

    当然,有些人可能有强迫症,觉得我Car类型的变量必须叫car,但又想指定注入bmw,怎么办?我们先看看@Autowired能不能指定名字吧:

    不能指定名字,因为Autowired只有一个属性:required,表示当前bean是否必须被注入

    为了弥补@Autowired不能指定名字的缺憾,Spring提供了@Qualifier注解

    1. @Qualifier("benz")
    2. @Autowired
    3. private Car car;

    即使Spring容器中有两个Car类型的bean,也只会按名字注入benz。

    其他的我就不测了,给个结论就好:

    • @Autowired:默认byType,type相同则byName
    • @Resource:和@Autowired几乎一样,但不能配合@Qualifier,因为它本身就可以指定beanName。但没有required属性
    1. @Resource(name = "benz")
    2. private Car car;
    • @Inject:用的很少,不做讨论

    2、JavaConfig+注解:@Configuration+@ComponentScan+@Component

    有没有发现,上面标题还是2️⃣?因为接下来要介绍的,还是注解开发。

    先复习一下前面两种方式:

    • 纯XML(负责定义bean,Java类负责定义依赖,Spring完成自动装配)
    1. <!-- 在xml中只定义bean,无需配置依赖关系 -->
    2. <bean id="person" class="com.bravo.xml.Person" autowire="byName"></bean>
    3. <bean id="car" class="com.bravo.xml.Car"></bean>

    • 注解+XML(@Component+@Autowired,但我们发现注解并不能单独使用,必须要XML中配置开启注解扫描才能生效)
     <context:component-scan base-package="com.bravo.annotation"/>

    之前我在注解(上)讲过,注解的使用必须包含三步:定义注解、使用注解、解析注解。@Component是Spring定义、我们使用,也肯定是由Spring解析。但是这个解析必须由我们手动开启。这就是标签的意义。

    到了这一步我们已经把标签完全消灭了。但是这种模式有点不伦不类。

    你说它叫XML配置开发吧,它又有@Component注解。你说它是注解开发吧,XML中还有一个在那嘚瑟呢。所以如何才能完全消灭XML呢?

    究其根本,我们发现无法消灭XML的原因在于:注解的读取和解析必须依赖于标签!因为我们要帮Spring开启注解扫描,不然他不知道去哪读取bean。

    既然标签可以被@Component代替,那么标签应该也能找到对应的注解。

    不错!这个注解就是@ComponentScan!如此一来我们就再也不需要spring-context.xml了。

    但是转念一想,脊背发凉...ClassPathXmlApplicationContext这个类要求我们必须传一个XML,怎么办?别担心,Spring同样提供了一个注解@Configuration,目的是让我们可以把一个普通的Java类等同于一个XML文件,而这个Java类就是JavaConfig,我们习惯称之为配置类。

    新建一个javaconfig包,把annotation包下的所有类移过来,并且新建AppConfig配置类

    1. @Configuration //表示这个Java类充当XML配置文件
    2. @ComponentScan(basePackages = "com.bravo.javaconfig") //相当于XML中的标签
    3. public class AppConfig {
    4. }

    这样,我们就可以把XML删除,用@ComponentScan来开启注解扫描。

    目录结构

    准备测试时,发现了大麻烦:

    ClassPathXmlApplicationContext无法接受AppConfig配置类,它只认XML

    所以,用AppConfig配置类替代XML只是我们的一厢情愿吗?

    其实是我们选错了实现类。ApplicationContext的子类除了ClassPathXmlApplicationContext,还有一个专门针对注解开发的:AnnotationConfigApplicationContext。

    新的Test

    1. public class Test {
    2. public static void main(String[] args) {
    3. // AnnotationConfigApplicationContext是Spring用来专门针对注解开发的ApplicationContext子类
    4. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    5. // 从容器中获取Person
    6. Person person = (Person) applicationContext.getBean("person");
    7. System.out.println(person);
    8. }
    9. }

    测试结果

    稳得一批

    至此,XML已经被我们完全消灭了。


    3、JavaConfig方式:@Configuration+@Bean

    严格来说,上面的做法并不是所谓的Java-based Container Configuration(@Configuration配置类)风格。我们虽然用到了@Configuration,但只是为了让Java配置类替代XML,最终消灭XML。这也太大材小用了...本质上,这还是@Component+@Autowired注解开发,只是开启注解扫描的方式从标签变为@ComponentScan。

    实际上,真正的Java-based Container Configuration编程风格是这样的:

    AppConfig(如果你不扫描@Component,则不需要@ComponentScan)

    1. @Configuration
    2. public class AppConfig {
    3. //new一个Benz对象,通过@Bean注解告知Spring把这个bean加到容器
    4. @Bean
    5. public Car benz(){
    6. return new Benz();
    7. }
    8. //new一个Bmw对象,通过@Bean注解告知Spring把这个bean加到容器
    9. @Bean
    10. public Car bmw(){
    11. return new Bmw();
    12. }
    13. //new一个Person对象,通过@Bean注解告知Spring把这个bean加到容器
    14. @Bean
    15. public Person person(){
    16. Person p = new Person();
    17. p.setCar(new Benz());
    18. return p;
    19. }
    20. }

    Benz(去除@Component,那是注解开发方式)

    1. public class Benz implements Car {
    2. }

    Bmw(去除@Component,那是注解开发方式)

    1. public class Bmw implements Car {
    2. }

    Person(去除@Component,那是注解开发方式)

    1. public class Person {
    2. private Car car;
    3. // setter方法。在@Bean场景下,手动调用setter方法设置成员变量
    4. public void setCar(Car car) {
    5. this.car = car;
    6. }
    7. @Override
    8. public String toString() {
    9. return "Person{" +
    10. "car=" + car +
    11. '}';
    12. }
    13. }

    测试结果

    小结

    Java-based Container Configuration编程风格指的是:

    • 用@Configuration把一个普通Java类变成配置类,充当XML
    • 在配置类中写多个方法,加上@Bean把返回值对象加到Spring容器中
    • 把配置类AppConfig喂给AnnotationConfigApplicationContext,让它像解析XML一样解析配置类
    • 无需加@Component注解,因为我们可以手动new之后通过@Bean加入容器


    大乱斗:@ImportResource、@Component、@Bean

    其实XML、注解、JavaConfig三种方式相互兼容,并不冲突。

    • XML的
    • @Component注解和扫描(不论是还是@ComponentScan)
    • @Configuration与@Bean

    为了证实它们确实不冲突,我搞了很变态的,一个项目里三种编程方式混用:

    • 两辆车子,bmw和benz交给@Bean(JavaConfig)
    • Person交给@Component和@ComponentScan(注解)
    • Student交给XML和@ImportResource(XML)

    目录结构

    AppConfig

    1. @Configuration //JavaConfig方式,把当前Java类作为配置类
    2. @ComponentScan(basePackages = "com.bravo.all")//注解方式,开启扫描
    3. @ImportResource("spring-context.xml")//XML方式,导入bean定义
    4. public class AppConfig {
    5. @Bean
    6. public Car benz(){
    7. return new Benz();
    8. }
    9. @Bean
    10. public Car bmw(){
    11. return new Bmw();
    12. }
    13. }

    Car

    1. public interface Car {
    2. }

    Benz(JavaConfig方式:@Bean加入Spring)

    1. public class Benz implements Car {
    2. }

    Bmw(JavaConfig方式:@Bean加入Spring)

    1. public class Bmw implements Car {
    2. }

    Person(注解方式:@ComponentScan扫描@Component加入Spring)

    1. @Component
    2. public class Person {
    3. // 用@Autowired告知Spring:请把Car装配进来
    4. @Qualifier("benz")
    5. @Autowired
    6. private Car car;
    7. @Override
    8. public String toString() {
    9. return "Person{" +
    10. "car=" + car +
    11. '}';
    12. }
    13. }

    Student(XML方式:使用定义)

    1. public class Student {
    2. private Car bmw;
    3. //由于在下方XML配置中,我选用了byName自动装配,而byName/byType都要提供setter方法
    4. public void setBmw(Car bmw) {
    5. this.bmw = bmw;
    6. }
    7. @Override
    8. public String toString() {
    9. return "Student{" +
    10. "car=" + bmw +
    11. '}';
    12. }
    13. }

    spring-context.xml

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans.xsd">
    6. <bean id="student" class="com.bravo.all.Student" autowire="byName">
    7. bean>
    8. beans>

    Test

    1. public class Test {
    2. public static void main(String[] args) {
    3. // AnnotationConfigApplicationContext是Spring用来专门针对注解开发的ApplicationContext子类
    4. ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
    5. // 从容器中获取Person
    6. Person person = (Person) applicationContext.getBean("person");
    7. System.out.println(person);
    8. // 从容器中获取Student
    9. Student student = (Student) applicationContext.getBean("student");
    10. System.out.println(student);
    11. }
    12. }

    测试结果

    通常来说,我们日常开发一般是注解+JavaConfig混用。也就是

    • @ComponentScan+@Configuration+@Component+@Bean


    小结

    • 纯XML配置开发:没有注解,全部标签,但也可以配置自动装配
    • 注解开发不能单独存在,需要开启扫描。自动装配一般用@Autowired
      • XML+注解:XML++@Component
      • JavaConfig+注解:@Configuration+@ComponentScan+@Component
    • JavaConfig方式:@Configuration+@Bean

    通常我们都会两两混用,比如XML+注解,或者JavaConfig+注解,但很少三种一起用。

    本文目的是让大家知道:

    • 3种编程风格:XML、注解、JavaConfig
    • 2种注入方式:setter方法、构造方法
    • 4种装配模式:byType、byName、constructor、no

    然后,有一点需要和大家说明,本文关于所谓的2种注入方式和4种装配方式,在宏观上来说是对的,但是在源码层面上来说,还是太笼统了。有机会的话,后面专门写一篇自动装配相关的文章。

  • 相关阅读:
    动态添加二级表头 You may have an infinite update loop in a component rende 9 function.
    java中截取字符串最后一位
    Java数据结构与算法(最长回文子串中心扩散法)
    C++设计模式-适配器(Adapter)
    腾讯发布 3D 虚拟场景自动生成解决方案,用 AIGC 助力游戏开发提效
    动态数组的插入、删除、统计、打印
    docker-compose实现容器任务编排
    Python Opencv实践 - 矩形轮廓绘制(直边矩形,最小外接矩形)
    A Guide to Java HashMap
    HTML5+CSS3小实例:悬停放大图片的旅游画廊
  • 原文地址:https://blog.csdn.net/smart_an/article/details/134262551