Spring之IOC、DI、Bean注入创建获取详解

2024-06-22 18:18

本文主要是介绍Spring之IOC、DI、Bean注入创建获取详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1 IOC

1.1 IOC– 控制反转 Inversion of Control

1.2 DI-依赖注入Dependency Injection

1.3 Bean的依赖注入方式

1.3.1 属性注入

1.3.2 setter方法注入

1.3.3 构造方法(构造子)注入

1.3.4 接口注入

1.3.5 Spring三种注入不推荐使用@Autowired 

1.4 Bean的创建方式

1.4.1 构造器实例化

1.4.2 静态工厂实例化

1.4.3 实例工厂实例化

1.5 spring容器bean的作用域

1.5.1 Spring的Bean主要支持五种作用域

1.5.2 Spring中单例Bean线程安全问题

1.6 Spring自动装配方式

1.6.1 什么是自动装配

1.6.2 自动装配方式

1.7 手动获取 Bean 的几种方式

1.7.1 直接注入 ApplicationContext

1.7.2 通过 ApplicationContextAware 接口获取

1.7.3 通过 ApplicationObjectSupport 和 WebApplicationObjectSupport 获取

1.7.4 通过 HttpServletRequest 获取

1.8 @Autowrite 和 @Resource 以及 @Qualifier 注解的区别


1 IOC

1.1 IOC– 控制反转 Inversion of Control

控制反转是将对象的生命周期控制权限交由Spring容器管理. 程序不再编写对象创建语句,只使用对象. 这样程序员的精力可以集中在业务开发中. 并且通过Spring容器实现了各层代码之间的解耦, 让代码没有依赖关系. 且实现了代码的可插拔性

就 IOC 本身而言,其并不是什么新技术,只是一种思想理念。IOC 的核心就是原先创建一个对象,我们需要自己直接通过 new 来创建,而 IOC 就相当于有人帮们创建好了对象,需要使用的时候直接去拿就行,IOC 主要有两种实现方式:

  • DL(Dependency Lookup):依赖查找
    这种就是说容器帮我们创建好了对象,我们需要使用的时候自己再主动去容器中查找,如:
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/application-context.xml");
    Object bean = applicationContext.getBean("object");
  • DI(Dependency Inject):依赖注入。
    依赖注入相比较依赖查找又是一种优化,也就是我们不需要自己去查找,只需要告诉容器当前需要注入的对象,容器就会自动将创建好的对象进行注入(赋值)

1.2 DI-依赖注入Dependency Injection

依赖:是说, bean对象都依赖Spring容器来管理生命周期. 注入:是说, bean对象的资源都由Spring容器来设置和装配.
依赖注入是控制反转的一种技术体现. 这种技术可以实现类之间的解耦. 当使用Spring容器管理对象的时候, 一个对象依赖的其他对象会通过被动的方式传递进来, 而不是这个对象自己创建或者查找依赖对象. 而是容器在对象初始化时不等对象请求就主动将依赖传递给它, 实现资源的设置和装配.

为对象的属性注入值,回调属性的set方法为属性赋值

  • 遍历beansMap(保存配置文件信息),获取bean节点及对应的property节点
  • 获取property节点的name属性,然后根据name属性获取对应的setter方法
  • 回调setter方法,将property节点的ref所引用的对象赋值给属性。

通过 xml 的注入方式我们不做讨论,在这里主要讨论基于注解的注入方式,基于注解的常规注入方式通常有三种:

  • 基于属性注入
  • 基于 setter 方法注入
  • 基于构造器注入

1.3 Bean的依赖注入方式

Spring支持构造方法注入属性注入setter方法注入接口注入

1.3.1 属性注入

Spring中属性注入不需要set方法是因为Spring使用反射机制直接访问对象的属性,从而实现属性的注入。
在注入时,Spring会根据属性的类型和名称,自动匹配相应的Bean进行注入。因此,只要属性的名称和类型与Bean的名称和类型匹配,就可以实现属性的注入,不需要手动编写set方法。这种方式称为自动装配(autowiring)。自动装配可以大大简化代码,提高开发效率。

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

