Spring 中 Bean 的生命周期

本文在介绍Spring中Bean的生命周期之前,先探讨下Bean对象的初始化和实例化概念

初始化和实例化的介绍

初始化条件

  • 遇到new,getstatic,putstatic,或invokestatic这4条字节码指令时

  • 使用java.lang.reflect包的方法对类进行反射调用时

  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先出法其父类的初始化

  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类

初始化和实例化的关系

在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化

初始化过程

代码演示

父类

public class Parent {
	private static int  a ;
	private static int b =1;
	private int c ;
	private int d = 1;

	static {
		System.out.println("(父类)"+"a静态成员变量初始化值"+a+"(静态代码块)");
		++a;
		System.out.println("(父类)"+"a静态成员变量赋值后"+a+"(静态代码块)");
		System.out.println("(父类)"+"b静态成员变量初始化值"+b+"(静态代码块)");
		++b;
		System.out.println("(父类)"+"b静态成员变量赋值后"+b+"(静态代码块)");
	}

	{
		System.out.println("(父类)"+"c私有成员变量初始化值"+c+"代码块");
		++c;
		System.out.println("(父类)"+"c私有成员变量赋值后"+c+"代码块");
		System.out.println("(父类)"+"d私有成员变量初始化值"+d+"代码块");
		++d;
		System.out.println("(父类)"+"d私有成员变量赋值后"+d+"代码块");
	}

	public Parent() {
		System.out.println("父类无参构造方法");
	}

	public Parent(int c, int d) {
		System.out.println("父类有参构造方法");
		this.c = c;
		this.d = d;
	}

	public static int getA() {
		return a;
	}

	public static void setA(int a) {
		System.out.println("父类 a变量set方法");
		Parent.a = a;
	}

	public static int getB() {
		return b;
	}

	public static void setB(int b) {
		System.out.println("父类 b变量set方法");
		Parent.b = b;
	}

	public int getC() {
		return c;
	}

	public void setC(int c) {
		System.out.println("父类 c变量set方法");
		this.c = c;
	}

	public int getD() {
		return d;
	}

	public void setD(int d) {
		System.out.println("父类 d变量set方法");
		this.d = d;
	}


	public void show() {

		System.out.println("父类show方法"+ "Parent{" +
				"c=" + c +
				", d=" + d +
				'}');
	}
}

子类

public class Children extends Parent {

	private static int  cA ;
	private static int cB =1;
	private int cC ;
	private int cD = 1;

	private static final String WORD = "hello,java";

	static {
		System.out.println("(子类)"+"cA静态成员变量初始化值"+cA+"(静态代码块)");
		++cA;
		System.out.println("(子类)"+"cA静态成员变量赋值后"+cA+"(静态代码块)");
		System.out.println("(子类)"+"cB静态成员变量初始化值"+cB+"(静态代码块)");
		++cB;
		System.out.println("(子类)"+"cB静态成员变量赋值后"+cB+"(静态代码块)");
	}

	{
		System.out.println("(子类)"+"cC私有成员变量初始化值"+cC+"代码块");
		++cC;
		System.out.println("(子类)"+"cC私有成员变量赋值后"+cC+"代码块");
		System.out.println("(子类)"+"cD私有成员变量初始化值"+cD+"代码块");
		++cD;
		System.out.println("(子类)"+"cD私有成员变量赋值后"+cD+"代码块");
	}

	public Children() {
		System.out.println("子类无参构造方法");
	}

	public Children(int cC, int cD) {
		System.out.println("子类有参构造方法");
		this.cC = cC;
		this.cD = cD;
	}

	public static int getcA() {
		return cA;
	}

	public static void setcA(int cA) {
		System.out.println("子类 cA变量set方法");
		Children.cA = cA;
	}

	public static int getcB() {
		return cB;
	}

	public static void setcB(int cB) {
		System.out.println("子类 cB变量set方法");
		Children.cB = cB;
	}

