Spring Bean的那些事儿


 

使用的spring源码版本 5.2.15.RELEASE

 

bean的作用域

作用域描述生命周期优点缺点
singleton 单例 (默认)该类在spring容器中只有一个实例,无论引用、获取多少次,使用的都是同一个对象spring容器启动时就创建实例,由spring容器管理实例的生命周期,随spring容器的卸载而销毁减少资源占用,后续可以快速获取bean不是线程安全的,多线程下操作同一个单例的bean,可能出现并发问题
prototype 原型每次获取该类的实例时,都会重新创建一个实例需要获取bean实例时由spring容器创建实例,创建好之后交给调用者,由调用者负责管理,实例没有被任何对象引用时变成垃圾等待gc回收线程安全,多线程下操作的是不同的实例,不会导致线程安全问题。原型主要是为了解决线程安全问题频繁创建实例会增加系统开销,使用时要慎重
request在web应用中有效,一次http请求对应一个实例一次http请求
session在web应用中有效,一个会话对应一个实例只在本次session中有效
globalSession只在基于portlet的web应用中才有效,spring5已经废弃了portlet,不做过多介绍
<!--scope属性指定作用域-->
<bean name="" class="" scope="prototype" />
@Component
@Scope("prototype")  //指定作用域,可以用string,也可以用 ConfigurableBeanFactory.SCOPE_PROTOTYPE 之类的预定义常量,@Scope的文档注释中有这些常量
public class Xxx {

}

 

beanName、alias

  • beanName:具有唯一性,唯一标识一个bean
  • alias:指定别名,一个bean可以拥有多个别名

spring默认不允许bean定义覆盖,如果在同一个配置文件或配置类中,beanName或别名发生重复,启动时会直接报错。
 

<!--id指定beanName,name指定别名-->
<bean id="xxx" name="xxx1,xxx2,xxx3" class="" />
@Component("xxx")  //@Component体系的注解可以用value属性指定组件名,即beanName,默认值是空串


//name属性指定beanName、别名,值是String[],可以指定单个或多个值,缺省时默认取方法名
//只有一个值时作为beanName,有多个值时第一个作为beanName,其它作为别名
@Bean(name = "xxx")
@Bean(name = {"xxx2", "xxx2"})  

//value、name属性互为别名,也可以用value属性指定
@Bean("xxx")
@Bean({"xxx2", "xxx2"})

 

beanName的默认生成策略

BeanNameGenerator 接口定义了生成beanName的方法

public interface BeanNameGenerator {

	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}

 

这个接口有2个实现类

  • AnnotationBeanNameGenerator:指定@Component体系注解的beanName生成方式
  • DefaultBeanNameGenerator:指定其它少数特殊情况下的beanName生成方式

@Bean比较特殊,没有使用以上2种方式,而是:取name或value属性指定的beanName,缺省时默认取方法名。

 

AnnotationBeanNameGenerator

generateBeanName()方法

@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
	//如果是@Component体系注解定义的bean,则先取value属性指定的beanName
	if (definition instanceof AnnotatedBeanDefinition) {
		String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
		//如果值不为空串,直接作为beanName返回
		if (StringUtils.hasText(beanName)) {
			return beanName;
		}
	}
	//否则调用 buildDefaultBeanName() 生成一个唯一的、默认的beanName
	return buildDefaultBeanName(definition, registry);
}

DefaultBeanNameGenerator 是少数特殊情况下的beanName生成方式,@Bean也比较特殊,平时说的beanName生成方式通常指的是AnnotationBeanNameGenerator,beanName的默认生成策略通常指的是 上面的 buildDefaultBeanName() 方法,而非 DefaultBeanNameGenerator 类。

 

buildDefaultBeanName() beanName的默认生成策略

protected String buildDefaultBeanName(BeanDefinition definition) {
	//获取全限定类名
	String beanClassName = definition.getBeanClassName();
	Assert.state(beanClassName != null, "No bean class name set");
	//获取不包含包名的类名
	String shortClassName = ClassUtils.getShortName(beanClassName);
	//调用 Introspector 的静态方法 decapitalize() 生成beanName
	return Introspector.decapitalize(shortClassName);
}
public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    //如果类名的前2个字母都是大写,直接取类名作为beanName,eg. VXOrder => VXOrder
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
        return name;
    }
    //否则取类名的camel写法作为beanName,eg. GoodsInfo => goodsInfo
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