1.3.2 setter方法注入

对于Setter方法注入,需要在类中定义对应的Setter方法,Spring会通过反射机制调用Setter方法来完成属性注入

@Service
public class UserService {private Wolf3Bean wolf3Bean;@Autowired  //通过setter方法实现注入public void setWolf3Bean(Wolf3Bean wolf3Bean) {this.wolf3Bean = wolf3Bean;}
}

为属性注入常量值

 <!-- setter注入:本质上调用的是属性的setter方法为属性注入常量值 --><bean id="user" class="cn.entity.Userinfo"><!-- user.setUserId(1) --><property name="userId" value="1"></property><!-- user.setUserName("zhangsan") --><property name="userName" value="zhangsan"></property></bean>

为属性注入对象

 <bean id="userService" class="cn.service.impl.UserServiceImpl"><!-- setter注入:回调setUserDao方法为属性赋值。 --><property name="userDao" ref="userHibernateDao"></property></bean>

为属性为集合类型的对象注入值         

集合示范类:

  public class TestCollection {private List list;private Set set;private Map map;private Properties props;省去getset方法}

    eg:这是集合类型的配置文件:

    <!-- 利用setter注入为集合赋值 --><bean id="testCollection" class="cn.entity.TestCollection"><!-- 为list注入值 --><property name="list"><list><value>list1</value><value>list2</value><value>list3</value></list></property><!-- 为set注入值 --><property name="set"><set><value>set1</value><value>set2</value><value>set3</value></set></property><!-- 为map注入值 --><property name="map"><map><entry key="m-key1" value="m-value1"></entry><entry key="m-key2" value="m-value2"></entry></map></property><!-- 为props属性注入值 --><property name="props"><props><prop key="p-key1">p-value1</prop><prop key="p-key2">p-value2</prop></props></property></bean>

byName

<beans>  <!--此时的id就必须与Order.java中所定义的OrderItem的对象名称一样了,不然就会找不到-->  <bean id="orderitem" class="org.jia.OrderItem">  <property name="orderdec" value="item00001"></property>  </bean>  <bean id="order" class="org.jia.Order" autowire="byName">  <property name="orderNum" value="order000007"></property>  </bean>  
</beans>

 byType

<beans>  <!--按照byType注入则就与id没有关系,可以随便定义id !!!但是不能出现多个此类的id-->  <bean id="orderitdfadafaem" class="org.jia.OrderItem">  <property name="orderdec" value="item00001"></property>  </bean>  <bean id="order" class="org.jia.Order" autowire="byType">  <property name="orderNum" value="order000007"></property>  </bean>  
</beans> 

根据constructor注入 

<beans> <bean id="orderItem" class="org.jia.OrderItem"> <property name="orderdec" value="item00001"></property> </bean> <bean id="order" class="org.jia.Order" autowire="constructor"> <property name="orderNum" value="order000007"></property> </bean> 
</beans>

1.3.3 构造方法(构造子)注入

构造函数注入,Spring会直接通过构造函数来实例化对象并完成属性注入,因此不需要定义对应的Setter方法,当两个类属于强关联时,我们也可以通过构造器的方式来实现注入

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

构造方法(构造子)注入回调构造方法为属性赋值;本质上回调类的构造方法对属性进行赋值   

        <!-- 构造方法注入:调用构造方法为属性注入常量值;注意:参数的下标从0开始 --><bean id="user2" class="cn.entity.Userinfo"><!--index="构造函数参数的下标,下标从0开始"--><constructor-arg index="0" value="2"></constructor-arg><constructor-arg index="1" value="admin"></constructor-arg><constructor-arg index="2" value="admin"></constructor-arg><constructor-arg index="3" value="1"></constructor-arg></bean>

如下java代码示例 

