3、基于注解的IoC装配与依赖注入

一、XML开启注解支持

1、添加context名称空间

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

</beans>

2、开启注解支持

1、在xml配置中,开启对Spring IoC核心注解的支持有两种方式:
  • 使用<context:annotation-config />标签
  • 使用<context:component-scan base-package="" />标签

3、annotation-config

1、它的作用是隐式地向IoC容器中注册以下4个BeanPostProcessor,不同的BeanPostProcessor用于检测、处理各自对应的的注解服务,相当于注解处理器,不同的Spring版本有不同的支持,详细请参考官网。
  • AutowiredAnnotationBeanPostProcessor:用来识别处理@Autowired@Value注解。
  • CommonAnnotationBeanPostProcessor:用来识别处理@Resource@PostConstruct@PreDestroy等注解。
  • PersistenceAnnotationBeanPostProcessor:用来识别和处理@PersistenceContext注解。
  • RequiredAnnotationBeanPostProcessor:用来识别和处理@Required的注解。

4、component-scan

1、它具有annotation-config的全部功能,它必须指定一个base-package属性,表示会自动扫描指定的包路径下面的所有类,并将带有注册注解的Bean注册到IoC容器中。
2、默认情况下自动将带有@Component、@Repository、@Service、@Controller、@RestController、@ControllerAdvice 和@Configuration等注解的对象注册到IoC容器中。
3、当使用context:component-scan后,可以将context:annotation-config配置移除
4、base-package属性说明:
  • 如果要配置扫描多个包路径,可以使用' '、','、';'等符号分割。
  • 也可以使用通配符*,例如:com.itan.*表示扫描com.itan包路径下的所有的类及所有子包中的类。
<context:annotation-config />

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

二、@Configuration注解

1、概述

1、@Configuration是一个类级别的注解,用于定义一个类为配置类,可替换bean.xml配置文件注册Bean对象。
2、被注解标注的类内部包含一个或多个@Bean注解方法,可以被AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext 进行扫描。用于构建Bean定义以及初始化Spring容器。
3、@Configuation等价于<beans></beans>标签
4、@Configuration标注的配置类,将会通过cglib生成代理对象,因此要求配置类不能是final的类
5、注解内部属性说明:
  • 由于有@Component注解的加持,那么被声明的配置类本身也是一个组件,可以通过context进行获取。
  • value属性用于声明配置类的名称。
  • proxyBeanMethods属性用于声明是否是代理对象方法

在这里插入图片描述

2、基础使用

1、和@Configuration注解一起搭配使用的三个常见注解:
  • @Bean:等价于<bean />标签,用于注册Bean对象,内部有一些初始化、销毁的属性。
  • @Scope:用于声明该Bean的作用域,作用域有singleton、prototype、request、session、application、websocket
  • @Lazy:是否开启懒加载。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Subject {
    private String name;

    private Teacher teacher;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private String name;

    private Integer age;

    private String sex;
}
/**
 * @Date: 2022/11/19
 * bean配置类
 */
@Configuration(value = "beanConfig")
public class BeanConfig {
    @Bean("teacher")
    public Teacher getTecher() {
        System.out.println("Teacher对象进行创建");
        return new Teacher("张三", 34, "男");
    }

    @Bean("subject")
    public Subject getSubject() {
        System.out.println("Subject对象进行创建");
        return new Subject("化学", getTecher());
    }
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    String[] beanNames = context.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        System.out.println(beanName);
    }
    System.out.println("============================");
    Teacher teacher = context.getBean("teacher", Teacher.class);
    System.out.println(teacher);
    Subject subject = context.getBean("subject", Subject.class);
    System.out.println(subject);
    Teacher teacher1 = context.getBean("teacher", Teacher.class);
    System.out.println("teacher与teacher1是否相等:" + (teacher == teacher1));
}
/**
 * 运行结果:
 * Teacher对象进行创建
 * Subject对象进行创建
 * 容器中的Bean的名称:org.springframework.context.annotation.internalConfigurationAnnotationProcessor
 * 容器中的Bean的名称:org.springframework.context.annotation.internalAutowiredAnnotationProcessor
 * 容器中的Bean的名称:org.springframework.context.annotation.internalCommonAnnotationProcessor
 * 容器中的Bean的名称:org.springframework.context.event.internalEventListenerProcessor
 * 容器中的Bean的名称:org.springframework.context.event.internalEventListenerFactory
 * 容器中的Bean的名称:beanConfig
 * 容器中的Bean的名称:teacher
 * 容器中的Bean的名称:subject
 * ============================
 * Teacher(name=张三, age=34, sex=男)
 * Subject(name=化学, teacher=Teacher(name=张三, age=34, sex=男))
 * teacher与teacher1是否相等:true
 */
总结:
  • @Configuation标注的类本身也是一个组件,被注册到IoC容器中。
  • 默认情况下是以饿汉单例的形式进行创建Bean。

3、proxyBeanMethods属性说明

1、两个概念:Full全模式,Lite轻量级模式;默认使用全模式,即proxyBeanMethods = true
  • Full当proxyBeanMethods参数设置为true时即为全模式,该模式下注入容器中的同一个组件无论被取出多少次都是同一个Bean实例,即单例对象,在该模式下Spring每次启动都会检查容器中是否存在该组件;该模式下,配置类会被代理,使用CGLIB代理
  • Lite当proxyBeanMethods参数设置为false时即为轻量级模式,该模式下注入容器中的同一个组件无论被取出多少次都是不同的Bean实例,即多实例对象,在该模式下Spring每次启动都会跳过检查容器中是否存在该组件
2、全模式与轻量级模式如何选择:
  • 当在你的同一个Configuration配置类中,注入到容器中的Bean实例之间有依赖关系时,建议使用Full全模式。
  • 当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高Spring启动速度和性能。

4、使用全模式

/**
 * 修改bean配置类,并设置使用全模式
 */
@Configuration(value = "beanConfig", proxyBeanMethods = true)
@Test
public void test2() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
    BeanConfig beanConfig = context.getBean("beanConfig", BeanConfig.class);
    Subject subject = context.getBean(Subject.class);
    Subject subject1 = beanConfig.getSubject();
    System.out.println("subject与subject1是否相等:" + (subject == subject1));
}
/**
 * 运行结果:
 * Teacher对象进行创建
 * Subject对象进行创建
 * subject与subject1是否相等:true
 */

5、使用轻量级模式

/**
 * 修改bean配置类,并设置使用轻量级模式
 */
@Configuration(value = "beanConfig", proxyBeanMethods = false)
/**
 * 运行结果:由于subject中依赖teacher,可以看到teacher对象也每次进行重新实例化
 * Teacher对象进行创建
 * Subject对象进行创建
 * Teacher对象进行创建
 * Subject对象进行创建
 * Teacher对象进行创建
 * subject与subject1是否相等:false
 */

三、@ComponentScan注解开启注解支持

1、概述

1、@ComponentScan注解主要用于扫描包上的注解将组件加载到IoC容器中,替代<context:component-scan />标签,简化XML配置文件。
2、@ComponentScan还有一个父类@ComponentScans注解,用于存放多个@ComponentScan注解。
3、@ComponentScan注解只能使用在有@Configuration配置注解的地方,因为component-scan标签必须存在于xml配置文件中;而在脱离xml使用时,需要使用带有在@Configuration配置注解上。并不局限与一定是 @Configuration也可以是@SpringBootConfiguration等
4、主要用于扫描被@Controller、@Service、@Repository、@Component等注解标注的类