没有指定beanName,类名的前2个字母都是大写,此时采用byName方式注入该bean容易出错,因为默认的beanName并不是类名的camel写法,最好换成byType方式注入。

 

DefaultBeanNameGenerator

实质是调用 BeanDefinitionReaderUtils 的静态方法 generateBeanName()

public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
		throws BeanDefinitionStoreException {

	//全限定类名
	String generatedBeanName = definition.getBeanClassName();
	//如果全限定类名为null
	if (generatedBeanName == null) {
		//如果parentName不为空,则取 parentName + "$child"
		if (definition.getParentName() != null) {
			generatedBeanName = definition.getParentName() + "$child";
		}
		//如果使用的FactoryBeanName不为空,则取 factoryBeanName + "$created"
		else if (definition.getFactoryBeanName() != null) {
			generatedBeanName = definition.getFactoryBeanName() + "$created";
		}
	}
	if (!StringUtils.hasText(generatedBeanName)) {
		throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " +
				"'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
	}

	//如果是inner bean,会拼接:# + 对象标识的hashCode的hex字符串 的后缀作为beanName返回
	if (isInnerBean) {
		// Inner bean: generate identity hashcode suffix.
		// Integer.toHexString(System.identityHashCode(obj))
		return generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
	}

	// Top-level bean: use plain class name with unique suffix if necessary.
	//否则拼接:# + 递增的自然数 的后缀作为beanName返回,eg. #0、#1、#2
	return uniqueBeanName(generatedBeanName, registry);
}


public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {
	String id = beanName;
	int counter = -1;

	// Increase counter until the id is unique.
	String prefix = beanName + GENERATED_BEAN_NAME_SEPARATOR;
	while (counter == -1 || registry.containsBeanDefinition(id)) {
		counter++;
		id = prefix + counter;
	}
	return id;
}

 

创建bean实例的2种方式

这里说的创建bean实例,是指doCreateBean()中的createBeanInstance()方法,发生在 populateBean() 填充属性之前。
 

创建bean实例的2种方式

  • 构造方法:通过反射调用对应的构造方法创建实例,bean对应的java类需要提供对应的构造方法
  • 工厂方法:调用工厂类的工厂方法获取目标实例。根据工厂方法的类型,可细分为静态工厂、实例工厂,也可以说总的有3种方式。
     

构造方法方式只能获取新创建的实例,工厂方法的优点在于灵活,可以获取新创建的实例,也可以返回已存在的实例。

构造方法往往很简短,只做一些简单操作,构造方法方式适用于创建过程简单的情况;如果创建实例的过程很复杂,或者不是通过创建的方式获取实例,则可以使用工厂方法方式。

 

构造方法方式

bean对应的java类需要提供对应的构造方法

<!--无参构造器-->
<bean name = "userService" class = "com.chy.mall.service.impl.UserServiceImpl" />

<!--带参构造器-->
<bean name="userService" class="com.chy.mall.service.impl.UserServiceImpl">
    <constructor-arg name="userMapper" ref="userMapper"/>
    <constructor-arg name="xxx" value="xxx"/>
</bean>

<!--无参构造器+setter方法,createBeanInstance()创建实例时调用无参构造器,populateBean()填充属性时调用setter方法注入property属性设置的值-->
<bean name="userService" class="com.chy.mall.service.impl.UserServiceImpl">
    <property name="userMapper" ref="userMapper"/>
    <property name="xxx" value="xxx"/>
</bean>

 

工厂方法方式
静态工厂
//工厂类,工厂类本身无需实例化
public class UserServiceFactory {

	//提供一个获取目标实例的静态方法
    public static UserService createUserService() {
        return new UserServiceImpl();
    }

}
<!--指定工厂类、要使用的工厂方法。如果是带参的工厂方法,用constructor-arg子元素注入实参-->
<bean name="userService" class="com.chy.mall.user.UserServiceFactory" factory-method="createUserService" />

 

实例工厂
//工厂类
public class UserServiceFactory {