public CatDaoImpl(String message){this. message = message;}

xml中操作如下: 

<bean id="CatDaoImpl" class="com.CatDaoImpl"> <constructor-arg value=" message "></constructor-arg>
</bean>

1.3.4 接口注入

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

@Autowired
private IWolf iWolf;

此时启动服务就会报错:

这个就是说本来应该注入一个类,但是 Spring 找到了三个,所以没法确认到底应该用哪一个。这个问题如何解决呢?
解决思路主要有以下 5 种:

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

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

  • 通过其他 @Condition 条件注解
    除了上面的配置文件条件,还可以通过其他类似的条件注解,如:
    @ConditionalOnBean:当存在某一个 Bean 时,初始化此类到容器。
    @ConditionalOnClass:当存在某一个类时,初始化此类的容器。
    @ConditionalOnMissingBean:当不存在某一个 Bean 时,初始化此类到容器。
    @ConditionalOnMissingClass:当不存在某一个类时,初始化此类到容器
    类似这种实现方式也可以非常灵活的实现动态化配置。
    不过上面介绍的这些方法似乎每次都只能固定注入一个实现类,那么如果我们就是想多个类同时注入,不同的场景可以动态切换而又不需要重启或者修改配置文件,又该如何实现呢?

  • 通过 @Resource 注解动态获取
    如果不想手动获取,我们也可以通过 @Resource 注解的形式动态指定 BeanName 来获取:
    @Component
    public class InterfaceInject {@Resource(name = "wolf1Bean")private IWolf iWolf;
    }
    

    如上所示则只会注入 BeanName 为 wolf1Bean 的实现类。

  • 通过集合注入
    除了指定 Bean 的方式注入,我们也可以通过集合的方式一次性注入接口的所有实现类:
    @Component
    public class InterfaceInject {@AutowiredList<IWolf> list;@Autowiredprivate Map<String,IWolf> map;
    }
    
    上面的两种形式都会将 IWolf 中所有的实现类注入集合中。如果使用的是 List 集合,那么我们可以取出来再通过 instanceof 关键字来判定类型;而通过 Map 集合注入的话,Spring 会将 Bean 的名称(默认类名首字母小写)作为 key 来存储,这样我们就可以在需要的时候动态获取自己想要的实现类。
  • @Primary 或 @Order 注解实现默认注入
    除了上面的几种方式,我们还可以在其中某一个实现类上加上 @Primary 注解来表示当有多个 Bean 满足条件时,优先注入当前带有 @Primary 注解的 Bean
    @Order 注解用于指定 Bean 的加载顺序,它可以用于同类型的 Bean 之间的排序
    @Component
    @Primary
    public class Wolf1Bean implements IWolf{
    }@Component
    @Order(1)
    public class A implements MyInterface {// ...
    }@Component
    @Order(2)
    public class B implements MyInterface {// ...
    }

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

1.3.5 Spring三种注入不推荐使用@Autowired 

@Autowired注解相信每个Spring开发者都不陌生了
但是当我们使用IDEA写代码的时候,经常会发现@Autowired注解下面是有小黄线的,我们把小鼠标悬停在上面,可以看到这个如下图所示的警告信息:

那为什么IDEA会给出Field injection is not recommended这样的警告呢?

一起来全面的了解下Spring中的三种注入方式以及他们之间在各方面的优劣。

三种依赖注入的对比

在知道了Spring提供的三种依赖注入方式之后,继续回到说到的问题:IDEA为什么不推荐使用Field Injection呢?

我们可以从多个开发测试的考察角度来对比一下它们之间的优劣:

可靠性

从对象构建过程和使用过程,看对象在各阶段的使用是否可靠来评判:

  • Field Injection:不可靠
  • Constructor Injection:可靠
  • Setter Injection:不可靠

由于构造函数有严格的构建顺序和不可变性,一旦构建就可用,且不会被更改。

可维护性