2、常用内部属性

1、value:指定扫描哪些包下的组件注解。
2、basePackages:同value属性作用相同。
3、basePackageClasses:扫描指定的类,且该类有组件注解才能被扫描到。
4、useDefaultFilters:过滤机制,常常搭配includeFilters与excludeFilters一起使用。是否启用自动检测使用@Component、@Repository、@Service、@Controller标注的类,默认为true。
5、includeFilters:指定哪些类型有资格进行组件扫描,需要将useDefaultFilters设置为false,才能让includeFilters参数生效
6、excludeFilters:指定哪些类型不符合组件扫描条件。
7、lazyInit:由于包扫描是一次性的,无法单独配置哪个组件是否懒加载,因此提供该属性用于声明是否开启懒加载扫描的Bean。

3、扫描包

package com.itan.componentscan.controller;

import org.springframework.stereotype.Controller;

/**
 * @Date: 2022/11/20
 */
@Controller
public class UserController {
}
package com.itan.componentscan.service;

import org.springframework.stereotype.Service;

/**
 * @Date: 2022/11/20
 */
@Service
public class UserService {
}
package com.itan.componentscan.dao;

import org.springframework.stereotype.Repository;

/**
 * @Date: 2022/11/20
 */
@Repository
public class UserDao {
}
/**
 * @Date: 2022/11/20
 * bean配置类,可以一次性扫描多个包,也可以是单个包
 */
@Configuration
@ComponentScan(value = {
        "com.itan.componentscan.controller",
        "com.itan.componentscan.service",
        "com.itan.componentscan.dao"
})
public class ComponentScanConfig {
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanConfig.class);
    String[] beanNames = context.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        System.out.println("容器中的Bean的名称:" + beanName);
    }
}
/**
 * 运行结果:
 * 容器中的Bean的名称:componentScanConfig
 * 容器中的Bean的名称:userController
 * 容器中的Bean的名称:userService
 * 容器中的Bean的名称:userDao
 */

4、扫描类

前提:被扫描的类上必须有组件注册的注解,否则无法扫描到
@Configuration
@ComponentScan(basePackageClasses = {
        UserController.class,
        UserService.class,
        UserDao.class
})
public class ComponentScanConfig {
}

/**
 * 运行结果:
 * 容器中的Bean的名称:componentScanConfig
 * 容器中的Bean的名称:userController
 * 容器中的Bean的名称:userService
 * 容器中的Bean的名称:userDao
 */

5、@Filter注解过滤

1、@ComponentScan注解中的includeFilters、excludeFilters属性用于指定组件的扫描与排除;不过更多的是使用excludeFilters排除掉不需要的组件。
2、@Filter注解的属性说明:
  • type:要使用过滤器类型,默认为FilterType.ANNOTATION
  • value:classes别名,数组类型。
  • classes:同value。
  • pattern:用于过滤器的模式,作为指定Class value的替代方法。
3、过滤器类型取值:
  • FilterType.ANNOTATION:按照注解类型排除或包含。
  • FilterType.ASSIGNABLE_TYPE:按照类型,如果是接口,那么所有的实现类都会被排除或包含。
  • FilterType.ASPECTJ:按照切面表达式排除或包含。
  • FilterType.REGEX:按照正则表达式排除或包含。
  • FilterType.CUSTOM:实现TypeFilter接口自定义排除或包含。

按注解类型进行排除:

/**
 * @Date: 2022/11/20
 * bean配置类,扫描com.itan.componentscan包下所有组件,并按照注解类型排除Controller类型的组件
 */
@Configuration
@ComponentScan(
        value = "com.itan.componentscan",
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ComponentScanConfig {
}

/**
 * 运行结果:
 * 容器中的Bean的名称:componentScanConfig
 * 容器中的Bean的名称:userDao
 * 容器中的Bean的名称:userService
 */

按照注解类型进行添加:

/**
 * @Date: 2022/11/20
 * bean配置类,扫描com.itan.componentscan包下所有组件,并按照注解类型只添加Controller类型的组件
 * 注:使用includeFilters前提是必须要将useDefaultFilters设置为false,否则就是扫描所有类型的组件
 */
@Configuration
@ComponentScan(
        value = "com.itan.componentscan", useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ComponentScanConfig {
}

/**
 * 运行结果:
 * 容器中的Bean的名称:componentScanConfig
 * 容器中的Bean的名称:userController
 */

6、自定义TypeFilter指定过滤规则

1、完成自定义表达式规则需要实现TypeFilter接口,通过该类的方法可以拿到被一次扫描到的类信息、注解、路径、甚至还可以获取到该类的其他信息。
/**
 * @Date: 2022/11/20
 * 自定义扫描规则
 */
public class CustomTypeFilter implements TypeFilter {
    /**
     * @param metadataReader        :读取到到当前正在扫描的类的信息
     * @param metadataReaderFactory :可以获取到其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader,
                         MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类的注解信息
        AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
        // 获取当前类的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类的路径
        Resource resource = metadataReader.getResource();
        // metadata和classMetadata变量又可以获取该类祖孙三代的很多信息
        System.out.println("经过自定义过滤器的类:" + classMetadata.getClassName());
        if (classMetadata.getClassName().contains("dao")) {
            System.out.println("被自定义过滤器放行的类:" + classMetadata.getClassName());
            // 放行所有全路径类名中包含dao的
            return true;
        }
        // 拒绝放行
        return false;
    }
}
/**
 * @Date: 2022/11/20
 * bean配置类,扫描com.itan.componentscan包下所有组件,并且使用自定义的过滤规,添加组件
 * 注:使用includeFilters前提是必须要将useDefaultFilters设置为false,否则就是扫描所有类型的组件
 */
@Configuration
@ComponentScan(
        value = "com.itan.componentscan", useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = CustomTypeFilter.class)}
)
public class ComponentScanConfig {
}

/**
 * 运行结果:
 * 经过自定义过滤器的类:com.itan.componentscan.controller.UserController
 * 经过自定义过滤器的类:com.itan.componentscan.dao.UserDao
 * 被自定义过滤器放行的类:com.itan.componentscan.dao.UserDao
 * 经过自定义过滤器的类:com.itan.componentscan.service.UserService
 * 容器中的Bean的名称:componentScanConfig
 * 容器中的Bean的名称:userDao
 */

四、组件注册相关注解

1、@Bean注解

1、@Bean注解是方法级注解,等价于<bean />标签,用于注册Bean对象,内部有一些初始化、销毁的属性等。

在这里插入图片描述

2、内部属性说明:
  • value:name属性的别名,在不需要其他属性时使用,也就是说value就是默认值。
  • name:bean的名称,可以为多个;如果未指定,则bean的名称是标注@Bean注解方法的方法名;如果指定了,方法的名称就会忽略;如果没有其他属性声明的话,bean的名称和别名可以通过value属性配置。
  • autowire:设置自动注入类型(按名称或类型)注入依赖项,返回一个Autowire类型的枚举;
    1. NO(AutowireCapableBeanFactory.AUTOWIRE_NO):默认值,不启用自动注入。
    2. BY_NAME(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME):通过属性名称自动注入,根据set方法的“set”后面的字符串去匹配bean name,没有匹配到就不注入。
    3. BY_TYPE(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE):通过setter方法的参数类型自动注入,如果有多个同类型的bean,则抛出异常。
  • initMethod:设置初始化时调用的方法,调用时机:对象创建完成(构造方法执行完),并且属性注入值后,调用初始化方法
  • destroyMethod:设置销毁时调用的方法,默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果不想执行该方法,则设置destroyMethod=""来防止触发销毁方法。
3、生命周期相关在《二、基于XML依赖注入详细配置》中详细说明了,请参考!
public class Car {
    public Car() {
        System.out.println("car constructor...");
    }

    private String brand;

    private String type;

    public void setBrand(String brand) {
        System.out.println("car setBrand...");
        this.brand = brand;
    }

    public void setType(String type) {
        System.out.println("car setType...");
        this.type = type;
    }

    public void init() {
        System.out.println("car init...");
    }

    public void detory() {
        System.out.println("car detory...");
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", type='" + type + '\'' +
                '}';
    }
}
/**
 * @Date: 2022/12/8
 * @Bean注解以及生命周期方法
 */
@Configuration
public class AnnotationBeanLifecycleConfig {
    @Bean(value = "car", initMethod = "init", destroyMethod = "detory")
    public Car createCar() {
        Car car = new Car();
        car.setBrand("路虎");
        car.setType("四驱");
        return car;
    }
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationBeanLifecycleConfig.class);
    // 获取容器中对象
    Car car = context.getBean("car", Car.class);
    System.out.println(car);
    // 销毁容器
    context.close();
}

