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

news2024/9/24 1:26:42

一、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核心注解的支持有两种方式:

3、annotation-config

1、它的作用是隐式地向IoC容器中注册以下4个BeanPostProcessor,不同的BeanPostProcessor用于检测、处理各自对应的的注解服务,相当于注解处理器,不同的Spring版本有不同的支持,详细请参考官网。

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属性说明:
<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注解一起搭配使用的三个常见注解:
@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
 */
总结:

3、proxyBeanMethods属性说明

1、两个概念:Full全模式,Lite轻量级模式;默认使用全模式,即proxyBeanMethods = true
2、全模式与轻量级模式如何选择:

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注解的属性说明:
3、过滤器类型取值:

按注解类型进行排除:

/**
 * @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
 * 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、内部属性说明:
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、标注位置:
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)方法,用于判断条件是否匹配,参数说明如下:
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、作用范围:
@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、作用范围:

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文件中的属性并注入:
2、注入外部配置文件中的属性的前提是必须将配置文件加载到当前应用程序运行的环境中,可以通过XML和注解两种方式将文件加载到运行的环境中。
3、基于XML方式将文件加载到运行环境中:
4、基于注解方式将文件加载到运行环境中:
# 在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:

在这里插入图片描述

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注入规则
3、注意:
/**
 * @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}
 */

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/137737.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03SpringCloudAlibaba服务注册中心—Zookeeper

目录 注册中心Zookeeper 五步曲&#xff1a; zookeeper服务提供者 1、03-cloud-provider-payment-zookeeper-8004 2、pom.xml 3、改yum 4、主启动 5、业务类 测试1&#xff1a; 测试2&#xff1a; ​ 服务节点是临时节点还是持久节点 粗划分&#xff1a; 细划分&am…

南京晓庄Java期末知识点复习

南京晓庄Java期末知识点复习第一章 Java 语言概述第二章 Java 语法基础标识符基本数据类型**运算符****语句**数组和字符串命令行参数输入输出第三章 类五个基本概念类类的封装类的继承**抽象类**接口关键字常用的工具类第四章 Applet及其应用**Java程序的两种基本形式****Appl…

红队渗透靶场之prime1.0(超详细!)

靶场考察知识 Wordpress WordPress是一个免费的开源内容管理系统&#xff08;CMS&#xff09;&#xff0c;可以用来创建和管理网站或博客。它是由PHP语言和MySQL数据库构建的&#xff0c;并且拥有大量的插件和主题&#xff0c;可以让您轻松地自定义网站的外观和功能 Wpscan扫…

引用量超6000的语义分割经典论文DeepLabv3解读

分享语义分割领域的一篇经典论文DeepLabv3&#xff0c;由谷歌发表于CVPR2017。开源代码地址&#xff1a; Tensorflow版本&#xff1a;https://github.com/tensorflow/models/tree/master/research/deeplabPytorch版本&#xff1a;https://github.com/open-mmlab/mmsegmentatio…

【Spring 入门教程3】

Spring_day03 &#x1f308;博客主页&#xff1a;屠一乐的博客 &#x1f4c5; 发文时间&#xff1a;2023.1.3 &#x1f388; 一定存在只有你才能做成的事 &#x1f339; 博主水平有限&#xff0c;如有错误&#xff0c;欢迎指正 欢迎各位&#x1f44d;收藏&#x1f48e;评论✉ …

mysql简介

一.mysql简介 MySQL是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&a…

TensorFlow之过拟合与欠拟合-3

1 基本概念 过度拟合&#xff08;overfit&#xff09; 正则化&#xff08;regularization&#xff09; L1正则化&#xff08;L1 regularization&#xff09; L2正则化&#xff08;L2 regularization&#xff09; 删除正则化&#xff08;dropout regularization&#xff09…

【计组】异常、CPU指令集--《深入浅出计算机组成原理》(五)

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 一、异常 &#xff08;一&#xff09;异常 异常其实是一个硬件和软件组合到一起的处理过程。异常的发生和捕捉&#xff0c;是在硬件层面完成的。但是异常的处理&#xff0c;是由软件来完成的。 …

【并发编程十】c++线程同步——条件变量(condition_variable)

【并发编程十】c线程同步——条件变量&#xff08;condition_variable&#xff09;一、互斥二、条件变量1、为何要引入条件变量?2、不使用条件变量3、使用条件变量3.1、互斥锁有什么问题&#xff1f;3.2、条件变量3.3、条件变量成员函数3.4、demo三、future四、信号量简介&…