主要从更容易阅读、分析依赖关系的角度来评判:

  • Field Injection:差
  • Constructor Injection:好
  • Setter Injection:差

还是由于依赖关键的明确,从构造函数中可以显现的分析出依赖关系,对于我们如何去读懂关系和维护关系更友好。

可测试性

当在复杂依赖关系的情况下,考察程序是否更容易编写单元测试来评判

  • Field Injection:差
  • Constructor Injection:好
  • Setter Injection:好

Constructor Injection和Setter Injection的方式更容易Mock和注入对象,所以更容易实现单元测试。

灵活性

主要根据开发实现时候的编码灵活性来判断:

  • Field Injection:很灵活
  • Constructor Injection:不灵活
  • Setter Injection:很灵活

由于Constructor Injection对Bean的依赖关系设计有严格的顺序要求,所以这种注入方式不太灵活。相反Field Injection和Setter Injection就非常灵活,但也因为灵活带来了局面的混乱,也是一把双刃剑。

循环关系的检测

对于Bean之间是否存在循环依赖关系的检测能力:

  • Field Injection:不检测
  • Constructor Injection:自动检测
  • Setter Injection:不检测

性能表现

不同的注入方式,对性能的影响

  • Field Injection:启动快
  • Constructor Injection:启动慢
  • Setter Injection:启动快

主要影响就是启动时间,由于Constructor Injection有严格的顺序要求,所以会拉长启动时间。

所以,综合上面各方面的比较,可以获得如下表格:

结果一目了然,Constructor Injection在很多方面都是优于其他两种方式的,所以Constructor Injection通常都是首选方案
而Setter Injection比起Field Injection来说,大部分都一样,但因为可测试性更好,所以当你要用@Autowired的时候,推荐使用Setter Injection的方式,这样IDEA也不会给出警告了。同时,也侧面反映了,可测试性的重要地位

因此,依赖注入的使用上,Constructor Injection是首选。使用@Autowired注解的时候,要使用Setter Injection方式,这样代码更容易编写单元测试。

1.4 Bean的创建方式

Spring容器创建Bean对象的方式有三种,分别是:用构造器实例化,静态工厂方法实例化,实例工厂方法实例化

1.4.1 构造器实例化

通过调用构造函数来创建bean对象。   常用在默认情况下,当我们在spring的配置文件中写了一个bean标签,并提供了Class属性spring就会调用默认构造函数创建对象。如果没有默认构造函数,则对象创建失败。

 <!-- 默认构造函数创建 --><bean id="accountService"class="com.test.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>

1.4.2 静态工厂实例化

静态工厂顾名思义,就是通过调用静态工厂的方法来获取自己需要的对象,为了让 Spring 管理所有对象,我们不能直接通过"工程类.静态方法()"来获取对象,而是依然通过 Spring 注入的形式获取

public class User {private String username;private String password;public User() {System.out.println("无参的构造函数被调用了...");}public User(String username,String password){System.out.println("有参构造函数被调用....");this.username=username;this.password=password;}省去getset方法
}

创建UserFactory工厂类,并在其中提供静态工程方法getUserInstance() 

public class UserFactory {public static User getUserInstance(){return new User();}public static User getUserInstance(String username,String password){return new User(username,password);}
}

 在spring配置文件中配置静态工程

<!-- 使用静态工程方法创建对象 --> 
<!--factory-method="静态的工程方法" -->
<bean id="user2" class="cn.entity.UserFactory" factory-method="getUserInstance"></bean>
<!-- 带参数的静态工程方法 -->
<bean id="user3" class="cn.entity.UserFactory" factory-method="getUserInstance"><constructor-arg index="0" value="zhangsan"></constructor-arg><constructor-arg index="1" value="123"></constructor-arg>
</bean>

测试类

public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ioc.xml");
//		//调用静态工程方法创建对象User staticMethodUser = applicationContext.getBean("user2", User.class);System.out.println(staticMethodUser);System.out.println("---------------");User staticMethodUser2 = applicationContext.getBean("user3", User.class);System.out.println(staticMethodUser2);}