	public int getcC() {
		return cC;
	}

	public void setcC(int cC) {
		System.out.println("子类 cC变量set方法");
		this.cC = cC;
	}

	public int getcD() {
		return cD;
	}

	public void setcD(int cD) {
		System.out.println("子类 cD变量set方法");
		this.cD = cD;
	}


	public void chilShow() {
		System.out.println("子类show方法"+ "Children{" +
				"cC=" + cC +
				", cD=" + cD +
				'}');
	}
}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest
public class BeanlifeApplicationTests {

	[@Test](https://my.oschina.net/azibug)
	public void test1() {
		Children children1 = new Children();
		System.out.println("+++++++++++++++++++++第二个对象");
		Children children2 = new Children();
		children1.show();
		children1.chilShow();
	}
	[@Test](https://my.oschina.net/azibug)
	public void test2() {
	}
}

输出信息

可以发现在初始化阶段: 父类的静态变量和静态块——>子类的静态块和静态变量——>父类的实例变量和父类的普通块——>父类的构造器——>子类的实例变量和普通块——>子类的构造器

初始化和实例化总结

换言之:类的初始化是指类加载过程中的初始化阶段对类变量按照指定程序的意图进行赋值的过程;而类的实例化是指在类完全加载到内存中后创建对象的过程。但并不意味着:只有类初始化操作结束后才能进行类实例化操作。

初始化和实例化是两个过程,初始化只执行一次,而实例化可以多次执行

Spring中Bean的生命周期

Bean的生命周期中核心接口

  • BeanFactoryPostProcessor
  • BeanPostProcessor(后置处理器)
  • InstantiationAwareBeanPostProcessor
  • BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean
  • init-method,destroy-method

① BeanFactoryPostProcessor

代码演示

Student Bean类

public class Student {
	private Integer id;
	private String name;
	public Student() {
		System.out.println("Student实例化");
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		System.out.println("Student注入Name属性:"+name);
		this.name = name;
	}

	public void init() {
		System.out.println("Student自定义初始化方法");
	}

	public void destroy() {
		System.out.println("Student自定义销毁方法");
	}
	[@Override](https://my.oschina.net/u/1162528)
	public String toString() {
		return "Student{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
	{
		System.out.println("代码块");
	}
	static {
		System.out.println("静态");
	}
}

自定义 MyBeanFactoryPostProcessor

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
		System.out.println("BeanFactoryPostProcessor开启++++++");
		Iterator<String> iterator = configurableListableBeanFactory.getBeanNamesIterator();
		while (iterator.hasNext()) {
			String next = iterator.next();
			System.out.println(next);
		}
		System.out.println("BeanFactoryPostProcessor结束++++++");
	}
}

注册 applicationContext.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean class="com.tcwong.Student" init-method="init" destroy-method="destroy" scope="prototype">
		<property name="name" value="Lee"/>
	</bean>
	<bean class="com.tcwong.MyBeanFactoryPostProcessor"/>
</beans>

测试类

public class Test {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		Student student1 = context.getBean(Student.class);
		context.registerShutdownHook();
		System.out.println("容器关闭后"+student1);
	}
}

控制台输出

可以看出Spring是在Bean实例化之前执行BeanFactoryPostProcessor的方法postProcessBeanFactory(..)。同时,postProcessBeanFactory(..)也会加载Springrng容器运行环境相关类。

修改MyBeanFactoryPostProcessor中的方法postProcessBeanFactory(..)

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
		System.out.println("BeanFactoryPostProcessor开启++++++");
		Iterator<String> iterator = configurableListableBeanFactory.getBeanNamesIterator();
		while (iterator.hasNext()) {
			String next = iterator.next();
			System.out.println(next);
			if ("com.tcwong.Student#0".equals(next)) {
				BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(next);
				MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
				if (propertyValues.contains("name")) {
					propertyValues.addPropertyValue("name", "susu");
				}
			}
		}
		System.out.println("BeanFactoryPostProcessor结束++++++");
	}
}