	//提供一个获取目标实例的实例方法
    public UserService createUserService() {
        return new UserServiceImpl();
    }

}
<!--工厂类本身需要实例化-->
<bean name="userServiceFactory" class="com.chy.mall.user.UserServiceFactory"/>

<!--指定要使用的工厂bean、工厂方法。如果是带参的工厂方法,用constructor-arg子元素注入实参-->
<bean name="userService" factory-bean="userServiceFactory" factory-method="createUserService" />

 

FactoryBean

FactoryBean本质也是实例工厂方式,默认使用 getObject() 作为工厂方法,可以借助 FactoryBean 来实现实例工厂

@Component  //FactoryBean需要实例化放到容器中,泛型指定目标类型
public class UserServiceFactoryBean implements FactoryBean<UserService> {

    /**
     * 重写 getObject(),返回目标实例
     */
    @Override
    public UserService getObject() throws Exception {
        return new UserServiceImpl();
    }

    /**
     * 重写 getObjectType(),返回目标类的Class对象
     */
    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }

    /**
     * isSingleton()用于设置目标bean的scope,默认实现是true——singleton
     * 如果要设置为prototype,可以重写此方法返回false
     */
    @Override
    public boolean isSingleton() {
        return false;
    }

}

 

单例bean的线程安全问题

镜像问题

  • spring中的bean是线程安全的吗?
  • 如何处理bean在线程并发时的线程安全问题
  • 如何保证 Controller 的并发安全?
     

原型模式每次获取都是创建新的实例,本身不存在线程安全问题;单例模式只有一个实例,多线程同时操作单例bean的实例时,可能发生线程安全问题。开发者平时很少关注单例bean的线程安全问题,因为很少使用多线程去操作单例。
 

单例bean的线程安全问题的解决方案

操作单例之前先加同步锁、把单例模式改成原型模式,这2种方式对性能影响大,基本不用。

导致单例线程不安全的原因往往是单例类中使用了成员变量来表示状态,常用以下2种方案

  • 无状态化,移除单例中表示状态的成员变量
  • 用ThreadLocal来存储表示状态的成员变量

 

标注bean的注解

标注在类上

  • @Component:将类标识为普通的bean
  • @Controller:将类标识为控制器
  • @Service:将类标识为业务层的bean
  • @Repository:将类标识为数据访问层的bean
  • @Configuration:将类标识为配置类的bean,以上几个注解本身都包含了 @Component ,都是在@Component的基础上实现的
  • @RestController:这个是spring-web的注解,等于 Controller + @ResponseBody
//都可以用value属性指定bean的名称
@Controller("userController")

 

标注在方法上

  • @Bean:把方法返回的对象作为bean实例放到spring容器中
//相当于<bean>标签,可以指定bean的名称、初始化方法、销毁方法
//name、value属性互为别名,都可以用于指定bean的名称
@Bean(name = "xxx", initMethod = "init", destroyMethod = "destroy")

需要注意的是,@Bean所在的类也要作为bean放到spring容器中,这样@Bean才会被扫描、才会生效,所以通常把@Bean抽出来,放在单独的配置配类中,用@Configuration标注配置类。

 

@Component 、@Bean的区别

  • @Component:标注在类上,把类标识为bean
  • @Bean:标在方法上,把方法返回的对象作为bean实例放到spring容器中

 

组件扫描

组件扫描:扫描指定包下标识为bean的注解,使之生效。

<!--开启注解配置-->
<context:annotation-config />

<!--开启组件扫描,指定要扫描的包,会自动扫描这些包中的标识为bean的注解-->
<context:component-scan base-package="com.chy.mall" />

 

springboot的@SpringBootApplication已经包含了@ComponentScan,但默认只扫描引导类的所在包,如果引导类位置比较特殊、不在根包下,可在引导类上用@ComponentScan指定要扫描的包

//值是string[]
@ComponentScan(basePackages = {"com.xxx.xxx1", "com.xxx.xxx2"})

//注解特性:值是只有一个元素的数组时,数组可以直接写成元素形式
@ComponentScan(basePackages = "com.xxx")

//basePackages的别名是value,注解特性:只设置value属性时,可以直接写值
@ComponentScan("com.xxx")

 