1.4.3 实例工厂实例化

实例工厂,也叫非静态工厂,意思是工厂方法不是静态的,所以我们需要首先 new 一个工厂实例,再调用普通的实例方法

public class UserFactory2 {public void init(){System.out.println("init().....");}public User getUserInstance(){return new User();}public User getUserInstance(String username,String password){return new User(username,password);}public void destory(){System.out.println("destory().....");}
}

 在spring配置文件中配置静态工程

<!-- 实例工程方法 --> <!-- 1.先创建工厂的实例 --><bean id="userFactory" class="cn.entity.UserFactory2"></bean><!-- 2.调用实例工厂中的工程方法创建对象 --><bean id="user4" factory-bean="userFactory"  factory-method="getUserInstance" init-method="init" destory-method="destroy"></bean><bean id="user5" factory-bean="userFactory"  factory-method="getUserInstance"><constructor-arg index="0" value="admin"></constructor-arg><constructor-arg index="1" value="admin"></constructor-arg></bean>

测试类

public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ioc.xml");
//		//调用静态工程方法创建对象User staticMethodUser = applicationContext.getBean("user2", User.class);System.out.println(staticMethodUser);System.out.println("---------------");User staticMethodUser2 = applicationContext.getBean("user3", User.class);System.out.println(staticMethodUser2);}

1.5 spring容器bean的作用域

1.5.1 Spring的Bean主要支持五种作用域

情况1:singleton时(默认)