/**
 * 运行结果如下:
 * car constructor...
 * car setBrand...
 * car setType...
 * car init...
 * Car{brand='路虎', type='四驱'}
 * car detory...
 */

2、@Component注解

1、@Component可以标注在类上,IoC容器启动时会自动在component-scan@ComponentScan注解配置包范围之中扫描被它标注的类,将它们注册到IoC容器中,这些类被称为“组件”,相当于XML中的<bean />标签。
2、在JavaWeb开发中,提供3个@Component注解衍生注解分别是:@Controller、@Service、@Repository,默认都是单例的
3、@Component泛指各种组件,就是说当我们的类不属于各种归类的时候,就可以使用@Component来标注这个类。
@Component
public class ComponentTest {
    public ComponentTest() {
        System.out.println("ComponentTest初始化");
    }
}
/**
 * @Date: 2022/11/20
 * bean配置类,扫描com.itan.component包下所有组件
 */
@Configuration
@ComponentScan(value = "com.itan.component")
public class ComponentConfig {
}
@Test
public void test2() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentConfig.class);
    String[] beanNames = context.getBeanDefinitionNames();
    for (String beanName : beanNames) {
        System.out.println("容器中的Bean的名称:" + beanName);
    }
}

/**
 * 运行结果:
 * ComponentTest初始化
 * 容器中的Bean的名称:componentConfig
 * 容器中的Bean的名称:componentTest
 */

3、@Scope注解

1、基于注解的组件的默认作用域都是singleton(单例的),如果要指定不同的作用域,可以使用此注解。
2、@Scope注解仅仅在一系列的Component组件注解标注的类和@Bean标注的工厂方法上有效果,它的作用域范围值与XML配置的作用域参数值一致,相当于<bean scope="" />请参考XML配置中的scope属性
/**
 * @Date: 2022/11/20
 * bean配置类
 */
@Configuration
public class ScopeConfig {
    @Bean("teacher")
    @Scope("prototype")
    public Teacher getTecher() {
        System.out.println("Teacher对象进行创建");
        return new Teacher("张三", 34, "男");
    }
}
@Test
public void test3() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeConfig.class);
    Teacher teacher1 = context.getBean("teacher", Teacher.class);
    Teacher teacher2 = context.getBean("teacher", Teacher.class);
    System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}

/**
 * 运行结果:可以看到获取的时候才会进行初始化
 * Teacher对象进行创建
 * Teacher对象进行创建
 * teacher1与teacher2是否相等:false
 */

4、@Lazy注解

1、@Lazy注解表示是否使用懒加载方式,相当于<bean lazy-init="">,默认值为true,表示延迟初始化,通过getBean获取的时候才会进行初始化。
2、@Lazy注解只对singleton(单例)的bean有效
3、与@Component(@Repository、@Service、@Controller)、@Configuration注解连用时,类中的所有@Bean方法的bean默认延迟初始化,如果某些@Bean方法上存在@Lazy注解并且value=false,那么表示将会立即初始化该bean,如果该方法是静态方法,那么外部lazy的类不受影响,如果是非静态的,那么外部lazy的类也会跟着初始化。
@Configuration
public class ScopeConfig {
    @Lazy
    @Bean("teacher")
    public Teacher getTecher() {
        System.out.println("Teacher对象进行创建");
        return new Teacher("张三", 34, "男");
    }
}
@Test
public void test3() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeConfig.class);
    Teacher teacher1 = context.getBean("teacher", Teacher.class);
    Teacher teacher2 = context.getBean("teacher", Teacher.class);
    System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}

/**
 * 运行结果:通过getBean获取的时候才会进行初始化
 * Teacher对象进行创建
 * teacher1与teacher2是否相等:true
 */

5、@Conditional注解

1、@Conditional注解表示满足指定的条件,才向IoC容器中注册组件;如果不满足,则不注入。
2、标注位置:
  • 类上:表示满足条件,这个类中的所有Bean才能注册。
  • 方法上:表示满足条件,被标注的Bean才能注册。
3、它还有以下一些扩展注解:
注解作用
@ConditionalOnBean当容器里有指定Bean的条件下就注入
@ConditionalOnMissingBean当容器里没有指定Bean的条件下就注入
@ConditionalOnSingleCandidate当指定Bean在容器中只有一个,或者虽然有多个但是指定首选 Bean,则条件也将匹配
@ConditionalOnClass当类路径下有指定类的条件下才匹配
@ConditionalOnMissingClass当类路径下没有指定类的条件下才匹配
@ConditionalOnProperty指定的属性是否有指定的值,如果有就匹配
@ConditionalOnResource仅当指定的资源在类路径上时才匹配
@ConditionalOnExpression基于SpEL表达式作为判断条件
@ConditionalOnJava基于Java版本作为判断条件
@ConditionalOnJndi在JNDI存在的条件下差在指定的位置
@ConditionalOnNotWebApplication当前项目不是Web项目的条件下
@ConditionalOnWebApplication当前项目是 Web项目的条件下
4、自定义条件实现需要实现org.springframework.context.annotation.Condition接口,该接口提供了一个boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法,用于判断条件是否匹配,参数说明如下:
  • ConditionContext:判断条件能使用的上下文(条件上下文环境)。
  • AnnotatedTypeMetadata:标注@Conditional注解的注释信息。
5、ConditionContext中的一些方法说明:
方法名说明
ConfigurableListableBeanFactory getBeanFactory获取IoC使用的Bean工厂(beanFactory)
ClassLoader getClassLoader获取类加载器
Environment getEnvironment获取当前环境信息
BeanDefinitionRegistry getRegistry获取Bean定义的注册类信息
ResourceLoader getResourceLoader获取当前使用
/**
 * @Date: 2022/11/30
 * 条件注解bean配置类
 */
@Configuration
public class ConditionalConfig {
    @Bean("linux")
    @Conditional(MacOSCondition.class)
    public Teacher linux() {
        return new Teacher("Linux", 34, "男");
    }

