1.快速入门-IOC-DL(依赖查找)

1.1 引入依赖

对于快速入门来说,我们只需要引入一个依赖即可:spring-context

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.2.8.RELEASE</version>
  5. </dependency>

1.2 创建配置文件

image.png

  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. https://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <!-- 申明bean的id,和bean的class-->
  7. <bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"></bean>
  8. </beans>

1.3 申明一个普通类

创建Person
image.png``
无需填充任何内容, 在1.2中配置,指向此类的声明

1.4 创建启动类

有了配置文件,就需要创建启动类,读取配置文件,加载配置文件刀spring的IOC中

  1. public class QuickstartByNameApplication {
  2. public static void main(String[] args) {
  3. // 采用ClassPathXmlApplicationContext来加载配置文件刀到BeanFactory 中
  4. // BeanFactory 是一个接口,采用借口接收,满足多台思想
  5. BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");
  6. // 从factory中获取Bean 通过Id名称获取
  7. Person person = (Person) factory.getBean("person");
  8. // 打印类的全限定名+内存地址
  9. System.out.println(person);
  10. }
  11. }

运行main方法后悔打印person的内存地址, 可以发现,我们使用spring创建了Person的实例,不再是使用new的方式创建类的实例,这就是依赖查找

2.依赖查找

1.1 byName

参考快速入门

1.2 byType

配置文件修改

  1. <bean class="com.linkedbear.spring.basic_dl.b_bytype.bean.Person"></bean>

启动类修改

  1. public class QuickstartByTypeApplication {
  2. public static void main(String[] args) {
  3. BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-bytype.xml");
  4. Person person = factory.getBean(Person.class);
  5. System.out.println(person);
  6. }
  7. }

1.3 ofType —一次性查找多个

声明一个 DemoDao ,并声明 3 种对应的实现类,分别模拟操作 MySQL 、Oracle 、Postgres 数据库的实现类:
image.png
对应的配置类,也把这几个 Bean 都注册上:

  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. https://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="demoMySQLDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoMySQLDao"/>
  7. <bean id="demoOracleDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoOracleDao"/>
  8. <bean id="demoPostgreDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoPostgresDao"/>
  9. </beans>

启动类

  1. public class OfTypeApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-oftype.xml");
  4. Map<String, DemoDao> beans = ctx.getBeansOfType(DemoDao.class);
  5. beans.forEach((beanName, bean) -> {
  6. System.out.println(beanName + " : " + bean.toString());
  7. });
  8. }
  9. }

1.4 通过注解查找

声明Bean+注解+配置文件
新建一个包 d_withanno ,并在里面声明一个注解:@Color 。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface Color {
  5. }

之后,创建几个类,以及运行 main 方法的启动类:
image.png
启动类

  1. public class WithAnnoApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
  4. Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);
  5. beans.forEach((beanName, bean) -> {
  6. System.out.println(beanName + " : " + bean.toString());
  7. });
  8. }
  9. }

运行 main 方法,可以发现控制台只打印了 Black 和 Red ,证明成功取出。

black : com.linkedbear.spring.basic_dl.d_withanno.bean.Black@553f17c red : com.linkedbear.spring.basic_dl.d_withanno.bean.Red@6c3708b3

1.5 获取IOC容器中所有的Bean

方法: getBeanDefinitionNames

  1. public class BeannamesApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
  4. String[] beanNames = ctx.getBeanDefinitionNames();
  5. // 利用jdk8的Stream快速编写打印方法
  6. Stream.of(beanNames).forEach(System.out::println);
  7. }
  8. }

1.6 延迟查找

对于一些特殊的场景,需要依赖容器中的某些特定的 Bean ,但当它们不存在时也能使用默认 / 缺省策略来处理逻辑。这个时候,使用上面已经学过的方式倒是可以实现,但编码可能会不很优雅。

1.6.1 使用现有方案实现Bean缺失时的缺省加载

