spring自动装配

spring自动装配,第1张

自动装配 1. 什么是自动装配

装配的概念。《spring实战》中给装配下了一个定义:创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化。这就是装配。

自动装配:也就是 **** 会在容器中自动的查找,并自动的给 **** 装配及其关联的属性

Spring 提供了 4 种自动装配策略

2. 依赖注入与自动装配的关系

依赖注入的本质就是装配,装配是依赖注入的具体行为

在传统的使用 xml 文件装配 bean 是一件很繁琐的事情,而且还需要找到对应类型的 bean 才能装配,一旦 bean 很多,就不好维护了。为了解决这种问题。自动装配就是开发人员不必知道具体要装配哪个 bean 的引用,这个识别的工作会由 spring 来完成。与自动装配配合的还有“自动检测”,这个动作会自动识别哪些类需要被配置成 bean,进而来进行装配

因此也可以这样理解:自动装配是为了将依赖注入“自动化”的一个简化配置的 *** 作

3.基于xml中显示配置 3.1.no

当使用autowire="no"时,不使用自动装配,依然需要手动装配,需要显式指定某个bean的引用

<bean id="userService" class="com.beans.UserServcie" autowire="no">
     <property name="userDao" ref="userDao">property>
bean>


3.2.byName
public class Car {
    String color;
    public void setColor(String color) {
        this.color = color;
    }
}

public class Person {
    Car car;
    public void setCar1(Car car) {
        this.car = car;
    }
}


<bean id="person" class="com.dream.pojo.Person" autowire="byName"/>
  
<bean id="car1" class="com.dream.pojo.Car">
    
    <property name="color" value="red"/>
bean>
3.3.byType

把与 **** 的属性具有相同类型的其他 **** 自动装配到 **** 的对应属性中

<bean id="person" class="com.dream.pojo.Person" autowire="byType"/>
 

3.4.constructor

把与 **** 的构造器入参具有相同类型的其他 **** 自动装配到 **** 构造器的对应入参中。值的注意的是,具有相同类型的其他 bean 这句话说明它在查找入参的时候,还是通过 bean的类型来确定

public class Person {
    Car car;
    public Person(Car car){
        this.car = car;
    }
}
<bean id="person" class="com.dream.pojo.Person" autowire="constructor"/>


默认的自动装配策略

默认情况下,default-autowire 属性被设置为 none,标示所有的 bean 都不使用自动装配,除非 bean 上配置了 autowire 属性

<beans default-autowire="byType" 
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="userDao" class="com.beans.UserDao">bean>
    <bean id="userService2" class="com.beans.UserServcie" autowire="byName">bean>
beans>


userService1的autowire="byName"则会使用byName装配。

4.使用注解实现自动装配

spring2.5之后提供了注解方式的自动装配。但是要使用这些注解,需要在配置文件中配置。只有加上这一配置,才可以使用注解进行自动装配,默认情况下基于注解的装配是被禁用的。


<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

beans>
4.1@Autowired 注解

<bean id="person" class="com.dream.pojo.Person"/>
<bean id="car" class="com.dream.pojo.Car"/>

使用 @Autowired 它有几个点需要注意

4.1.1强制性

默认情况下,它具有强制契约特性,其所标注的属性必须是可装配的。如果没有 **** 可以装配到 **** 所标注的属性或参数中,那么你会看到 **** 的异常信息

public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
            Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
    
    // 查找Bean
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    // 如果拿到的 Bean 集合为空,且 isRequired 为 true,就抛出异常
    if (matchingBeans.isEmpty()) {
        if (descriptor.isRequired()) {
            raiseNoSuchBeanDefinitionException(type, "", descriptor);
        }
        return null;
    }
}

看到上面的源码,我们可以得到这一信息,bean 集合为空不要紧,关键 isRequired 条件不能成立,如果成立就会抛异常。那么,如果我们不确定属性是否可以装配,可以这样来使用 Autowired

@Autowired(required = false)
UserService userService;

4.1.2装配策略

我记得曾经有个面试题是这样问的:Autowired 是按照什么策略来自动装配的呢?关于这个问题,不能一概而论,你不能简单的说按照类型或者按照名称。但可以确定的一点的是,它默认是按照类型来自动装配的,即 byType

  • 默认按照类型装配

    关键点 findAutowireCandidates 这个方法

protected Map<String, Object> findAutowireCandidates(
        String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
    
    // 获取给定类型的所有 bean 名称,里面实际循环所有的 beanName,获取它的实例
    // 再通过 isTypeMatch 方法来确定
    String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
            this, requiredType, true, descriptor.isEager());
            
    Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length);
    
    // 根据返回的 beanName,获取其实例返回
    for (String candidateName : candidateNames) {
        if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) {
            result.put(candidateName, getBean(candidateName));
        }
    }
    return result;
}

可以看到它返回的是一个列表,那么就表明,按照类型匹配可能会查询到多个实例。到底应该装配哪个实例呢?我看有的文章里说,可以加注解以此规避。比如 @qulifier、@Primary 等,实际还有个简单的办法

比如,按照 UserService 接口类型来装配它的实现类。UserService 接口有多个实现类,分为 UserServiceImpl、UserServiceImpl2。那么我们在注入的时候,就可以把属性名称定义为 bean实现类的名称