当bean的作用域为singleton时(默认),在初始化spring容器时,在Spring容器仅存在一个Bean实例,Bean以单实例的方式存在
(ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ioc.xml");)  创建bean的对象,针对用户的所有请求都将返回一个相同的bean对象.

 <bean id="user" class="cn.entity.User" scope="singleton"></bean>

情况2:prototype时(原型)

当bean的作用域为prototype时(原型),当调用getBean方法时针对用户的请求创建bean的对象,针对每一个情况创建一个对象,推迟到使用getBean()方式获取对象时才创建。    即:每次从容器重调用Bean时,都会返回一个新的实例。

 <bean id="user" class="cn.entity.User" scope="prototype"></bean>

以下三个作用域于只在Web应用中适用:

  • request : 每一次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP Request内有效。
  • session : 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean
  • globalSession:同一个全局Session共享一个Bean,只用于基于Protlet的Web应用,Spring5中已经不存在
<!-- scope范围 --><bean id="user" class="cn.entity.User" scope="singleton" init-method="init"></bean>
<!-- 使用静态工厂创建对象 --><bean id="userStatic" class="cn.entity.UserFactory" factory-method="getInstance"  init-method="init"></bean>属性factory-bean: 可选属性, 代表当前bean使用什么工厂bean创建对象. 此属性与class属性互斥.此属性与class属性互斥.此属性与class属性互斥.
<!-- 使用动态工厂创建对象 --><!-- 1.先创建工厂的实例 --><bean id="userFactory" class="cn.jzh.entity.UserFactory2" init-method="init"></bean><!-- 2.通过实例工厂方法创建对象 --><!-- factory-bean和class互斥 --><bean id="user2" factory-bean="userFactory" factory-method="getInstance" init-method="init"></bean>

1.5.2 Spring中单例Bean线程安全问题

首先结论在这:Spring中的单例Bean不是线程安全的

因为单例Bean,是全局只有一个Bean,所有线程共享。如果说单例Bean,是一个无状态的,也就是线程中的操作不会对Bean中的成员变量执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

假如这个Bean是有状态的,也就是会对Bean中的成员变量进行写操作,那么可能就存在线程安全的问题。

有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。

无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。

单例Bean线程安全问题怎么解决呢?

常见的有这么些解决办法:

  1. 将Bean定义为多例
    这样每一个线程请求过来都会创建一个新的Bean,但是这样容器就不好管理Bean,不能这么办。
  2. 在Bean对象中尽量避免定义可变的成员变量
  3. 将Bean中的成员变量保存在ThreadLocal中
    我们知道ThredLoca能保证多线程下变量的隔离,可以在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal里,这是推荐的一种方式

1.6 Spring自动装配方式

1.6.1 什么是自动装配

Spring IOC容器知道所有Bean的配置信息,此外,通过Java反射机制还可以获知实现类的结构信息,如构造方法的结构、属性等信息。掌握所有Bean的这些信息后,Spring IOC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须通过显式的方式进行依赖配置。

Spring提供的这种方式,可以按照某些规则进行Bean的自动装配,元素提供了一个指定自动装配类型的属性:autowire="<自动装配类型>"

1.6.2 自动装配方式

自动装配模式可以简化Spring配置文件. 提高开发效率.    

bean标签中的属性 :  autowire="byName|byType|constructor|autodetect"          

byName:    通过属性名称进行自动装配:会根据属性名称在spring容器查找与其相同名称的bean,然后进行注入
要求:属性名称必须与spring容器中待注入(是指配置文件里要set进属性里面的)的bean的id一致。
见解:要注入的service层的dao名字要和配置文件里面的bean节点id一致

eg: 配置文件:

<bean id="userDao" class="cn.dao.impl.UserDaoImpl"></bean>
<bean id="userHibernate" class="cn.dao.impl.UserHibernateImpl"></bean>
<bean id="userService" class="cn.service.impl.UserServiceImpl" autowire="byName">           </bean>

service层:

               private UserDao userHibernate;public void setUserHibernate(UserDao userHibernate) {this.userHibernate = userHibernate;}@Overridepublic void add() {userHibernate.add();}

byType:  通过属性的类型进行自动装配:会根据属性类型在spring容器查找与其相同类型的bean,然后进行注入
要求:当前spring容器待注入的类型要唯一,否则将抛出NoUniqueBeanDefinitionException异常
见解:要注入的servic层的dao的类型要和配置文件里面bean的class类一致

constructor:通过构造方法进行自动装配:优先使用byName自动装配模式调用构造方法进行bean对象的构建.
如果byName自动装配模式无法匹配所有的构造参数,则根据byType自动装配模式调用构造方法进行bean对象的构建.这时如果有多个同类型bean,会在构建bean对象时抛出异常
与 byType类似, 只不过它是针对构造函数注入而言的。如果Boss有一个构造函数,构造函数包含一个Car类型的入参,如果容器中有一个Car类型的Bean,则Spring将自动把这个Bean作为Boss构造函数的入参;如果容器中没有找到和构造函数入参匹配类型的Bean,则Spring将抛出异常

autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配,如果Bean提供了默认的构造函数,则采用byType,否则采用constructor 

1.7 手动获取 Bean 的几种方式

在 Spring 项目中,手动获取 Bean 需要通过 ApplicationContext 对象,这时候可以通过以下 5 种方式进行获取:

1.7.1 直接注入 ApplicationContext

最简单的一种方法就是通过直接注入的方式获取 ApplicationContext 对象,然后就可以通过 ApplicationContext 对象获取 Bean :

@Component
public class InterfaceInject {@Autowiredprivate ApplicationContext applicationContext;//注入public Object getBean(){return applicationContext.getBean("wolf1Bean");//获取bean}
}

1.7.2 通过 ApplicationContextAware 接口获取

通过实现 ApplicationContextAware 接口来获取 ApplicationContext 对象,从而获取 Bean。
需要注意的是,实现 ApplicationContextAware 接口的类也需要加上注解,以便交给 Spring 统一管理(这种方式也是项目中使用比较多的一种方式):

@Component
public class SpringContextUtil implements ApplicationContextAware {private static ApplicationContext applicationContext = null;@Overridepublic 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);}
}

封装之后,我们就可以直接调用对应的方法获取 Bean 了:

Wolf2Bean wolf2Bean = SpringContextUtil.getBeanByName("wolf2Bean");
Wolf3Bean wolf3Bean = SpringContextUtil.getBeanByType(Wolf3Bean.class);

1.7.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);}@PostConstructpublic 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();}
}

1.7.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();}
}

1.8 @Autowrite 和 @Resource 以及 @Qualifier 注解的区别

上面我们看到了,注入一个 Bean 可以通过 @Autowrite,也可以通过 @Resource 注解来注入,这两个注解有什么区别呢?

  • @Autowrite:通过类型去注入,可以用于构造器和参数注入。当我们注入接口时,其所有的实现类都属于同一个类型,所以就没办法知道选择哪一个实现类来注入。
  • @Resource:默认通过名字注入,不能用于构造器和参数注入。如果通过名字找不到唯一的 Bean,则会通过类型去查找。如下可以通过指定 name 或者 type 来确定唯一的实现:
    @Resource(name = "wolf2Bean",type = Wolf2Bean.class)
    private IWolf iWolf;

@Qualifier 注解是用来标识合格者,当 @Autowrite 和 @Qualifier 一起使用时,就相当于是通过名字来确定唯一:

@Qualifier("wolf1Bean")
@Autowired
private IWolf iWolf;

那可能有人就会说,我直接用 @Resource 就好了,何必用两个注解结合那么麻烦,这么一说似乎显得 @Qualifier 注解有点多余?
@Qualifier 注解是多余的吗
我们先看下面声明 Bean 的场景,这里通过一个方法来声明一个 Bean (MyElement),而且方法中的参数又有 Wolf1Bean 对象,那么这时候 Spring 会帮我们自动注入 Wolf1Bean:

@Component
public class InterfaceInject2 {@Beanpublic MyElement test(Wolf1Bean wolf1Bean){return new MyElement();}
}

然而如果说我们把上面的代码稍微改一下,把参数改成一个接口,而接口又有多个实现类,这时候就会报错了:

@Component
public class InterfaceInject2 {@Beanpublic MyElement test(IWolf iWolf){//此时因为IWolf接口有多个实现类,会报错return new MyElement();}
}

而 @Resource 注解又是不能用在参数中,所以这时候就需要使用 @Qualifier 注解来确认唯一实现了(比如在配置多数据源的时候就经常使用 @Qualifier 注解来实现):

@Component
public class InterfaceInject2 {@Beanpublic MyElement test(@Qualifier("wolf1Bean") IWolf iWolf){return new MyElement();}
}

这篇关于Spring之IOC、DI、Bean注入创建获取详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1085042

相关文章

ESP32 esp-idf esp-adf环境安装及.a库创建与编译

简介 ESP32 功能丰富的 Wi-Fi & 蓝牙 MCU, 适用于多样的物联网应用。使用freertos操作系统。 ESP-IDF 官方物联网开发框架。 ESP-ADF 官方音频开发框架。 文档参照 https://espressif-docs.readthedocs-hosted.com/projects/esp-adf/zh-cn/latest/get-started/index

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前

eclipse运行springboot项目,找不到主类

解决办法尝试了很多种,下载sts压缩包行不通。最后解决办法如图: help--->Eclipse Marketplace--->Popular--->找到Spring Tools 3---->Installed。

JAVA读取MongoDB中的二进制图片并显示在页面上

1:Jsp页面: <td><img src="${ctx}/mongoImg/show"></td> 2:xml配置: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001

Java面试题:通过实例说明内连接、左外连接和右外连接的区别

在 SQL 中,连接(JOIN)用于在多个表之间组合行。最常用的连接类型是内连接(INNER JOIN)、左外连接(LEFT OUTER JOIN)和右外连接(RIGHT OUTER JOIN)。它们的主要区别在于它们如何处理表之间的匹配和不匹配行。下面是每种连接的详细说明和示例。 表示例 假设有两个表:Customers 和 Orders。 Customers CustomerIDCus