    @Bean("windows")
    @Conditional(WindowsCondition.class)
    public Teacher windows() {
        return new Teacher("Windows", 26, "女");
    }
}
/**
 * @Date: 2022/11/30
 * MacOS条件判断
 */
public class MacOSCondition implements Condition {
    /**
     * 判断条件是否匹配
     * @param context       条件上下文
     * @param metadata      注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 获取IoC使用的Bean工厂(beanFactory)
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = context.getClassLoader();
        // 获取当前环境信息
        Environment environment = context.getEnvironment();
        // 获取Bean定义的注册类信息
        BeanDefinitionRegistry registry = context.getRegistry();
        // 获取操作系统信息
        String osName = environment.getProperty("os.name");
        System.out.println("操作系统为:" + osName);
        // 如果操作系统为Mac OS X,就将Bean注册到IoC容器中
        if (osName.equals("Mac OS X")) {
            return true;
        }
        return false;
    }
}
/**
 * @Date: 2022/11/30
 * Windows条件判断
 */
public class WindowsCondition implements Condition {
    /**
     * 判断条件是否匹配
     * @param context       条件上下文
     * @param metadata      标注@Conditional注释的类或方法信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        /**
         * 获取指定类型注解的属性,若不存在返回null
         * annotationName:为注释类型的完全限定类名
         */
        Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.Bean");
        // 获取注解标注的方法名称
        String methodName = ((StandardMethodMetadata) metadata).getMethodName();
        System.out.println("方法名为:" + methodName);
        System.out.println("@Bean注解信息为:" +  attributes);
        // 如果方法名为windows,就将Bean注册到IoC容器中
        if (methodName.equals("windows")) {
            return true;
        }
        return false;
    }
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionalConfig.class);
    String[] definitionNames = context.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println("bean名称:" + name);
    }
}
/**
 * 运行结果如下:
 * 操作系统为:Mac OS X
 * 方法名为:windows
 * @Bean注解信息为:{autowire=NO, autowireCandidate=true, destroyMethod=(inferred), initMethod=, name=[windows], value=[windows]}
 * bean名称:conditionalConfig
 * bean名称:linux
 * bean名称:windows
 */

6、@Import注解

1、@Import注解通过快速导入的方式实现将Bean实例注册到IoC容器中,与在XML的配置中使用<import>标签一样。
2、@Import在使用时可以声明在JAVA类上,或者作为元注解使用(即声明在其他注解上)。
3、使用@Import注解导入的Bean默认的名称为类的完全限定名
/**
 * @Date: 2022/12/4
 * ImportBean测试Bean
 */
public class ImportBean {
}
/**
 * @Date: 2022/11/30
 * Import注解bean配置类
 */
@Configuration
@Import(ImportBean.class)
public class ImportConfig {
}
@Test
public void test2() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
    String[] definitionNames = context.getBeanDefinitionNames();
    for (String name : definitionNames) {
        System.out.println("bean名称:" + name);
    }
}
/**
 * 运行结果如下:
 * bean名称:importConfig
 * bean名称:com.itan.imports.ImportBean
 */

7、@Import注解扩展用法

1、通过@Import注解导入一个类,且该实现了ImportSelector接口,重写了selectImports方法,该方法返回String类型的数组,数组中的类都会被注册到IoC容器中。
2、通过@Import注解导入一个类,且该实现了ImportBeanDefinitionRegistrar接口,在重写方法registerBeanDefinitions中,能够获取到BeanDefinitionRegistry注册器,能手动向beanDefinitionMap中注册bean
/**
 * @Date: 2022/11/30
 * 自定义返回需要导入的组件
 */
public class MyImportSelector implements ImportSelector {
    /**
     * 获取要导入到容器中的组件的全类名
     * @param importingClassMetadata    当前@Import注解的类的所有注解信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.itan.imports.ImportBean", Bed.class.getName()};
    }
}

@Configuration
// @Import({ImportBean.class})
@Import({ImportBean.class, MyImportSelector.class})
public class ImportConfig {
}


/**
 * test2方法运行结果如下:
 * bean名称:importConfig
 * bean名称:com.itan.imports.ImportBean
 * bean名称:com.itan.beans.Bed
 */
/**
 * @Date: 2022/11/30
 * 自定义导入bean注册器
 */
public class MyImportDefintionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata    当前@Import注解的类的所有注解信息
     * @param registry                  BeanDefinition注册器,可以对未定义的bean进行注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取是否存在bean
        boolean exists = registry.containsBeanDefinition("people");
        System.out.println("注册之前是否存在people:" + exists);
        // 不存在手动注册
        if (!exists) {
            RootBeanDefinition beanDefinition = new RootBeanDefinition(People.class);
            // 可以指定bean的名称
            registry.registerBeanDefinition("people", beanDefinition);
        }
        exists = registry.containsBeanDefinition("people");
        System.out.println("注册之后是否存在people:" + exists);
    }
}

@Configuration
// @Import({ImportBean.class})
@Import({ImportBean.class, MyImportSelector.class, MyImportDefintionRegistrar.class})
public class ImportConfig {
}


/**
 * test2方法运行结果如下:
 * 注册之前是否存在people:false
 * 注册之后是否存在people:true
 * bean名称:importConfig
 * bean名称:com.itan.imports.ImportBean
 * bean名称:com.itan.beans.Bed
 * bean名称:people
 */

8、@DependsOn注解

1、@DependsOn注解可以指定当前Bean所依赖的其他Bean,也就是说被依赖的Bean会比当前Bean先注册到IoC容器中;等同于XML配置中的depends-on属性。
2、@DependsOn既可以指定初始化的依赖顺序,也可以指定销毁顺序(倒序执行销毁,也就是当前Bean销毁之后,其依赖的Bean倒序执行销毁);只针对作用范围为singleton的Bean才有效,对于作用范围为prototype的Bean也会按照顺序进行初始化,但不会执行销毁回调
3、作用范围:
  • 可以定义在类和方法上。
  • 可以定义在带有@Component注解及其衍生注解的类上。
  • 可以定义在带有@Bean注解的方法上。
@Scope("singleton")
@Component
@DependsOn({"dependC","dependB"})
public class DependA {
    public DependA() {
        System.out.println("DependA构造方法");
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("DependA执行初始化回调方法");
    }

    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("DependA执行销毁回调方法");
    }
}


@Scope("singleton")
@Component
public class DependB {
    public DependB() {
        System.out.println("DependB构造方法");
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("DependB执行初始化回调方法");
    }

    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("DependB执行销毁回调方法");
    }
}


@Scope("singleton")
@Component
public class DependC {
    public DependC() {
        System.out.println("DependC构造方法");
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("DependC执行初始化回调方法");
    }

    @PreDestroy
    public void destroy() throws Exception {
        System.out.println("DependC执行销毁回调方法");
    }
}
/**
 * @Date: 2022/11/30
 * DependOn配置类
 */
@Configuration
@ComponentScan("com.itan.componentscan")
public class DependOnConfig {
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    // 销毁容器
    context.close();
}

/**
 * 运行结果如下:作用范围为singleton,可以看到初始化时优先初始化DependC,容器销毁时优先执行DependA的销毁回调
 * DependC构造方法
 * DependC执行初始化回调方法
 * DependB构造方法
 * DependB执行初始化回调方法
 * DependA构造方法
 * DependA执行初始化回调方法
 * DependA执行销毁回调方法
 * DependB执行销毁回调方法
 * DependC执行销毁回调方法
 */