依赖注入的几种方式

  • 构造方法注入 | 构造器注入:调用构造方法进行注入,实参可通过 constructor-ar 子元素传入
  • setter方法注入 | 设值注入:调用无参构造器创建实例,再调用setter方法注入所需的属性(成员字段),属性可通过property子元素设置
  • 工厂方法注入:通过工厂方法进行注入,可以细分为静态工厂方法注入、实例工厂方法注入,实参可通过 constructor-arg 子元素传入

构造方法可以一次性注入多个依赖,setter方法一次只能注入一个依赖;推荐用构造方法注入大体依赖,后续需要补充、覆盖的少量依赖用 setter 方法进行注入;如果注入过程比较复杂,需要对参数进行一系列处理,可以使用工厂方法进行注入。

 

自动装配

自动装配的注解
  • @Autowired:按类型自动装配(byType),required属性默认值为true,要求spring容器中必须要有指定类型的实例(即不允许为null)
  • @Qualifier:和@Autowired搭配使用,用于注入指定类型、指定名称的实例,@Autowired限定bean的类型,@Qualifier限定bean的名称
  • @Resource:这是jdk自带的注解,可指定装配方式,未指定装配方式时默认先按名称装配(byName),如果找不到指定名称的bean则按类型装配(byType)
  • @Value:只能注入基本类型、String,支持SpEL(spring表达式语言,${表达式})。这个不是自动装配的注解,只是在这里顺便提一下。
@Autowired(required = false)  //默认required=true 容器中必需要有该类型的bean。


@Autowired
@Qualifier("xxx")  //必须要用value属性指定要注入的bean的beanName



//可指定装配方式,未指定时先按byName方式装配,如果容器中找不到指定名称的bean,则按byType方式装配
@Resource(name = "xxx")  
@Resource(type = Xxx.class)  
@Resource  


@Value("1")  //值是String形式
private int xxx;

这4个都可以标注在成员变量、setter方法上,@Autowired还可以标注在构造方法上。

标注在字段上时,叫做字段注入 field injection,spring官方不推荐使用字段注入,但实际很常用。字段注入实际是通过 BeanPostProcessor 完成的,比如@Autowired 注解的实现类是 AutowiredAnnotationBeanPostProcessor。

@Resource 是jdk提供的注入注解,是注入规范,与容器无关,由容器自行实现。更推荐使用 @Resource 进行注入,这样更换容器时无需调整注入注解。

 

注入多个bean实例

//声明为bean数组、集合,会注解该接口、类的所有实现
@Resource
private PrizeService[] prizeServices;
  • 如果是数组、列表这种有序的,可以在实现类上用 @Order(n) 指定注入顺序,数值越小,优先级越高,越先注入。
  • 如果接口、类上使用了泛型,可以指定泛型,这样只注入该类型泛型的实现类
public interface PrizeService<T> {
    T sendPrize();
}


@Service
public class VipPrizeServiceImpl implements PrizeService<String> {

    @Override
    public String sendPrize() {
        return null;
    }

}
@Resource
private PrizeService<String>[] prizeServices;

 

问题答疑

可以注入null 、空串吗?

可以

 

@Qualifier有什么作用?

与@Autowired搭配使用,注入指定类型、指定名称的实例

 

@Autowired、@Resource有什么区别?

  • @Autowired是按byType方式进行装配,required 属性默认为true,要求spring容器中必须要有依赖实例,可以设置为false,设置为false时注入的是null。可以与@Qualifier搭配使用,可以注入指定类型、指定名称的实例。
  • @Resource可以指定装配方式,未指定时先按byName方式装配,如果容器中找不到指定名称的bean,再按byType方式装配

 

@Autowired的实现原理

@Value、@Autowired 的实现原理都是一样的,都是通过特殊的bean后置处理器 AutowiredAnnotationBeanPostProcessor 进行解析、处理。
 

1、spring容器启动时

AbstractApplicationContext 的 refresh() 方法会注册所有的 BeanPostProcessor,其中就包括 AutowiredAnnotationBeanPostProcessor

registerBeanPostProcessors(beanFactory);

 

2、创建bean实例的属性填充阶段

AbstractAutowireCapableBeanFactory 的 ceateBean() -> doCreateBean() -> populateBean() 末尾处代码

boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