可以看到

在BeanFactoryPostProcessor中的postProcessBeanFactory(..)中还可以定义Bean指定属性值

② BeanPostProcessor(后置处理器)

代码演示

自定义 MyBeanPostProcessor

public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("BeanPostProcessor中-----"+"postProcessBeforeInitialization方法-----"+"bean:"+bean+",beanName:"+beanName);
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("BeanPostProcessor中-----"+"postProcessAfterInitialization方法-----"+"bean:"+bean+",beanName:"+beanName);
		return bean;
	}
}

注册 applicationContext.xml

<bean class="com.tcwong.MyBeanPostProcessor"/>

控制台输出

可以看出,在BeanPostProcessor中有两个方法,

  • postProcessBeforeInitialization(..):实例化、依赖注入完毕, 在调用显示的初始化之前完成一些定制的初始化任务

  • postProcessAfterInitialization(..):实例化、依赖注入、初始化完毕时执行

③ InstantiationAwareBeanPostProcessor

从源码可以看到

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	/**
	 * Apply this BeanPostProcessor <i>before the target bean gets instantiated</i>.
	 * The returned bean object may be a proxy to use instead of the target bean,
	 * effectively suppressing default instantiation of the target bean.
	 * <p>If a non-null object is returned by this method, the bean creation process
	 * will be short-circuited. The only further processing applied is the
	 * {@link #postProcessAfterInitialization} callback from the configured
	 * {@link BeanPostProcessor BeanPostProcessors}.
	 * <p>This callback will only be applied to bean definitions with a bean class.
	 * In particular, it will not be applied to beans with a factory method.
	 * <p>Post-processors may implement the extended
	 * {@link SmartInstantiationAwareBeanPostProcessor} interface in order
	 * to predict the type of the bean object that they are going to return here.
	 * <p>The default implementation returns {@code null}.
	 * @param beanClass the class of the bean to be instantiated
	 * @param beanName the name of the bean
	 * @return the bean object to expose instead of a default instance of the target bean,
	 * or {@code null} to proceed with default instantiation
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see #postProcessAfterInstantiation
	 * @see org.springframework.beans.factory.support.AbstractBeanDefinition#hasBeanClass
	 */
	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	/**
	 * Perform operations after the bean has been instantiated, via a constructor or factory method,
	 * but before Spring property population (from explicit properties or autowiring) occurs.
	 * <p>This is the ideal callback for performing custom field injection on the given bean
	 * instance, right before Spring's autowiring kicks in.
	 * <p>The default implementation returns {@code true}.
	 * @param bean the bean instance created, with properties not having been set yet
	 * @param beanName the name of the bean
	 * @return {@code true} if properties should be set on the bean; {@code false}
	 * if property population should be skipped. Normal implementations should return {@code true}.
	 * Returning {@code false} will also prevent any subsequent InstantiationAwareBeanPostProcessor
	 * instances being invoked on this bean instance.
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @see #postProcessBeforeInstantiation
	 */
	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		return true;
	}

	/**
	 * Post-process the given property values before the factory applies them
	 * to the given bean, without any need for property descriptors.
	 * <p>Implementations should return {@code null} (the default) if they provide a custom
	 * {@link #postProcessPropertyValues} implementation, and {@code pvs} otherwise.
	 * In a future version of this interface (with {@link #postProcessPropertyValues} removed),
	 * the default implementation will return the given {@code pvs} as-is directly.
	 * @param pvs the property values that the factory is about to apply (never {@code null})
	 * @param bean the bean instance created, but whose properties have not yet been set
	 * @param beanName the name of the bean
	 * @return the actual property values to apply to the given bean (can be the passed-in
	 * PropertyValues instance), or {@code null} which proceeds with the existing properties
	 * but specifically continues with a call to {@link #postProcessPropertyValues}
	 * (requiring initialized {@code PropertyDescriptor}s for the current bean class)
	 * @throws org.springframework.beans.BeansException in case of errors
	 * @since 5.1
	 * @see #postProcessPropertyValues
	 */
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {

		return null;
	}