/**
 * 运行结果如下:若将Bean作用范围改为prototype,可以看到初始化时依然保证顺序执行初始化
 * DependC构造方法
 * DependC执行初始化回调方法
 * DependB构造方法
 * DependB执行初始化回调方法
 * DependA构造方法
 * DependA执行初始化回调方法
 */

五、属性赋值相关注解

1、@Value注解

1、@Value注解可以用于字面量值的直接注入,或者获取外部配置文件(properties/yml)中的属性,也可以使用SPEL的语法,等价于XML配置中的value属性和标签。
2、作用范围:
  • 可以用在属性、方法(普通方法、@Bean标注方法都行)。
  • 可以用在参数(普通方法、@Bean方法、构造方法)中的参数上。
  • 用在方法上时,表示对方法参数都注入相同的值;用在方法中的具体参数上,表示对具体参数注入指定值

2、注入字面量值

1、@Value可以直接注入基本类型、包装类型、字符串类型等字面量值,甚至可以通过Spring提供的内置转换器自动处理简单的类型转换(转换为数组、集合等复杂结构)。
2、也可以通过SPEL操作字符串方法(如:大小写转换,截取、拼接等)。
3、基本类型:byte、boolean、char、double、float、int、long、short,对于boolean类型的可以使用true或false,也可以使用0或1。
4、包装类型:Byte、Boolean、Character、Double、Float、Integer、Long、Short
5、字符串类型:String、CharSequence
/**
 * @Date: 2022/12/10
 * @value注解实体类
 */
@Component("value")
public class ValueStr {
    /*基本类型*/
    @Value("1")
    private byte b;

    /*包装类型*/
    @Value("1")
    private Integer i;

    /*字符串类型*/
    @Value("我是字符串")
    private String str;

    /*整型基本类型的数组,使用","会自动拆分*/
    @Value("1,2,3,4")
    private byte[] bytes;
    @Value("1,2,3,4")
    private int[] ints;
    @Value("1,2,3,4")
    private long[] longs;
    @Value("1,2,3,4")
    private short[] shorts;

    /*SPEL表达式,#{'要分割的数据'.进行的操作}*/
    @Value("#{'1,0,1,1'.split(',')}")
    private char[] chars;
    @Value("#{'1,0,1,1'.split(',')}")
    private float[] floats;
    @Value("#{'1,0,1,1'.split(',')}")
    private double[] doubles;
    @Value("#{'1,0,1,1'.split(',')}")
    private boolean[] booleans;

    /*SPEL表达式,调用String类方法*/
    @Value("#{'abc'.toUpperCase()}")
    private String upStr;

    /*SPEL表达式进行计算*/
    @Value("#{25-2}")
    private int age;

    /*转集合类型*/
    @Value("#{'张三,李四,王五,张三'.split(',')}")
    private List<String> strList;
    // set类型会自动去重
    @Value("#{'张三,李四,王五,张三'.split(',')}")
    private Set<String> strSet;
    // Map类型 #{mapJsonValue}类似于JSON格式,会自动去重
    @Value("#{{'k1':100, 'k2':200}}")
    private Map<String, Integer> map;

    /*用在方法上,表示对方法上的所有参数注入相同值*/
    private int count;
    private int sum;
    @Value("200")
    private void setField(int count, int sum) {
        this.count = count;
        this.sum = sum;
    }

    /*用在方法的参数上,表示对指定的参数注入指定的值*/
    private String sex;
    public ValueStr(@Value("男") String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "ValueStr{" +
                "b=" + b +
                ", i=" + i +
                ", str='" + str + '\'' +
                ", bytes=" + Arrays.toString(bytes) +
                ", ints=" + Arrays.toString(ints) +
                ", longs=" + Arrays.toString(longs) +
                ", shorts=" + Arrays.toString(shorts) +
                ", chars=" + Arrays.toString(chars) +
                ", floats=" + Arrays.toString(floats) +
                ", doubles=" + Arrays.toString(doubles) +
                ", booleans=" + Arrays.toString(booleans) +
                ", upStr='" + upStr + '\'' +
                ", age=" + age +
                ", strList=" + strList +
                ", strSet=" + strSet +
                ", map=" + map +
                ", count=" + count +
                ", sum=" + sum +
                ", sex='" + sex + '\'' +
                '}';
    }
}
/**
 * @Date: 2022/12/10
 * @value注解使用的配置
 */
@Configuration
@ComponentScan("com.itan.beans")
public class AnnotationValueConfig {

}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);
    ValueStr value = context.getBean("value", ValueStr.class);
    System.out.println(value.toString());
}

/**
 * 运行结果:
 * ValueStr{b=1, i=1, str='我是字符串', 
 *              bytes=[49, 44, 50, 44, 51, 44, 52], 
 *              ints=[1, 2, 3, 4], 
 *              longs=[1, 2, 3, 4], 
 *              shorts=[1, 2, 3, 4], 
 *              chars=[1, 0, 1, 1], 
 *              floats=[1.0, 0.0, 1.0, 1.0], 
 *              doubles=[1.0, 0.0, 1.0, 1.0], 
 *              booleans=[true, false, true, true], 
 *              upStr='ABC', age=23, 
 *              strList=[张三, 李四, 王五, 张三], 
 *              strSet=[张三, 李四, 王五], 
 *              map={k1=100, k2=200}, 
 *              count=200, sum=200, sex='男'
 *          }
 */

3、注入外部配置文件中的属性

1、@Value可以获取外部properties或yml文件中的属性并注入:
  • 注入一般属性的格式:${key},如果配置文件中没有该key,则${key}本身作为值尝试注入;如果@Value引用到key不存在,也可以设置默认值,格式为${key:defaultValue},若只写:则表示注入空字符串
  • 注入单value集合的格式:#{'${key}'.split(',')},可以使用String类提供的一些方法。
  • 注入Map集合的格式就类似JSON格式。
2、注入外部配置文件中的属性的前提是必须将配置文件加载到当前应用程序运行的环境中,可以通过XML和注解两种方式将文件加载到运行的环境中。
3、基于XML方式将文件加载到运行环境中:
  • 导入context名称空间:xmlns:context="http://www.springframework.org/schema/context"
  • 使用标签<context:property-placeholder location="classpath:配置文件名称" />
4、基于注解方式将文件加载到运行环境中:
  • 使用@PropertySource注解将文件加载到运行环境中。
  • @PropertySources注解可以聚合多个@PropertySource注解。
# 在resource目录下新建一个spring.properties配置文件

b=1
i=1
str=我是字符串
# 指定分隔符号,然后使用split拆分即可
bytes=1,2,3,4
ints=1,2,3,4
longs=1,2,3,4
shorts=1,2,3,4
chars=1,0,1,1
floats=1,0,1,1
doubles=1,0,1,1
booleans=1,0,1,1
upStr=abcd
strList=张三,李四,王五,张三
strSet=张三,李四,王五,张三
map={'k1':100, 'k2':200}
/**
 * @Date: 2022/12/10
 * @value注解实体类
 */
@Component("value1")
public class ValueStr1 {
    /*基本类型*/
    @Value("${b}")
    private byte b;

    /*包装类型*/
    @Value("${i}")
    private Integer i;

