大纲
-
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;
评论