PropertyDescriptor[] filteredPds = null;
if (hasInstAwareBpps) {
	if (pvs == null) {
		pvs = mbd.getPropertyValues();
	}
	//遍历所有的 InstantiationAwareBeanPostProcessor,这个BeanPostProcessor 接口用于添加实例化前回调,以及实例化后但在设置显式属性或自动装配之前的回调
	//AutowiredAnnotationBeanPostProcessor 实现类 -> SmartInstantiationAwareBeanPostProcessor 接口 ->  InstantiationAwareBeanPostProcessor 接口,也是该接口的实例
	for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
		//处理 InstantiationAwareBeanPostProcessor 方式的注入
		PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
		if (pvsToUse == null) {
			if (filteredPds == null) {
				filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			}
			pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
			if (pvsToUse == null) {
				return;
			}
		}
		pvs = pvsToUse;
	}
}
if (needsDepCheck) {
	if (filteredPds == null) {
		filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
	}
	checkDependencies(beanName, mbd, filteredPds, pvs);
}

if (pvs != null) {
	applyPropertyValues(beanName, mbd, bw, pvs);
}

 

3、AutowiredAnnotationBeanPostProcessor 的 postProcessProperties()

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	//提取注解的注入元数据
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
		//进行属性注入
		metadata.inject(bean, beanName, pvs);
	}
	catch (BeanCreationException ex) {
		throw ex;
	}
	catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
	}
	return pvs;
}

 

4、InjectionMetadata 的 inject()

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		//遍历需要注入的属性|元素,进行注入
		for (InjectedElement element : elementsToIterate) {
			//实质是调用静态内部类 InjectedElement 的inject()完成单个属性的注入
			element.inject(target, beanName, pvs);
		}
	}
}

 

5、InjectedElement 的 inject()

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
				throws Throwable {

	//判断是字段注入还是方法注入(标注在字段上、还是方法上)
	//字段注入
	if (this.isField) {
		Field field = (Field) this.member;
		//允许访问该字段
		ReflectionUtils.makeAccessible(field);
		//通过反射直接给该字段赋值。此处使用的set()方法是Field的方法,不是我们自己写的setter方法
		field.set(target, getResourceToInject(target, requestingBeanName));
	}
	else {
		if (checkPropertySkipping(pvs)) {
			return;
		}
		try {
			Method method = (Method) this.member;
			//允许访问该方法
			ReflectionUtils.makeAccessible(method);
			//通过反射调用该方法注入属性
			method.invoke(target, getResourceToInject(target, requestingBeanName));
		}
		catch (InvocationTargetException ex) {
			throw ex.getTargetException();
		}
	}
}

 

总结

@Value、@Autowired 都是通过特殊的bean后置处理器 AutowiredAnnotationBeanPostProcessor 实现的,

在高级容器调用 refresh() 刷新时 会注册所有的 BeanPostProcessor,其中包括 AutowiredAnnotationBeanPostProcessor;

在创建bean实例的属性填充阶段,会执行AutowiredAnnotationBeanPostProcessor 的 postProcessProperties() 方法,提取注解的注入元信息为 InjectionMetadata,调用 InjectionMetadata 的 inject() 注入@Value、@Autowired 属性;

inject() 实质是通过反射进行注入,字段注入通过 Field 的 set() 方法直接设置属性值,方法注入通过反射直接调用对应的方法进行注入。

 

自动装配的贪婪原则

自动装配会尽可能多的注入依赖,setter注入方式会注入尽可能多的依赖,构造器注入方式会优先使用依赖个数多的构造器。

 

自动装配常见报错

找不到对应的bean

常见原因

  • 没有放到spring容器中,比如类上没有标注@Component、方法上没有标注@Bean;
  • 没有开启组件扫描,或者组件扫描指定的包不全,springboot应用可以检查引导类位置、@ComponentScan配置
  • 使用的byName方式注入,检查beanName是否一致

 

存在多个可用实例

常见原因:采用byTyp模式进行注入,依赖的类型有多个实例

常见的2种解决方式

  • 改为byName方式注入
  • 标注依赖的bean时,额外使用@Primary标注首选的bean,注入该类型的实例时优先注入@Primary标注的实例,多数据源即使用此种方式实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值