    /*字符串类型*/
    @Value("${str}")
    private String str;

    /*整型基本类型的数组,使用","会自动拆分*/
    @Value("#{'${bytes}'.split(',')}")
    private byte[] bytes;
    @Value("#{'${ints}'.split(',')}")
    private int[] ints;
    @Value("#{'${longs}'.split(',')}")
    private long[] longs;
    @Value("#{'${shorts}'.split(',')}")
    private short[] shorts;

    /*SPEL表达式,#{'要分割的数据'.进行的操作}*/
    @Value("#{'${chars}'.split(',')}")
    private char[] chars;
    @Value("#{'${floats}'.split(',')}")
    private float[] floats;
    @Value("#{'${doubles}'.split(',')}")
    private double[] doubles;
    @Value("#{'${booleans}'.split(',')}")
    private boolean[] booleans;

    /*SPEL表达式,调用String类方法*/
    @Value("#{'${upStr}'.toUpperCase()}")
    private String upStr;

    /*SPEL表达式进行计算*/
    @Value("#{${b} + ${i}}")
    private int age;

    /*转集合类型*/
    @Value("#{'${strList}'.split(',')}")
    private List<String> strList;
    // set类型会自动去重
    @Value("#{'${strSet}'.split(',')}")
    private Set<String> strSet;
    // Map类型 #{mapJsonValue}类似于JSON格式,会自动去重
    @Value("#{${map}}")
    private Map<String, Integer> map;
    
    // 默认值
    @Value("${defaultValue:我是默认值}")
    private String defaultValue;

    @Override
    public String toString() {
        return "ValueStr1{" +
                "b=" + b +
                ", i=" + i +
                ", str='" + str + '\'' +
                ", bytes=" + Arrays.toString(bytes) +
                ", ints=" + Arrays.toString(ints) +
                ", longs=" + Arrays.toString(longs) +
                ", shorts=" + Arrays.toString(shorts) +
                ", chars=" + Arrays.toString(chars) +
                ", floats=" + Arrays.toString(floats) +
                ", doubles=" + Arrays.toString(doubles) +
                ", booleans=" + Arrays.toString(booleans) +
                ", upStr='" + upStr + '\'' +
                ", age=" + age +
                ", strList=" + strList +
                ", strSet=" + strSet +
                ", map=" + map +
                ", defaultValue='" + defaultValue + '\'' +
                '}';
    }
}
/**
 * @Date: 2022/12/10
 * @value注解使用的配置
 */
@Configuration
@ComponentScan("com.itan.beans")
// 加载配置文件,并且设置字符编码
@PropertySource(value="classpath:spring.properties", encoding="UTF-8")
public class AnnotationValueConfig {

}
@Test
public void test2() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);
    ValueStr1 value = context.getBean("value1", ValueStr1.class);
    System.out.println(value.toString());
}

/**
 * 运行结果:
 * ValueStr{
 *              b=1, i=1, str='我是字符串',
 *              bytes=[1, 2, 3, 4],
 *              ints=[1, 2, 3, 4],
 *              longs=[1, 2, 3, 4],
 *              shorts=[1, 2, 3, 4],
 *              chars=[1, 0, 1, 1],
 *              floats=[1.0, 0.0, 1.0, 1.0],
 *              doubles=[1.0, 0.0, 1.0, 1.0],
 *              booleans=[true, false, true, true],
 *              upStr='ABCD', age=2,
 *              strList=[张三, 李四, 王五, 张三],
 *              strSet=[张三, 李四, 王五],
 *              map={k1=100, k2=200},
 *              defaultValue='我是默认值'
 *         }
 */

4、注入bean的属性

1、@Value可以获取其他Bean的属性值然后注入到自己的属性中。
2、注意:要求尝试获取属性的bean的属性有getter方法或者被public修饰
/**
 * @Date: 2022/12/10
 * @value注解注入其他bean属性值,
 * 要求获取属性bean的属性被public修饰,或者提供getter方法
 */
@Component("value2")
public class ValueStr2 {
    /*基本类型*/
    @Value("#{value1.b}")
    private byte b;

    /*包装类型*/
    @Value("#{value1.i}")
    private Integer i;

    /*字符串类型*/
    @Value("#{value1.str}")
    private String str;

    /*数组*/
    @Value("#{value1.bytes}")
    private byte[] bytes;

    /*Map集合*/
    @Value("#{value1.map}")
    private Map<String, Integer> map;

    /*数组/集合某个索引/key的数据*/
    @Value("#{value1.strList[0]}")
    private String name;
    @Value("#{value1.map[k2]}")
    private Integer score;
    @Value("#{value1.booleans[1]}")
    private boolean flag;

    @Override
    public String toString() {
        return "ValueStr2{" +
                "b=" + b +
                ", i=" + i +
                ", str='" + str + '\'' +
                ", bytes=" + Arrays.toString(bytes) +
                ", map=" + map +
                ", name='" + name + '\'' +
                ", score=" + score +
                ", flag=" + flag +
                '}';
    }
}
@Test
public void test3() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);
    ValueStr2 value = context.getBean("value2", ValueStr2.class);
    System.out.println(value.toString());
}

/**
 * 运行结果:
 * ValueStr2{
 *              b=1, i=1, str='我是字符串',
 *              bytes=[1, 2, 3, 4],
 *              map={k1=100, k2=200},
 *              name='张三', score=200, flag=false
 *          }
 */

六、自动注入相关注解

1、@Autowired依赖自动注入

1、@Autowired是Spring提供的一个依赖自动注入的注解,在基于XML的依赖注入详细配置文章中可以了解到自动注入,它是使用AutowiredAnnotationBeanPostProcessor处理器来解析的。
2、相比于XML配置的优势:XML的自动注入都是通过构造器或者setter方法实现,还存在某些限制;而@Autowired注解使用更加灵活方便。
3、@Autowired要求其所在的类被注册到IoC容器中,bean注册的注解有@Controller、@Service、@Repository、@Component、@Bean
4、作用范围:可以作用在构造器、方法、属性、参数上。
5、@Autowired的自动注入规则
  1. 默认优先按照参数或属性对应的类型在容器中查找对应类型的Bean(通过ioc.getBean(xxx.class)方式),类型可以向下兼容。
  2. 如果找到一个匹配的Bean,就将该Bean实例注入到对应的参数或属性中,没有则抛出异常。
  3. 如果找到多个相同类型的Bean,再将属性或参数的名称作为Bean的name在容器中查找(通过ioc.getBean("xxx")方式)。
  4. 如果找到使用参数名或属性名作为name的Bean,就将该Bean实例注入进去到对应类型的参数或属性中;如果没有找到,就会抛出异常。

2、@Autowired标注在构造器上

1、标注在构造器上时,表示对该构造器的所有参数都从IoC容器中查找匹配的Bean实例注入进去
2、查找的类型就是参数对应的类型,查找的名称就是参数名
3、构造器上使用@Autowired注解自动注入的规则:
  1. 若一个Bean存在多个构造器,那么只能有一个构造器上使用@Autowired注解并设置required为true或只使用@Autowired注解(默认required=true),那么容器将会只使用该构造器进行依赖注入
  2. 若将@Autowired注解并设置required为false,那么可以在多个构造器上使用该注解,表示有多个构造器可以作为“候选构造器”。Spring会选择能够满足最多、最匹配参数的依赖项的构造器作为依赖注入的构造器
  3. 如果Spring最终选择其中一个候选构造器作为依赖注入的构造器,必须满足该构造器上的参数全部都能成功注入。如果所有的候选构造器中没有一个能满足构造器上的参数全部都能成功注入,那么会直接调用默认的无参构造器,所有的依赖项都不会注入
  4. 如果一个Bean存在多个构造器,但没有一个构造器标注@Autowired注解,则将使用无参构造器,这样的话就不会进行构造器依赖注入
  5. 如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解
  6. @Autowired注解标注的构造器不必是public修饰的