@Autowired
UserService UserServiceImpl2;

这样的话,spring 会按照 byName 来进行装配。首先,如果查到类型的多个实例,spring 已经做了判断

public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
            Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
            
    // 按照类型查找 bean 实例
    Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
    // 如果 bean 集合为空,且 isRequired 成立,就抛出异常
    if (matchingBeans.isEmpty()) {
        if (descriptor.isRequired()) {
            raiseNoSuchBeanDefinitionException(type, "", descriptor);
        }
        return null;
    }
    // 如果查找的 bean 实例大于 1 个
    if (matchingBeans.size() > 1) {
        // 找到最合适的那个,如果没有合适的,也抛出异常
        String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor);
        if (primaryBeanName == null) {
            throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet());
        }
        if (autowiredBeanNames != null) {
            autowiredBeanNames.add(primaryBeanName);
        }
        return matchingBeans.get(primaryBeanName);
    }   
}

可以看出,如果查到多个实例,determineAutowireCandidate 方法就是关键。它来确定一个合适的 bean 返回。其中一部分就是按照 bean 的名称来匹配

protected String determineAutowireCandidate(Map<String, Object> candidateBeans, 
                DependencyDescriptor descriptor) {
    // 循环拿到的 bean 集合
    for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) {
        String candidateBeanName = entry.getKey();
        Object beanInstance = entry.getValue();
        // 通过 matchesBeanName 方法来确定 bean 集合中的名称是否与属性的名称相同
        if (matchesBeanName(candidateBeanName, descriptor.getDependencyName())) {
            return candidateBeanName;
        }
    }
    return null;
}

最后我们回到问题上,得到的答案就是:*** 默认使用 **** 来装配属性,如果匹配到类型的多个实例,再通过 **** 来确定 ***。

总结:

常用的自动装配注解有以下几种:@Autowired,@Resource,@Inject,@Qualifier,@Named。@Autowired注解是byType类型的,这个注解可以用在属性上面,setter方面上面以及构造器上面。使用这个注解时,就不需要在类中为属性添加setter方法了。但是这个属性是强制性的,也就是说必须得装配上,如果没有找到合适的bean能够装配上,就会抛出异常。这时可以使用required=false来允许可以不被装配上,默认值为true。当required=true时,@Autowired要求必须装配,但是在没有bean能装配上时,就会抛出异常:NoSuchBeanDefinitionException,如果required=false时,则不会抛出异常。另一种情况是同时有多个bean是一个类型的,也会抛出这个异常。此时需要进一步明确要装配哪一个Bean,这时可以组合使用@Qualifier注解,值为Bean的名字即可。@Qualifier注解使用byName进行装配,这样可以在多个类型一样的bean中,明确使用哪一个名字的bean来进行装配。@Qualifier注解起到了缩小自动装配候选bean的范围的作用。注意:@Autowired注解是spring提供的,所以会依赖spring的包。

还有一个byType的注解@Inject,与@Autowired注解作用一样,也是byType类型,而且是java ee提供的,完全可以代替@Autowired注解,但是@Inject必须是强制装配的,没有required属性,也就是不能为null,如果不存在匹配的bean,会抛出异常。

@Autowired与@Qualifier可以组合使用,@Inject也有一个组合的注解,就是@Named注解,与@Qualifier作用一样,也是byName,但是不是spring的,是java ee标准的。

这样就出现了两套自动装配的注解组合,@Autowired与@Qualifier是spring提供的,@Inject与@Named是java ee的。

但是@Qualifier注解在java ee中也有一样,作用与spring的@Qualifier注解一模一样,只是所在的包不一样。不过建议大家使用spring的。最后还有一个@Resouce注解, 这个注解也是java ee的,也是byName类型的,原理同@Qualifier和@Named是一样的。

@Resource和@Autowired

  • 都是用来自动装配的,都可以放在属性字段上

  • 实现方式不同

    • @Autowired默认通过bytype的方式实现,如果有多个类型,则通过byname实现,如果两个都找不到,就报错!

    • @Resource默认通过byname的方式实现,如果找不到名字,则通过bytype实现,如果两个都找不到,就报错!

  • 执行的顺序不同:

    • @Autowired默认通过bytype的方式实现

    • @Resource默认通过byname的方式实现

  • 类型重复的话,如果名字不是默认的(如cat1而没有默认的cat)

    • @Autowired配合@Qualifier(value = “cat1”)使用

    • @Resource直接使用@Resource(name = “cat1”)

5.基于注解创建Bean装载到ioc容器

需开启注解扫描


<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:annotation-config/>
    
    <context:component-scan base-package="com.dream.pojo"/>

beans>


@Component  //等价于
@Scope("prototype")//
public class Person {

    @Autowired
    Car car;
}


@Component  //等价于
public class Car {

    @Value("red") //等价于
    String color;
}

衍生的注解

@Component有几个衍生的注解,我们在web开发中,会按照mvc三层架构分层!

  • dao【@Repository】

  • service 【@Service】

  • controller 【@Controller】

    这四个注解功能都是一样的,都是代表将某个注册类注入到Spring中,装配Bean

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/723173.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存