Spring好好学二(DI依赖注入)

Spring好好学二(DI依赖注入)

大纲

  • 1、基于xml的依赖注入

  • 2、基于注解的依赖注入

  • 3、手动获取Bean

  • 4、@Autowrite 和 @Resource区别

1、依赖注入

        对于spring配置一个bean时,如果需要给该bean提供一些初始化参数,则需要通过依赖注入方式,所谓的依赖注入就是通过spring将bean所需要的一些参数传递到bean实例对象的过程(将依赖关系注入到对象中,不需要每次都new对象),spring的依赖注入有3种方式:

1、使用属性的setter方法注入 ,这是最常用的方式;
2、使用构造器注入;
3、基于注解的注入;

1.1 使用属性的setter方法注入

        属性注入即通过setXxx()方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点

<bean id="……" class="……">
    <property name="属性1" value="……"/>
    <property name="属性2" ref="……"/>
    ……
</bean>

        属性注入要求Bean提供一个默认的构造函数(不带参的构造函数),并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值

1.2 构造函数注入

        构造函数注入是除属性注入之外的另一种常用的注入方式,它保证一些必要的属性在Bean实例化时就得到设置(construct是bean生命周期的第一步,实例化bean),并且确保了Bean实例在实例化后就可以使用。

        第一,在类中,不用为属性设置setter方法(但是可以有),但是需要生成该类带参的构造方法。

        第二,在配置文件中配置该类的bean,并配置构造器,在配置构造器中用到了节点。该节点有四个属性

1、 index是索引,指定注入的属性位置,从0开始;
2、 type是指该属性所对应的类型;
3、 ref 是指引用的依赖对象;
4、value 当注入的不是依赖对象,而是基本数据类型时,就用value;
<!-- 构造函数注入(按类型匹配) -->
<bean id="car1" class="com.spring.model.Car">
    <constructor-arg type="int" value="300"></constructor-arg>
    <constructor-arg type="java.lang.String" value="宝马"></constructor-arg>
    <constructor-arg type="double" value="300000.00"></constructor-arg>
</bean>

即:Car类需要有一个三个参数的构造函数:

package com.spring.model;
public class Car {
    private int maxSpeed;
    private String brand;
    private double price;
    //带参构造方法
    public Car(int maxSpeed,String brand, double price){
        this.maxSpeed=maxSpeed;
        this.brand=brand;
        this.price=price;
    }
    public void run(){
        System.out.println("brand:"+brand+",maxSpeed:"+maxSpeed+",price:"+price);
    }
}

如果Car构造函数3个入参的类型相同,仅通过type就无法确定对应关系了,这时需要通过入参索引的方式进行确定, 或者联合使用类型和索引匹配入参(type和index)

<!-- 构造函数注入(按索引匹配) -->
<bean id="car2" class="com.spring.model.Car">
    <!-- 注意索引从0开始 -->
    <constructor-arg index="0" value="宝马"></constructor-arg>
    <constructor-arg index="1" value="中国一汽"></constructor-arg>
    <constructor-arg index="2" value="300000.00"></constructor-arg>
</bean>

1.3 依赖注入的本质

        spring可以使用xml和注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个bean的引用,这个识别的工作会由spring来完成,自动装配就是为了将依赖注入“自动化”的一个简化配置的操作

        创建应用对象之间协作关系的行为称为装配。也就是说当一个对象的属性是另一个对象时,实例化时,需要为这个对象属性进行实例化,这就是装配。

2、基于注解的依赖注入

基于注解的常规注入方式通常有三种:

  • 基于属性注入

  • 基于 setter 方法注入

  • 基于构造器注入

  • 1、基于注解的的属性注入

@Service
public class UserService {
    @Autowired
    private Wolf1Bean wolf1Bean;//通过属性注入

}
  • 2、基于注解的setter方法注入
@Service
public class UserService {
    private Wolf3Bean wolf3Bean;

    @Autowired  //通过setter方法实现注入
    public void setWolf3Bean(Wolf3Bean wolf3Bean) {
        this.wolf3Bean = wolf3Bean;
    }
}
  • 3、基于注解的构造器注入
@Service
public class UserService {
     private Wolf2Bean wolf2Bean;

     @Autowired //通过构造器注入
    public UserService(Wolf2Bean wolf2Bean) {
        this.wolf2Bean = wolf2Bean;
    }
}

2.1 接口注入

        在上面的三种常规注入方式中,假如我们想要注入一个接口,而当前接口又有多个实现类,那么这时候就会报错,因为 Spring 无法知道到底应该注入哪一个实现类。比如我们上面的三个类全部实现同一个接口 IWolf,那么这时候直接使用常规的,不带任何注解元数据的注入方式来注入接口 IWolf

@Autowired
private IWolf iWolf; 

        此时,启动项目会报错,本来应该注入一个类,但是 Spring 找到了三个,所以没法确认到底应该用哪一个。

2.1.1 通过配置文件和 @ConditionalOnProperty 注解实现

        通过 @ConditionalOnProperty 注解可以结合配置文件来实现唯一注入。下面示例就是说如果配置文件中配置了 lonely.wolf=test1,那么就会将 Wolf1Bean 初始化到容器,此时因为其他实现类不满足条件,所以不会被初始化到 IOC 容器,所以就可以正常注入接口:

@Component
@ConditionalOnProperty(name = "lonely.wolf",havingValue = "test1")
public class Wolf1Bean implements IWolf{
}

        当然,这种配置方式,编译器可能还是会提示有多个 Bean,但是只要我们确保每个实现类的条件不一致,就可以正常使用。