/**
 * @Date: 2022/12/11
 * Autowired标注在构造上
 */
@Component
public class AutowiredConstructor {
    private UserService userService;

    private UserDao userDao;

    @Autowired
    public AutowiredConstructor(UserService userService, UserDao userDao) {
        this.userService = userService;
        this.userDao = userDao;
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                '}';
    }
}
/**
 * @Date: 2022/12/11
 * Autowired配置类
 */
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {

}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    AutowiredConstructor value = context.getBean(AutowiredConstructor.class);
    System.out.println(value.toString());
}

/**
 * 运行结果如下:
 * Creating shared instance of singleton bean 'autowiredConfig'
 * Creating shared instance of singleton bean 'autowiredConstructor'
 * Creating shared instance of singleton bean 'userService'
 * Creating shared instance of singleton bean 'userDao'
 * Autowiring by type from bean name 'autowiredConstructor' via constructor to bean named 'userService'
 * Autowiring by type from bean name 'autowiredConstructor' via constructor to bean named 'userDao'
 * Creating shared instance of singleton bean 'userController'
 * AutowiredConstructor{
 *      userService=com.itan.componentscan.service.UserService@3012646b,
 *      userDao=com.itan.componentscan.dao.UserDao@4a883b15
 * }
 */
1、扩展1:
  • 如果将@Autowired注解注释掉,所依赖的对象照样被注入进来了,参考上面构造器注入规则的第5条。

在这里插入图片描述

2、扩展2:
  • @Autowired注解中包含一个required属性,默认值为true;为false就表示非必须的依赖,如果找不到依赖项也不会报错
  • 如果将required设置为false,并且将UserService上的@Service注解注释掉,启动程序测试,发现还是会报错。
  •  No qualifying bean of type 'com.itan.componentscan.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    
  • 解决上面问题,添加无参构造器,或者添加其他参数的构造器即可。参考上面构造器注入规则的第2、3、4条。

在这里插入图片描述

3、构造器依赖项不能注入问题的解决方式

1、根据构造器自动注入的规则发现,即使设置了required=false,只要有一个依赖项不能注入,那么该构造器就不会使用。因此在构建对象时必须将可能的构造器参数组合都列出来,全部作为候选构造器,才能实现有就注入,没有就不注入的功能,这样代码就非常臃肿,参数一多还非常麻烦。
2、解决方式:
  1. 可以通过JDK8提供的Optional类来实现,防止空指针。
  2. 可以使用Spring5.0提供的@Nullable注解或者JSR-305中的@Nullable注解标注到参数字段上。
/**
 * @Date: 2022/12/11
 * 使用Optional实现有就注入,没有就不注入功能
 */
@Component
public class AutowiredConstructorOptional {
    private UserService userService;

    private UserDao userDao;

    // 如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解
    // @Autowired
    public AutowiredConstructorOptional(Optional<UserService> optionalUserService, Optional<UserDao> optionalUserDao) {
        optionalUserService.ifPresent(userService -> this.userService = userService);
        optionalUserDao.ifPresent(userDao -> this.userDao = userDao);
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                '}';
    }
}

@Test
public void test2() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    AutowiredConstructorOptional value = context.getBean(AutowiredConstructorOptional.class);
    System.out.println(value.toString());
}

/**
 * 运行结果如下:
 * AutowiredConstructor{userService=null, userDao=UserDao{label='@Repository注入的'}}
 */

/**
 * @Date: 2022/12/11
 * 使用@Nullable注解实现有就注入,没有就不注入功能
 */
@Component
public class AutowiredConstructorNullable {
    private UserService userService;

    private UserDao userDao;

    // 如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解
    // @Autowired
    public AutowiredConstructorNullable(@Nullable UserService userService, @Nullable UserDao userDao) {
        this.userService = userService;
        this.userDao = userDao;
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                '}';
    }
}

@Test
public void test3() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    AutowiredConstructorNullable value = context.getBean(AutowiredConstructorNullable.class);
    System.out.println(value.toString());
}

/**
 * 运行结果如下:
 * AutowiredConstructor{userService=null, userDao=UserDao{label='@Repository注入的'}}
 */

4、@Autowired标注在方法上

1、标注在方法上时,表示对该方法的所有参数都从IoC容器中查找匹配的Bean实例注入进去;与标注在构造器上差不多。
2、查找的类型就是参数对应的类型,查找的名称就是参数名

5、@Autowired标注在属性上

1、标注在属性上时,表示该属性从IoC容器中查找匹配的Bean实例注入进去;使用最多的一种方式。
2、查找的类型就是参数对应的类型,查找的名称就是参数名
3、注意:@Autowired是不能直接为静态属性注入值的,但是可以通过方法注入(构造方法或setter方法)或者使用@PostConstruct注解的方式在加载类的构造函数之后执行
/**
 * @Date: 2022/12/13
 * 为静态属性注入值
 */
@Component
public class AutowiredStaticField {
    private static UserService userService;

    private static UserDao userDao;

    private static UserController userController;

    @Autowired
    private AnnotationConfigApplicationContext context;

    // 标注在方法上
    @Autowired
    public void setUserDao(UserDao userDao) {
        AutowiredStaticField.userDao = userDao;
    }

    // 标注在参数上
    public AutowiredStaticField(@Autowired UserService userService) {
        this.userService = userService;
    }

    // Bean创建完成并且属性注入值完成,执行初始化方法。
    @PostConstruct
    public void init() {
        userController = context.getBean(UserController.class);
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                ", userController=" + userController +
                '}';
    }
}
@Test
public void test4() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    AutowiredStaticField value = context.getBean(AutowiredStaticField.class);
    System.out.println(value.toString());
}

/**
 * 运行结果如下:
 * AutowiredConstructor{userService=com.itan.componentscan.service.UserService@7d0b7e3c, 
 * userDao=UserDao{label='@Repository注入的'}, 
 * userController=com.itan.componentscan.controller.UserController@15bb5034}
 */

6、@Autowired注入多个Bean

1、可以将注解标注在Bean对应类型的数组的字段或者方法上,就可以从IoC容器中获取到该Bean对应类型的所有Bean,且能向下兼容,同样也适用于集合(LIst、Map、Set…)。
2、对于有顺序的集合,还可以指定注入Bean的顺序,可以通过@Order(XXX)注解设置依赖注入顺序,值越小,排序越靠前,@Order仅仅只能表示注入到集合的顺序
3、对于Map<String, XXX>集合,key为XXX类型的所有Bean的名字,value为实现类对象
// 定义一个接口
public interface UploadHandler {
}

// 实现类,并设置顺序
@Order(2)
@Component
public class ImgUpload implements UploadHandler{
}

// 实现类,并设置顺序
@Order(1)
@Component
public class PDFUpload implements UploadHandler {
}
@Component
public class UploadFactory {
    private List<UploadHandler> uploadHandlerList;

