《SpringBoot 3:入门与应用实战》第 2 章 IOC 思想与实现 阅读笔记 3
《SpringBoot 3入门与应用实战》第 2 章 IOC 思想与实现 阅读笔记 32.4 注解驱动的 IOC从 Spring Boot 发布以来使用 XML 配置文件的场景越来越少取而代之的是基于注解配置类来驱动 IOC 容器。从 Spring Framework 推出3.0版本后支持的最低 Java 版本为 Java 5Java 5 最有特点的新特性之一就是引入了注解Spring Framework 3.0 开始也通过引入大量注解代替XML 的方式进行声明式开发。2.4.1 注解驱动 IOC 的依赖查找相较于 XML 配置文件的驱动方式基于注解驱动的配置会全部编写在配置类中一个配置类可以理解为一个 XML 配置文件。配置类只需要在类上标注一个 Configuration 注解。与 XML 配置文件使用 标签的方式类比通过注解配置类注册 Bean 所使用的是 Bean 注解。方法的返回值代表注册的类型方法名代表 Bean 的 id。也可以直接在 Bean 注解中使用 name 属性显式地声明 Bean 的 id。packagecom.yangjunbo.annotation.examplea;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassQuickstartConfiguration{// 等同于 bean idperson classcom.yangjunbo.annotation.examplea.Person/Bean(nameperson)publicPersonperson(){returnnewPerson();}}注解驱动的场景下Spring Framework 提供的是 AnnotationConfigApplicationContext用作注解驱动 IOC 容器。整体的编码方式非常简单甚至与 XML 配置文件的方式没有什么区别。packagecom.yangjunbo.annotation.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(QuickstartConfiguration.class);Personpersonctx.getBean(Person.class);System.out.println(person);}}2.4.2 注解驱动 IOC 的依赖注入编写基于 Bean 注解的依赖注入注入哪些属性完全是在代码中编写的。这种写法与使用 XML 配置文件的方式完全等价。packagecom.yangjunbo.annotation.examplea;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassAnnotationDIConfiguration{BeanpublicPersonperson(){PersonpersonnewPerson();person.setName(person);person.setAge(123);returnperson;}BeanpublicCatcat(){CatcatnewCat();cat.setName(test-cat-anno);// 直接拿上面的person()方法作为返回值即可相当于refcat.setMaster(person());returncat;}}packagecom.yangjunbo.annotation.examplea;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(QuickstartConfiguration.class);Personpersonctx.getBean(Person.class);System.out.println(person);ApplicationContextctx2newAnnotationConfigApplicationContext(AnnotationDIConfiguration.class);Catcatctx2.getBean(Cat.class);System.out.println(cat);}}2.4.3 组件注册与扫描机制翻看 AnnotationConfigApplicationContext 的构造方法可以发现它还有一个传入 basePackage 的构造方法要想理解这个概念就需要了解注解驱动中一个非常重要的机制组件注册与扫描。1.组件注册的根源ComponentSpring Framework 规定当一个类上标注了 Component 注解并被组件扫描时这个类将在 IOC 容器初始化时被创建并注册到 IOC 容器中成为一个 Bean。默认情况下组件生成的 beanName 为“首字母小写的类名”例如 Person 的默认名称是 personDemoServiceImpl 的默认名称是demoServiceImpl。如果需要指定 Bean 的名称就在 Component 注解中声明 value 属性。注意如果 标签不声明 id 属性默认生成的 Bean 的名称不是“首字母小写的类名”这一点与注解扫描的规则不同packagecom.yangjunbo.annotation.exampleb;importorg.springframework.stereotype.Component;ComponentpublicclassPerson{}2.组件扫描如果只是将 Component 注解标注在类上而不进行扫描那么 IOC 容器将无法感知到组件注册当没有扫描组件时强行获取 Bean一定会抛出 NoSuchBeanDefinitionException 异常为此需要引入一个新的注解用于组件扫描ComponentScan。ComponentScan注解通常标注在配置类上。在代码清单2-31中我们在配置类ComponentScanConfiguration上额外标注一个ComponentScan 注解并指定要扫描的包路径它就可以扫描指定路径包及子包下的所有 Component 组件。如果不指定扫描路径就默认扫描本类所在包及子包下的所有 Component 组件。可以一次性声明多个扫描的包。声明了 ComponentScan 之后重新启动配置类可以发现 Person 已经成功被注册如果 Spring Framework 的版本比较老可能会看到如下的写法ComponentScan(basePackages“com.linkedbear.spring.annotation.c_scan.bean”)这两个写法实质上是一样的。packagecom.yangjunbo.annotation.exampleb;importorg.springframework.context.annotation.ComponentScan;importorg.springframework.context.annotation.Configuration;ConfigurationComponentScan(com.yangjunbo.annotation.exampleb)publicclassComponentScanConfiguration{}Spring Framework 还提供了第二种组件扫描的使用方式。在 AnnotationConfigApplicationContext 的构造方法中有一个类型为 String 可变参数的构造方法当我们传入需要扫描的包之后也可以直接扫描到那些标注了 Component 的 Bean 并注册到 IOC 容器中。packagecom.yangjunbo.annotation.exampleb;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(ComponentScanConfiguration.class);System.out.println(ctx.getBean(Person.class));ApplicationContextctx2newAnnotationConfigApplicationContext(com.yangjunbo.annotation.exampleb);System.out.println(ctx2.getBean(Person.class));}}对于 XML 配置文件驱动的 IOC 容器同样可以启用组件扫描只需要在 XML 配置文件中声明一个标签 context:component-scan之后使用ClassPathXmlApplicationContext 驱动 IOC 容器同样可以获取 person 对象。?xml version1.0 encodingUTF-8?beansxmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://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.xsdcontext:component-scanbase-packagecom.yangjunbo.annotation.exampleb//beanspackagecom.yangjunbo.annotation.exampleb;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(ComponentScanConfiguration.class);System.out.println(ctx.getBean(Person.class));ApplicationContextctx2newAnnotationConfigApplicationContext(com.yangjunbo.annotation.exampleb);System.out.println(ctx2.getBean(Person.class));BeanFactorybeanFactorynewClassPathXmlApplicationContext(annotation/inject-scan.xml);PersonpersonbeanFactory.getBean(Person.class);System.out.println(person);}}3.组件注册的其他注解Spring Framework 为了迎合 Web 应用开发时所采用的经典三层架构额外提供了三个注解Controller、Service、Repository分别对应三层架构中的表现层、业务层、持久层。这三个注解的作用与 Component 完全一致其实它们的底层也都是 Component。有了以上三个注解在进行符合三层架构的应用开发时对于那些业务逻辑层的类就可以直接标注注解而不用一个个地写 标签或者借助 Bean 注解进行组件注册。4.Configuration 也是 Component如果在驱动注解 IOC 容器时直接扫描包括配置类在内的整个根包则在运行 main 方法时会看到配置类 ComponentScanConfiguration 也被注册到 IOC 容器中。packagecom.yangjunbo.annotation.exampleb;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;importjava.util.stream.Stream;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{ApplicationContextctxnewAnnotationConfigApplicationContext(ComponentScanConfiguration.class);System.out.println(ctx.getBean(Person.class));ApplicationContextctx2newAnnotationConfigApplicationContext(com.yangjunbo.annotation.exampleb);System.out.println(ctx2.getBean(Person.class));BeanFactorybeanFactorynewClassPathXmlApplicationContext(annotation/inject-scan.xml);PersonpersonbeanFactory.getBean(Person.class);System.out.println(person);ApplicationContextctx3newAnnotationConfigApplicationContext(com.yangjunbo.annotation.exampleb);String[]beanNamesctx3.getBeanDefinitionNames();Stream.of(beanNames).forEach(System.out::println);}}翻看 Configuration 注解的源码可以发现它也被标注了 Component 注解说明被标注 Configuration 注解的类对于 IOC 容器而言同样也是一个 Bean。2.4.4 注解驱动与XML驱动互通如果一个应用中既有注解驱动配置又有 XML 配置文件则可能出现由一方引入另一方配置的情况。1.XML 配置文件引入注解驱动在 XML 中要引入注解驱动需要开启注解配置同时注册对应的配置类。?xml version1.0 encodingUTF-8?beansxmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://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/beanclasscom.yangjunbo.annotation.examplec.AnnotationDIConfiguration//beanspackagecom.yangjunbo.annotation.examplec;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{BeanFactorybeanFactorynewClassPathXmlApplicationContext(annotation/examplec/inject-annotation.xml);CatcatbeanFactory.getBean(Cat.class);System.out.println(cat);}}2.注解配置类引入 XML 配置文件在注解配置类中引入 XML 配置文件需要在配置类上标注 ImportResource 注解并声明配置文件的路径。?xml version1.0 encodingUTF-8?beansxmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdbeanidpersonclasscom.yangjunbo.annotation.examplec.Personpropertynamenamevaluetest-person-byset/propertynameagevalue18//beanbeanidcatclasscom.yangjunbo.annotation.examplec.Catpropertynamenamevaluetest-cat/!-- ref引用上面的person对象 --propertynamemasterrefperson//bean/beanspackagecom.yangjunbo.annotation.examplec;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.ImportResource;ConfigurationImportResource(classpath:annotation/examplec/inject-configuration.xml)publicclassXmlDIConfiguration{}packagecom.yangjunbo.annotation.examplec;importcom.yangjunbo.annotation.exampleb.ComponentScanConfiguration;importcom.yangjunbo.annotation.exampleb.Person;importorg.springframework.beans.factory.BeanFactory;importorg.springframework.context.ApplicationContext;importorg.springframework.context.annotation.AnnotationConfigApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;publicclassAnnotationConfigApplication{publicstaticvoidmain(String[]args)throwsException{BeanFactorybeanFactorynewClassPathXmlApplicationContext(annotation/examplec/inject-annotation.xml);CatcatbeanFactory.getBean(Cat.class);System.out.println(cat);ApplicationContextctxnewAnnotationConfigApplicationContext(XmlDIConfiguration.class);System.out.println(ctx.getBean(Cat.class));}}