2.1.2 通过其他 @Condition 条件注解

除了上面的配置文件条件,还可以通过其他类似的条件注解,如:

  • @ConditionalOnBean:当存在某一个 Bean 时,初始化此类到容器。
  • @ConditionalOnClass:当存在某一个类时,初始化此类的容器。
  • @ConditionalOnMissingBean:当不存在某一个 Bean 时,初始化此类到容器。
  • @ConditionalOnMissingClass:当不存在某一个类时,初始化此类到容器。
  • ...

类似这种实现方式也可以非常灵活的实现动态化配置。

不过上面介绍的这些方法似乎每次都只能固定注入一个实现类,那么如果我们就是想多个类同时注入,不同的场景可以动态切换而又不需要重启或者修改配置文件,又该如何实现呢?

2.1.3 通过 @Resource 注解动态获取

@Component
public class InterfaceInject {
    @Resource(name = "wolf1Bean")
    private IWolf iWolf;
}

2.1.4 通过集合注入

通过集合的方式一次性注入接口的所有实现类:

@Component
public class InterfaceInject {
    @Autowired
    List<IWolf> list;

    @Autowired
    private Map<String,IWolf> map;
}

        上面的两种形式都会将 IWolf 中所有的实现类注入集合中。如果使用的是 List 集合,那么我们可以取出来再通过 instanceof 关键字来判定类型;而通过 Map 集合注入的话,Spring 会将 Bean 的名称(默认类名首字母小写)作为 key 来存储,这样我们就可以在需要的时候动态获取自己想要的实现类。

2.1.5 @Primary 注解实现默认注入

        在其中某一个实现类上加上 @Primary 注解来表示当有多个 Bean 满足条件时,优先注入当前带有 @Primary 注解的 Bean

@Component
@Primary
public class Wolf1Bean implements IWolf{
}

        通过这种方式,Spring 就会默认注入 wolf1Bean,而同时我们仍然可以通过上下文手动获取其他实现类,因为其他实现类也存在容器中

3、手动获取Bean

3.1 直接注入

@Component
public class InterfaceInject {
    @Autowired
    private ApplicationContext applicationContext;//注入

    public Object getBean(){
        return applicationContext.getBean("wolf1Bean");//获取bean
    }
}

3.2 通过 ApplicationContextAware 接口获取

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 通过名称获取bean
     */
    public static <T>T getBeanByName(String beanName){
        return (T) applicationContext.getBean(beanName);
    }

    /**
     * 通过类型获取bean
     */
    public static <T>T getBeanByType(Class<T> clazz){
        return (T) applicationContext.getBean(clazz);
    }
}

3.3 ApplicationObjectSupport 和 WebApplicationObjectSupport

这两个对象中,WebApplicationObjectSupport 继承了 ApplicationObjectSupport,所以并无实质的区别。

同样的,下面这个工具类也需要增加注解,以便交由 Spring 进行统一管理:

@Component
public class SpringUtil extends /*WebApplicationObjectSupport*/ ApplicationObjectSupport {
    private static ApplicationContext applicationContext = null;

    public static <T>T getBean(String beanName){
        return (T) applicationContext.getBean(beanName);
    }

    @PostConstruct
    public void init(){
        applicationContext = super.getApplicationContext();
    }
}

有了工具类,在方法中就可以直接调用了:

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {
    @GetMapping("/bean3")
    public Object getBean3(){
        Wolf1Bean wolf1Bean = SpringUtil.getBean("wolf1Bean");
        return wolf1Bean.toString();
    }
}

3.4 通过 HttpServletRequest 获取

通过 HttpServletRequest 对象,再结合 Spring 自身提供的工具类 WebApplicationContextUtils 也可以获取到 ApplicationContext 对象,而 HttpServletRequest 对象可以主动获取(如下 getBean2 方法),也可以被动获取(如下 getBean1 方法):

@RestController
@RequestMapping("/hello")
@Qualifier
public class HelloController {

    @GetMapping("/bean1")
    public Object getBean1(HttpServletRequest request){
        //直接通过方法中的HttpServletRequest对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        Wolf1Bean wolf1Bean = (Wolf1Bean)applicationContext.getBean("wolf1Bean");

        return wolf1Bean.toString();
    }

    @GetMapping("/bean2")
    public Object getBean2(){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();//手动获取request对象
        ApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

        Wolf2Bean wolf2Bean = (Wolf2Bean)applicationContext.getBean("wolf2Bean");
        return wolf2Bean.toString();
    }
}

4、@Autowrite 和 @Resource区别

        注入一个 Bean 可以通过 @Autowrite,也可以通过 @Resource 注解来注入,这两个注解有什么区别呢?

  • @Autowrite:通过类型(byType)去注入,可以用于构造器和参数注入。当我们注入接口时,其所有的实现类都属于同一个类型,所以就没办法知道选择哪一个实现类来注入。但是如果只根据type无法辨别注入对象时,就需要配合使用 @Qualifier 注解或者 @Primary 注解使用。
  • @Resource:默认通过名字(byName)注入,不能用于构造器和参数注入。如果通过名字(byName)找不到唯一的 Bean,则会通过类型(byType)去查找。如下可以通过指定 name 或者 type 来确定唯一的实现:
@Resource(name = "wolf2Bean",type = Wolf2Bean.class)
 private IWolf iWolf;
@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;
end
  • 作者:旭仔(联系作者)
  • 发表时间:2024-04-27 13:52
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论