自动装配是spring框架的重要功能,是使用spring满足bean依赖的一种方式,spring会在应用中为某个bean寻找其依赖的bean。在spring中有着以下5种装配模型,每一种模型对应着一个int值:
spring种5中装配模型
- 1、AUTOWIRE_NO = 0;(这个NO咋说呢,理解为不使用自动装配吧,spring默认的值)
- 2、AUTOWIRE_BY_NAME = 1;(通过名字自动装配)
- 3、AUTOWIRE_BY_TYPE = 2;(通过类型自动装配)
- 4、AUTOWIRE_CONSTRUCTOR = 3;(通过构造函数自动装配)
- 5、AUTOWIRE_AUTODETECT = 4;(已经被标注过时,本文不再讨论)
常见误区
我们在开发中使用最常见的应该就是通过@Autowired注解来完成注入的。这里有个常见的误区,网上看到说使用@Autowired就是通过类型来装配的。在我理解看来,这种说法是不对的。原因有两点:
1、@Autowired可以通过类型来找对应的类,如果通过类型找不到就通过名字来找,如果还是找不到就会排除异常。
2、虽然使用了@Autowired注解,但装配模型依然还是AUTOWIRE_NO,并未看到有改变装配模型的源码。这也是spring默认的装配模型
使用案例
下面通过代码来证明上面两点 有如下代码
OrderService
@Service
public class OrderService {
}
CustomerService
@Service
public class CustomerService {
}
UserService
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Autowired
private Object customerService;
public OrderService getOrderService() {
return orderService;
}
public Object getCustomerService() {
return customerService;
}
}
AppConfig
@Configuration
@ComponentScan("com.study")
public class AppConfig {
}
Test
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.refresh();
UserService userService = context.getBean(UserService.class);
System.out.println(userService.getOrderService());
GenericBeanDefinition userServiceGenericBeanDefinition = (GenericBeanDefinition)context.getBeanDefinition("userService");
System.out.println(userServiceGenericBeanDefinition.getAutowireMode());
System.out.println(userService.getCustomerService());
}
}
运行以上代码,会输出如下:
com.study.service.OrderService@649d209a
0
com.study.service.CustomerService@357246de
这应该是开发中最常用的方式了,使用了@Autowired注解完成属性注入,但是装配模型依然是0;并且CustomerService这个类也能完成装配。但是,如果在UserService将customerService改成别的,就无法通过名字找出CustomerService类,执行测试类就会抛出UnsatisfiedDependencyException异常。
没有@Autowired注解如何完成注入?
现在,去掉OrderService上的@Autowired注解,再运行测试类,userService.getOrderService()输出的就会为null,也就是说OrderService装配失败。那不通@Autowired注解该如何完成装配呢?这时,就需要更改UserService的装配模型,让spring通过其他模型帮我们完成自动装配。要更改装配模型,我们可以使用ImportBeanDefinitionRegistrar这个接口了(关于ImportBeanDefinitionRegistrar的其他用法可以参照这篇文章ImportBeanDefinitionRegistrar——spring对外提供的注册bean功能)。
1、通过类型装配
增加如下代码:
MyImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
GenericBeanDefinition genericBeanDefinition = (GenericBeanDefinition) registry.getBeanDefinition("userService");
genericBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
这段代码中,将UserService的装配模型改为了AUTOWIRE_BY_TYPE。通过类型装配,还需要提供setter方法,在UserService中增加、修改为如下代码。
UserService
private OrderService orderService1111;
public void setXxxx(OrderService orderService434) {
this.orderService1111 = orderService434;
}
public OrderService getOrderService() {
return orderService111;
}
要说明的是,通过类型转配,和提供的set方法名字没有关系,和属性名字也不会有关系。 此外还需要在Appconfig上增加如下注解
Appconfig
@Import(MyImportBeanDefinitionRegistrar.class)
再运行测试类,正常输出。
2、通过名称装配
MyImportBeanDefinitionRegistrar
修改MyImportBeanDefinitionRegistrar中的装配模型为AUTOWIRE_BY_NAME。 UserService 由于通过名称装配,需要根据setter的名字来查找对应的类,所有需要修改UserService中的set方法名
public void setOrderService(OrderService orderService434) {
this.orderService111 = orderService434;
}
再运行测试类,依然正常输出。
3、通过构造方法装配
MyImportBeanDefinitionRegistrar
修改MyImportBeanDefinitionRegistrar中的装配模型为AUTOWIRE_CONSTRUCTOR。
UserService 由于是根据构造方法注入,自然需要添加构造方法
UserService(OrderService orderService434){
this.orderService111 = orderService434;
}
再运行测试类,依然正常输出。
通过类型装配的案例——Mybatis-spring
上面演示了通过更改装配类型使得不同添加@Autowired注解就可以完成属性的装配,其实在Mybatis-spring中正是这样做的。通过阅读源码,经过以下调用链可以看到Mybatis-spring的做法:
MapperScannerRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
-->MapperScannerRegistrar#registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry)
-->ClassPathMapperScanner#doScan(String... basePackages)
-->ClassPathMapperScanner#processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions)
processBeanDefinitions(Set beanDefinitions)方法中有如下代码:
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
Mybatis-spring通过这种方式,避免了使用@Autowired注解完成自动装配,减少了对spring注解的依赖。
哪些类型的属性spring不会装配?
spring并不是给所有的属性都会装配的,在spring内部限定了部分类型是不是装配的,通过一下调用链可以看到源码中限制的类型:
AbstractAutowireCapableBeanFactory#populateBean
-->autowireByName(beanName, mbd, bw, newPvs);或者autowireByType(beanName, mbd, bw, newPvs);
-->AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties
-->BeanUtils.isSimpleProperty(pd.getPropertyType())
BeanUtils.isSimpleProperty(pd.getPropertyType())中代码如下:
public static boolean isSimpleProperty(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> clazz) {
return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
Enum.class.isAssignableFrom(clazz) ||
CharSequence.class.isAssignableFrom(clazz) ||
Number.class.isAssignableFrom(clazz) ||
Date.class.isAssignableFrom(clazz) ||
URI.class == clazz || URL.class == clazz ||
Locale.class == clazz || Class.class == clazz);
}
/**
* Check if the given class represents a primitive (i.e. boolean, byte,
* char, short, int, long, float, or double) or a primitive wrapper
* (i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double).
* @param clazz the class to check
* @return whether the given class is a primitive or primitive wrapper class
*/
public static boolean isPrimitiveOrWrapper(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
return (clazz.isPrimitive() || isPrimitiveWrapper(clazz));
}
可以看到:Enum、CharSequence、Number、Date、URI、URL、Locale、Class以及基本类型和基本类型的包装类是不会被装配的。