spring自动装配模型该这么理解

2,492 阅读4分钟

自动装配是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以及基本类型和基本类型的包装类是不会被装配的。