【并发编程九】c++线程同步——互斥(mutex)

【并发编程九】c线程同步——互斥&#xff08;mutex&#xff09;一、互斥1、mutex1.1、mutex1.2、 lock_guard1.3、 RAII2、std::recursive_mutex3、std::shared_mutex、std::shared_lock、std::unique_lock4、std::scoped_lock二、条件变量三、future四、信号量简介&#xff1…

chrony服务器

Chrony是一个开源自由的网络时间协议 NTP 的客户端和服务器软软件。它能让计算机保持系统时钟与时钟服务器&#xff08;NTP&#xff09;同步&#xff0c;因此让你的计算机保持精确的时间&#xff0c;Chrony也可以作为服务端软件为其他计算机提供时间同步服务。 Chrony由两个程序…

【嵌入式Linux环境搭建-10】tftp服务安装、双网卡并行

10.tftp服务安装 板子有有线网卡&#xff0c;需有线连接到电脑 主机安装TFTP服务 $ sudo apt-get install xinetd tftpd tftp $ sudo vim /etc/xinetd.d/tftp /*添加下面内容, service tftp { protocol udp port 69 socket_type dgram wait …

aws lambda 理解RIC和RIE构建和测试容器镜像并通过cdk部署lambda函数

参考资料 AWS Lambda 的新功能 — 容器映像支持 快速构建基于 Lambda 容器镜像的 OCR 应用 利用 Lambda 容器镜像搭建分布式压测引擎 lambda容器镜像构建 可以将 Lambda 函数打包和部署最大 10 GB 的容器映像&#xff0c;轻松构建和部署依赖于大量依赖项的更大型工作负载 …

【Java核心技术】创建多线程

1、多线程的概述 什么是线程&#xff1f; 线程(thread)是一个程序内部的一条执行路径。 我们之前启动程序执行后&#xff0c;main方法的执行其实就是一条单独的执行路径。 public static void main(String[] args) {// 代码...for (int i 0; i < 10; i) {System.out.prin…

《柳叶刀》:约20%中国男性死亡可归因于吸烟

*仅供医学专业人士阅读参考 吸烟喝酒可谓众所周知的两大“健康刺客”。一首歌名为《给我一杯酒》中的歌词唱到“给我一杯酒&#xff0c;再给我一只烟&#xff0c;说走就走&#xff0c;我有的是时间”&#xff0c;传唱度极高&#xff0c;甚至还一度成为短视频平台的最热门的BGM之…

概论_第2章_重点内容__随机变量函数的概率分布

一 定义 概括地说&#xff1a; 随机变量Y是随机变量X的函数。 设g(x) 是一给定的连续函数&#xff0c; 称Yg(X) 为随机变量X的一个函数&#xff0c; Y也是一个随机变量。当X取值 时&#xff0c;Y取值 . ~~~~~~~~~~~~~~ 本文讨论连续型随机变量函数。 定理1: 设X为连续型…

Appium环境搭建及元素定位

01 Appium简介 Appium是一个开源测试自动化框架&#xff0c;可用于原生&#xff0c;混合和移动Web应用程序测试。它使用WebDriver 协议驱动iOS&#xff0c;Android和Windows应用程序。 01 环境搭建步骤 Appium环境安装&#xff1a; 第一步 安装 appium 桌面版客户端 Appium…

Linux挂载磁盘命令

需求&#xff1a; 只有一个硬盘&#xff0c;创建多个挂载点 参考&#xff1a;linux如何实现挂载—linux_是小明同学啊的博客-CSDN博客_linux挂载 1 查看当前挂载情况 执行 df -h&#xff0c; 查看当前 / 根目录下挂载的分区名&#xff0c;在下图中为倒数第三行&#xff0c; …

html练习

1. 2. 3. <h1>各科小常识</h1> <h3>语文</h3> <p> 三国演义是中国四大古典名著之一&#xff0c;元末明初小说家罗贯中所著。是中国第一部章回体历史演义的小说&#xff0c;描写了从东汉末年到西晋初年近100年的历史风云。</p> <hr&g…

Ubuntu20.04 LTS 安装 ros Noetic 树莓派4/PC

Ubuntu 20.04 LTS 安装树莓派系统. 主要参考了这两篇文章&#xff1a; https://blog.csdn.net/liangforcold/article/details/126385774 https://blog.csdn.net/yangcunbiao/article/details/123056806 https://blog.csdn.net/duping812/article/details/110391081 1、下载安…