    // 属性注入
    @Autowired
    private Set<UploadHandler> uploadHandlerSet;

    @Autowired
    private Map<String, UploadHandler> uploadHandlerMap;

    private UploadHandler[] uploadHandlers;

    // 方法注入
    @Autowired
    public void setUploadHandlers(UploadHandler[] uploadHandlers) {
        this.uploadHandlers = uploadHandlers;
    }

    // 构造注入
    @Autowired
    public UploadFactory(List<UploadHandler> uploadHandlerList) {
        this.uploadHandlerList = uploadHandlerList;
    }

    @Override
    public String toString() {
        return "UploadFactory{" +
                "uploadHandlerList=" + uploadHandlerList +
                "\n, uploadHandlerSet=" + uploadHandlerSet +
                "\n, uploadHandlerMap=" + uploadHandlerMap +
                "\n, uploadHandlers=" + Arrays.toString(uploadHandlers) +
                '}';
    }
}
@Test
public void test5() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    UploadFactory factory = context.getBean(UploadFactory.class);
    System.out.println(factory.toString());
}

/**
 * 运行结果如下:
 *     1、可以对比发现有序集合是按照@Order设置的顺序注入的。
 *	   2、Map类型的key为实现类的名称,value是实现类对象
 * UploadFactory{uploadHandlerList=[com.itan.componentscan.service.PDFUpload@75d3a5e0, com.itan.componentscan.service.ImgUpload@2034b64c]
 * , uploadHandlerSet=[com.itan.componentscan.service.ImgUpload@2034b64c, com.itan.componentscan.service.PDFUpload@75d3a5e0]
 * , uploadHandlerMap={imgUpload=com.itan.componentscan.service.ImgUpload@2034b64c, PDFUpload=com.itan.componentscan.service.PDFUpload@75d3a5e0}
 * , uploadHandlers=[com.itan.componentscan.service.PDFUpload@75d3a5e0, com.itan.componentscan.service.ImgUpload@2034b64c]}
 */

4、@Qualifier注解

1、@Autowired注解默认按照类型匹配注入Bean,如果存在多个类型兼容或者相同的Bean,搭配@Qualifier表明注入的是哪个实现类的Bean。
2、@Qualifier注解可以设置一个value值,该value值表示一个Bean的name,强制指定需要注入的Bean
@Repository
public class UserDao {
    private String label = "@Repository注入的";

    public void setLabel(String label) {
        this.label = label;
    }

    @Override
    public String toString() {
        return "UserDao{" +
                "label='" + label + '\'' +
                '}';
    }
}
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {
    // @Bean方式注入一个userDao,容器中存在两个相同类型的UserDao
    @Bean("userDao1")
    public UserDao userDao() {
        UserDao userDao = new UserDao();
        userDao.setLabel("使用@Bean注解注册的");
        return userDao;
    }
}
@Component
public class AutowiredConstructor {
    private UserService userService;

    private UserDao userDao;

    // 使用@Qualifier强制注入名称为userDao1的Bean
    @Autowired(required = false)
    public AutowiredConstructor(@Qualifier("userDao1") UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                '}';
    }
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);
    AutowiredConstructor value = context.getBean(AutowiredConstructor.class);
    System.out.println(value.toString());
}

/**
 * 运行结果如下:
 * AutowiredConstructor{userService=null, userDao=UserDao{label='使用@Bean注解注册的'}}
 */

5、@Primary注解

1、@Autowired在按照类型无法选择唯一的依赖注入时(比如存在多个类型兼容或者相同的Bean),可以使用@Primary注解。
2、表示当有多个Bean是依赖项的候选对象时,应该优先考虑被@Primary标注的Bean
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {
    // @Bean方式注入一个userDao,容器中存在两个相同类型的UserDao,由于被@Primary标注,所以注入userDao1
    @Bean("userDao1")
    @Primary
    public UserDao userDao() {
        UserDao userDao = new UserDao();
        userDao.setLabel("使用@Bean注解注册的");
        return userDao;
    }
}
@Component
public class AutowiredConstructor {
    private UserService userService;

    private UserDao userDao;

    @Autowired(required = false)
    public AutowiredConstructor(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public String toString() {
        return "AutowiredConstructor{" +
                "userService=" + userService +
                ", userDao=" + userDao +
                '}';
    }
}

/**
 * 运行结果如下:
 * AutowiredConstructor{userService=null, userDao=UserDao{label='使用@Bean注解注册的'}}
 */

6、@Resource注解

1、@Resource注解类似@Autowired注解,也可以实现依赖自动注入功能,它是JSR250提供的,位于javax.annotation.Resource。
2、@Resource注入规则
  • 默认通过名称注入,如果标注在字段上,且没有设置name属性,那么字段名就作为查找的beanName;如果标注在setter方法上,就将方法名set后面的部分作为查找的beanName。
  • 如果没有设置name属性或者在IoC容器中没有找到同名的Bean,那么就通过类型注入,如果没有找到该类型的Bean,或者找到多个(类型向下兼容),那么抛出异常。
  • 也可以指定name和type属性,表示只通过Bean name或者Bean type匹配依赖项,如果两个属性都指定值,那么要求name和type都要匹配才行,type可以向上兼容
3、注意:
  • @Resource注解只有标注在属性和单个参数的方法上才生效,多个参数则抛出异常:@Resource annotation requires a single-arg method。因此,如果注入目标是一个构造器或多参数方法,那么应该坚持使用@Autowired
  • @Resource也可以配合@Qualifier使用,但是@Resource的name优先级高于@Qualifier指定的值
  • @Resource标注的方法或者属性,默认表示依赖必须注入,并且没有requird选项,不支持null,但是支持Optional包装的null
/**
 * @Date: 2022/12/25
 * Resource注入Bean
 */
@Component
public class ResourceBean {
    // 默认按照属性名注入,若没有找到userDao1,则按类型注入
    @Resource
    private UserDao userDao1;

    // 按照beanName注入
    @Resource(name = "imgUpload")
    private UploadHandler uploadHandler1;

    // 按照beanType注入
    @Resource(type = PDFUpload.class)
    private UploadHandler uploadHandler2;

    // 按照beanName和beanType注入,必须两个都匹配才能注入
    @Resource(name = "txtUpload", type = TxtUpload.class)
    private UploadHandler uploadHandler3;

    @Override
    public String toString() {
        return "ResourceBean{" +
                "userDao1=" + userDao1 +
                "\n, uploadHandler1=" + uploadHandler1 +
                "\n, uploadHandler2=" + uploadHandler2 +
                "\n, uploadHandler3=" + uploadHandler3 +
                '}';
    }
}
/**
 * @Date: 2022/12/25
 * resource注解配置类
 */
@Configuration
@ComponentScan("com.itan.componentscan")
public class ResourceConfig {
}
@Test
public void test1() {
    // 通过配置类创建容器对象
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ResourceConfig.class);
    ResourceBean bean = context.getBean(ResourceBean.class);
    System.out.println(bean);
}

/**
 * 运行结果如下:
 * ResourceBean{userDao1=UserDao{label='@Repository注入的'}
 * , uploadHandler1=com.itan.componentscan.service.ImgUpload@8519cb4
 * , uploadHandler2=com.itan.componentscan.service.PDFUpload@35dab4eb
 * , uploadHandler3=com.itan.componentscan.service.TxtUpload@2d901eb0}
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值