InstantiationAwareBeanPostProcessor是BeanPostProcessor的子接口,除了继承父接口的postProcessBeforeInitialization(..)和postProcessAfterInitialization(..)外,还有自定义的的 postProcessBeforeInstantiation(..)、postProcessAfterInstantiation(..)、postProcessProperties(..)

代码演示

自定义 MyInstantiationAwareBeanPostProcessor

public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
	@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInstantiation(实例化)-------"+beanName);
		return null;
	}

	@Override
	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInstantiation(实例化)-------"+beanName);
		return false;
	}

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
		System.out.println("postProcessProperties-------"+beanName);
		return null;
	}

	@Override
	public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
		System.out.println("postProcessPropertyValues------"+beanName);
		return null;
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInitialization(初始化)--------"+beanName);
		return null;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInitialization(初始化)-------"+beanName);
		return null;
	}
}

注册applicationContext.xml

<bean class="com.tcwong.MyInstantiationAwareBeanPostProcessor"/>

控制台输出

可以看出,

  • postProcessBeforeInstantiation(..):自身方法,是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。
  • postProcessAfterInstantiation(..): 在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置。
  • postProcessBeforeInitialization(..):在自定义初始化方法前执行
  • postProcessAfterInitialization(..):在自定义初始化方法后执行

修改postProcessProperties(..)方法,并修改postProcessAfterInstantiation(..)返回值为true,

 @Override
	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInstantiation(实例化)-------"+beanName);
		return true;
	}

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
		System.out.println("postProcessProperties-------"+beanName);
		if(bean instanceof Student){
			PropertyValue value = pvs.getPropertyValue("name");
			System.out.println("修改前name的值是:"+value.getValue());
			value.setConvertedValue("kang");
		}
		return pvs;
	}

可以看到:

  • 当postProcessAfterInstantiation(..)返回值为true,postProcessPropertyValues(..)就会在postProcessAfterInstantiation(..)后执行,并设置相关属性值

修改方法 postProcessBeforeInstantiation(..),增加返回Bean对象

 public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInstantiation(实例化)-------"+beanName);
		if (beanClass == Student.class) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(beanClass);
			enhancer.setCallback(new MethodInterceptor() {
				@Override
				public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
					System.out.println("postProcessBeforeInstantiation(实例化)代理前"+method);
					Object object = methodProxy.invokeSuper(o, objects);
					System.out.println("postProcessBeforeInstantiation(实例化)代理后"+method);
					return object;
				}
			});
			Student student = (Student)enhancer.create();
			return student;
		}
		return null;
	}

可以看出

当postProcessBeforeInstantiation(..)返回实例对象时,postProcessAfterInstantiation(..)及Bean自定义初始化,依赖注入便不再执行,直接执行postProcessAfterInitialization(..)初始化完成。

查看源码发现

@Nullable
	protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
		Object bean = null;
		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
			// Make sure bean class is actually resolved at this point.
			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
				Class<?> targetType = determineTargetType(beanName, mbd);
				if (targetType != null) {
					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
					if (bean != null) {
						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
					}
				}
			}
			mbd.beforeInstantiationResolved = (bean != null);
		}
		return bean;
	}

	/**
	 * Apply InstantiationAwareBeanPostProcessors to the specified bean definition
	 * (by class and name), invoking their {@code postProcessBeforeInstantiation} methods.
	 * <p>Any returned object will be used as the bean instead of actually instantiating
	 * the target bean. A {@code null} return value from the post-processor will
	 * result in the target bean being instantiated.
	 * @param beanClass the class of the bean to be instantiated
	 * @param beanName the name of the bean
	 * @return the bean object to use instead of a default instance of the target bean, or {@code null}
	 * @see InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation
	 */
	@Nullable
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}
	


	@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}
  • 如果postProcessBeforeInstantiation返回不为null,会执行postProcessAfterInitialization方法,Spring容器便不会再调用doCreateBean方法()生成Bean对象了。

④ BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean

代码演示

分别实现 BeanNameAware, BeanFactoryAware , InitializingBean , DisposableBean 接口

public class Student implements BeanNameAware, BeanFactoryAware , InitializingBean , DisposableBean {
	private Integer id;
	private String name;
	private String beanName;
	private BeanFactory beanFactory;

	public String getBeanName() {
		return beanName;
	}

	public BeanFactory getBeanFactory() {
		return beanFactory;
	}

	public Student() {
		System.out.println("Student实例化");
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		System.out.println("Student注入Name属性:"+name);
		this.name = name;
	}

	public void init() {
		System.out.println("Student自定义初始化方法");
	}


	@Override
	public String toString() {
		return "Student{" +
				"id=" + id +
				", name='" + name + '\'' +
				'}';
	}
	{
		System.out.println("代码块");
	}
	static {
		System.out.println("静态");
	}

	@Override
	public void setBeanName(String name) {
		System.out.println("BeanNameAware中setBeanName()方法:");
		this.beanName = name;
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		System.out.println("BeanFactoryAware中setBeanFactory()方法:");
		this.beanFactory = beanFactory;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("InitializingBean中afterPropertiesSet()方法:");
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("Disposable中Beandestroy()方法");
	}

	public void myDestroy() {
		System.out.println("自定义destroy-method");
	}

}

可以看到,

  • BeanNameAware, BeanFactoryAware 在属性注入时分别执行setBeanName()、setBeanFactory()方法,

  • InitializingBean 在postProcessBeforeInitialization后执行afterPropertiesSet

  • DisposableBean在Bean销毁时,执行Beandestroy(),并在自定义destroy-method 前执行

注意:

当设置Bean的作用域为prototype时,Spring容器不能够对Bean的整个生命周期进行管理,最终对象的销毁和资源回收由客户端决定。而singleton的整个生命周期由Spring管理。

Spring中Bean生命周期总结

生命周期结构图

  1. Spring 容器启动初始化的时候,会先实例化BeanFactoryPostProcessor,调用其postProcessBeanFactory(),并可以修改Bean 属性。该方法在实例化Bean对象之前执行。

  2. 执行 InstantiationAwareBeanPostProcessor 中 postProcessBeforeInstantiation()方法,该方法如果返回的不为null则会直接调用postProcessAfterInitialization()方法,而跳过了Bean实例化后及初始化前的相关方法。postProcessAfterInstantiation()方法会在 对象实例化后执行,并根据返回true 或者false 来决定是否执行postProcessProperties()方法对Bean属性赋值。

  3. Bean 对象属性注入,调用BeanNameAware中setBeanName()方法和BeanFactoryAware中setBeanFactory()方法。

  4. 执行 BeanPostProcessor 中的 postProcessBeforeInitialization()方法,此时可以可以根据Bean参数,通过反射操作Bean对象

  5. 执行BeanPostProcessor子类InstantiationAwareBeanPostProcessor中的postProcessBeforeInitialization()方法

  6. InitializingBean中afterPropertiesSet()方法

  7. 执行Bean 自定义init-method 中的方法

  8. 执行 BeanPostProcessor中的postProcessAfterInitialization()方法 。

  9. 执行BeanPostProcessor子类InstantiationAwareBeanPostProcessor中的postProcessAfterInitialization()方法。

  10. Bean对象实例化完成

  11. 对象销毁

  12. 执行 Disposable中Beandestroy()方法

  13. 执行Bean自定义destroy-method 中的方法

转载于:https://my.oschina.net/tcwong/blog/3060471

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值