当获取不到对应的Bean的时候报错 NoSuchBeanDefinitionException 在cathc中去创建这个Bean

  1. public class ImmediatlyLookupApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-lazylookup.xml");
  4. Cat cat = ctx.getBean(Cat.class);
  5. System.out.println(cat);
  6. Dog dog;
  7. try {
  8. dog = ctx.getBean(Dog.class);
  9. } catch (NoSuchBeanDefinitionException e) {
  10. // 找不到Dog时手动创建
  11. dog = new Dog();
  12. }
  13. System.out.println(dog);
  14. }
  15. }

既然作为一个容器,能获取自然就能有检查,ApplicationContext 中有一个方法就可以专门用来检查容器中是否有指定的 Bean :containsBean

  1. Dog dog = ctx.containsBean("dog") ? (Dog) ctx.getBean("dog") : new Dog();

但注意,这个 containsBean 方法只能传 bean 的 id ,不能查类型,所以虽然可以改良前面的方案,但还是有问题:如果 Bean 的名不叫 dog ,叫 wangwang ,那这个方法岂不是废了?所以这个方案还是不够好,需要改良。

1.6.2 延迟查找

如果能有一种机制,我想获取一个 Bean 的时候,你可以先不给我报错,先给我一个包装让我拿着,回头我自己用的时候再拆开决定里面有还是没有,这样是不是就省去了 IOC 容器报错的麻烦事了呢?在 SpringFramework 4.3 中引入了一个新的 API :ObjectProvider ,它可以实现延迟查找。

  1. public class LazyLookupApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-lazylookup.xml");
  4. Cat cat = ctx.getBean(Cat.class);
  5. System.out.println(cat);
  6. // 下面的代码会报Bean没有定义 NoSuchBeanDefinitionException
  7. // Dog dog = ctx.getBean(Dog.class);
  8. // 这一行代码不会报错
  9. ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
  10. }
  11. }

可以发现,ApplicationContext 中有一个方法叫 getBeanProvider ,它就是返回上面说的那个“包装”。如果直接 getBean ,那如果容器中没有对应的 Bean ,就会报 NoSuchBeanDefinitionException;如果使用这种方式,运行 main 方法后发现并没有报错,只有调用 dogProvider 的 getObject ,真正要取包装里面的 Bean 时,才会报异常。所以总结下来,ObjectProvider 相当于延后了 Bean 的获取时机,也延后了异常可能出现的时机
但是,上面的问题还没有被解决呀,调用 getObject 方法还是会报异常,那下面咱就继续研究 ObjectProvider 的其他一些方法。
ObjectProvider 中还有一个方法:getIfAvailable ,它可以在找不到 Bean 时返回 null 而不抛出异常。使用这个方法,就可以避免上面的问题了。改良之后的代码如下:
有三种写法:

  1. Dog dog = dogProvider.getIfAvailable();
  2. if (dog == null) {
  3. dog = new Dog();
  4. }
  1. Dog dog = dogProvider.getIfAvailable(() -> new Dog());
  1. Dog dog = dogProvider.getIfAvailable(Dog::new);

3.依赖注入

通过上面创建的Bean实例都是不携带任何属性的,如果我们创建的Bean需要预设一些属性,就需要采用IOC的依赖注入

1.1配置文件实现简单的属性注入

声明两个类:Person 与 Cat ,形成“猫需要依赖人”的场景:

  1. public class Person {
  2. private String name;
  3. private Integer age;
  4. // getter and setter ......
  5. }
  6. public class Cat {
  7. private String name;
  8. private Person master;
  9. // getter and setter ......
  10. }

之后,咱在 resources 目录下新建 basic_di 文件夹,并声明配置文件 inject-set.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. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. https://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
  7. <property name="name" value="test-person-byset"/>
  8. <property name="age" value="18"/>
  9. </bean>
  10. <bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
  11. <property name="name" value="test-cat"/>
  12. <!--ref 标签可以关联 bean-->
  13. <property name="master" ref="person"/>
  14. </bean>
  15. </beans>

编写启动类

  1. public class QuickstartInjectBySetXmlApplication {
  2. public static void main(String[] args) throws Exception {
  3. BeanFactory beanFactory = new ClassPathXmlApplicationContext("basic_di/inject-set.xml");
  4. Person person = beanFactory.getBean(Person.class);
  5. System.out.println(person);
  6. Cat cat = beanFactory.getBean(Cat.class);
  7. System.out.println(cat);
  8. }
  9. }

1.2 setter属性注入

  1. @Bean
  2. public Person person() {
  3. Person person = new Person();
  4. person.setName("test-person-anno-byset");
  5. person.setAge(18);
  6. return person;
  7. }

1.3 构造器注入

  1. public Person(String name, Integer age) {
  2. this.name = name;
  3. this.age = age;
  4. }

1.4 通过@Value 注解从配置文件读取

通过@Value 或者@ConfigurationProperties 从配置文件读取参数

1.5 在一个bean中注入另一个bean

1.直接在属性上使用@Autowired

  1. @Component
  2. public class Dog {
  3. // ......
  4. @Autowired
  5. private Person person;

2.在构造方法上使用

  1. @Component
  2. public class Dog {
  3. // ......
  4. private Person person;
  5. @Autowired
  6. public Dog(Person person) {
  7. this.person = person;
  8. }

3.在setter上使用

  1. @Component
  2. public class Dog {
  3. // ......
  4. private Person person;
  5. @Autowired
  6. public void setPerson(Person person) {
  7. this.person = person;
  8. }

4.使用@Autowired 注入不存在的Bean
简单概括这个异常,就是说本来想找一个类型为 Person 的 Bean ,但一个也没找到!那必然没找到啊,@Component 注解被注释掉了,自然就不会注册了。如果出现这种情况下又不想让程序抛异常,就需要在 @Autowired 注解上加一个属性:required = false

  1. @Autowired(required = false)
  2. private Person person;
  1. 使用@Component 向容器中注入Bean的时候指定名称

@Component(“administrator”)

  1. 使用 @Autowired 注入 指定名称的Bean
    1. @Autowired
    2. @Qualifier("administrator")
    3. private Person person;
    7.@Primary:默认Bean
    @Primary 注解的使用目标是被注入的 Bean ,在一个应用中,一个类型的 Bean 注册只能有一个,它配合 @Bean 使用,可以指定默认注入的 Bean :
    1. @Bean
    2. @Primary
    3. public Person master() {
    4. Person master = new Person();
    5. master.setName("master");
    6. return master;
    7. }
    重新运行测试类,发现 Cat 中注入的 Person 是 master ,Dog 中注入的还是 administrator ,可见 @Qualifier 不受 @Primary 的干扰。
    同样的,在 xml 中可以指定 标签中的 primary 属性为 true ,跟上面标注 @Primary 注解是一样的。

    1.6@Autowired注入的原理逻辑

    先拿属性对应的类型,去 IOC 容器中找 Bean ,如果找到了一个,直接返回;如果找到多个类型一样的 Bean , 把属性名拿过去,跟这些 Bean 的 id 逐个对比,如果有一个相同的,直接返回;如果没有任何相同的 id 与要注入的属性名相同,则会抛出

4.注册驱动与组建扫描

在使用XML驱动的IOC的容器中,使用的是ClassPathXmlApplicationContext 对应的的类路径下的xml驱动, 还可以使用注解加载驱动的方式,

4.1 注解驱动IOC的依赖查找

  1. @Configuration
  2. public class QuickstartConfiguration {
  3. // 对应配置文件中的bean标签
  4. @Bean(name = "aaa") // 4.3.3之后可以直接写value
  5. public Person person() {
  6. return new Person();
  7. }
  8. }

这种使用方式,可以解释为:向 IOC 容器注册一个类型为 Person ,id 为 person 的 Bean方法的返回值代表注册的类型,方法名代表 Bean 的 id 。当然,也可以直接在 @Bean 注解上显式的声明 Bean 的 id ,只不过在注解驱动的范畴里,它不叫 id 而是叫 name

启动类:

  1. public class AnnotationConfigApplication {
  2. public static void main(String[] args) throws Exception {
  3. ApplicationContext ctx = new AnnotationConfigApplicationContext(QuickstartConfiguration.class);
  4. Person person = ctx.getBean(Person.class);
  5. System.out.println(person);
  6. }
  7. }

4.2注解IOC容器的其他用法

翻看 AnnotationConfigApplicationContext 的构造方法,可以发现它还有一个方法,是传入一组 basePackage ,翻译过来是 “根包” 的意思,它又是什么意思呢?这就涉及到下面的概念了:组件注册与扫描

4.3组建注册与组建扫描

上面声明的方式,如果需要注册的组件特别多,那编写这些 @Bean 无疑是超多工作量,于是 SpringFramework 中给咱整了几个注解出来,可以帮咱快速注册需要的组件,这些注解被成为模式注解 ( stereotype annotation )
在类上标注 @Component 注解,即代表该类会被注册到 IOC 容器中作为一个 Bean 。

4.3.1一切组件注册的根源:@Component

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

如果不指定 Bean 的名称,它的默认规则是 “类名的首字母小写”(例如 Person 的默认名称是 person ,DepartmentServiceImpl 的默认名称是 departmentServiceImpl )。

4.3.2 组建扫描 @ComponentScan

只声明了组件,咱在写配置类时如果还是只写 @Configuration 注解,随后启动 IOC 容器,那它是感知不到有 @Component 存在的,一定会报 NoSuchBeanDefinitionException 。
为了解决这个问题,咱需要引入一个新的注解:@ComponentScan 。
在配置类上额外标注一个 @ComponentScan ,并指定要扫描的路径,它就可以扫描指定路径包及子包下的所有 @Component 组件

  1. @Configuration
  2. @ComponentScan("com.linkedbear.spring.annotation.c_scan.bean")
  3. public class ComponentScanConfiguration {
  4. }

如果不指定扫描路径,则默认扫描本类所在包及子包下的所有 @Component 组件
上面的方法等同于如下

  1. ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.annotation.c_scan.bean");

1.5 给Bean注入另一个Bean

  1. 直接在属性上使用@Autowired

    1. @Component
    2. public class Dog {
    3. // ......
    4. @Autowired
    5. private Person person;
    6. }
  2. 在构造方法上使用

    1. @Component
    2. public class Dog {
    3. // ......
    4. private Person person;
    5. @Autowired
    6. public Dog(Person person) {
    7. this.person = person;
    8. }

    3.在setter上使用

    注: 小结和思考

    1.依赖查找与依赖注入的对比

  • 作用目标不同
    • 依赖注入的作用目标通常是类成员
    • 依赖查找的作用目标可以是方法体内,也可以是方法体外
  • 实现方式不同
    • 依赖注入通常借助一个上下文被动的接收
    • 依赖查找通常主动使用上下文搜索

      2.IOC 的两种实现方式是什么?它们的区别和联系是什么?

3. BeanFactory与ApplicationContext

BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext 是 BeanFactory 的子接口。它增加了:

  • 与 SpringFramework 的 AOP 功能轻松集成
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层特定的上下文,例如 Web 应用程序中使用的 WebApplicationContext
  • 对比了 BeanFactory 与 ApplicationContext 的不同指标: | Feature | BeanFactory | ApplicationContext | | —- | —- | —- | | Bean instantiation/wiring —— Bean的实例化和属性注入 | Yes | Yes | | Integrated lifecycle management —— 生命周期管理 | No | Yes | | Automatic BeanPostProcessor registration —— Bean后置处理器的支持 | No | Yes | | Automatic BeanFactoryPostProcessor registration —— BeanFactory后置处理器的支持 | No | Yes | | Convenient MessageSource access (for internalization) —— 消息转换服务(国际化) | No | Yes | | Built-in ApplicationEvent publication mechanism —— 事件发布机制(事件驱动) | No | Yes |

BeanFactory 接口提供了一个抽象的配置和对象的管理机制,ApplicationContext 是 BeanFactory 的子接口,它简化了与 AOP 的整合、消息机制、事件机制,以及对 Web 环境的扩展( WebApplicationContext 等),BeanFactory 是没有这些扩展的。
ApplicationContext 主要扩展了以下功能:

  • AOP 的支持( AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
  • 配置元信息( BeanDefinition 、Environment 、注解等 )
  • 资源管理( Resource 抽象 )
  • 事件驱动机制( ApplicationEvent 、ApplicationListener )
  • 消息与国际化( LocaleResolver )
  • Environment 抽象