stringutils 用哪个包 apache spring_Spring(二): DI的概念,手写DI

DI(Dependency injection)依赖注入

哪些地方会有依赖?

构造参数依赖
属性依赖

依赖注入的本质是什么?

给值:给入构造参数值,给属性赋值

参数值、属性值可能是什么值?

直接值、bean依赖

直接值会有哪几种情形?

基本数据类型值、String
数组、集合
Properties
Map

本质:参数值、属性值都是值。Bean工厂在进行依赖注入时,就是给入值

把值给到bean工厂,bean工厂就可以帮住赋值了。


如何告诉bean工厂该给如什么构造参数值?即如何来定义参数依赖?

如何来定义属性依赖?


DI依赖注入-构造参数依赖定义分析

如何定义构造参数依赖?

假如
public class Girl{ 
public Girl(String name,int age,char cup,Boy boyfriend){ 
... 
} 
... 
}

要创建一个Girl是如何创建的?直接给入就行了

Boy tom = new Boy("Tom");
Girl beautGirl = new Girl("小丽",18,'D',tom ); 

怎么定义构造参数依赖?

第一个参数值是:“小丽”
第一个参数值是:18
第一个参数值是:‘D’
第一个参数值是:依赖一个Boy bean

参数可以多个,用什么存储

集合:List

参数有顺序,如何体现顺序?

按照参数顺序放入List

参数值可以是直接值,也可以是bean依赖,如何表示?

Object

如果用Object来表示值,如何区分是Bean依赖?

为Bean依赖定义一种数据类型(BeanReference),bean工厂在构造Bean实例的时候,
遍历判断参数是否是BeanReference,如果是则替换为依赖的Bean实例

如果直接值是数组、集合等,它们的元素中有的是Bean依赖,怎么处理?

元素值还是用BeanReference,同样bean工厂在使用时需遍历替换

BeanReference该是怎样的?

BeanReference就是用来说明bean依赖的:依赖哪个bean

54c25e8c1955ce58ddd9be6be97bf42f.png

801f194443565ca9a2666b73e76cabf5.png

DI的实现:

在BeanDefinition中增加构造参数值的接口

c3dbabee50cab3b46f3f7a132acdfc19.png

在GenericBeanDefinition中增加对应这个方法的实现:

a9f994c219d0ce90a62485f7c6ea252a.png

DI实现:BeanFactory中实现构造参数依赖注入1:

首先需要把bean定义中的构造参数引用转为真实的值,在DefaultBeanFactory中增加一个方法:

7cde2fbffce49d6941a57bcae1337884.png

b80f44ab1466da833b793f1b4e6066da.png

DI实现:BeanFactory中实现构造参数依赖注入2:

有参数了,如何断定是哪个构造方法、哪个工厂方法?

判断的逻辑:先根据参数的类型进行精确匹配查找,如果未找到则进行2

获得所有的构造方法,遍历,通过参数数量过滤,再对比形参类型与实参类型

1、方法是可以重载的
2、形参定义的时候可能是接口或者父类,实参则是具体的子实现
3、反射提供的获取的构造方法、方法的API如下:

67fa1e8ad2effd94a57a2f6906b58b76.png

ee28bf7a6228275d51f6dfb9f06d0925.png

当判断出构造方法或工厂方法后,对于原型Bean,下次获取Bean的时候可以缓存下这个构造方法或工厂方法,在BeanDefinition中增加缓存的方法:

1fb98e084b47c7c51514b5e56418477a.png

在GenericBeanDefinition中增加对应的实现:

5a3e8e4996f9fb2bae5967318e0858cb.png

接下来就可以写构造方法或工厂方法的代码了

BeanDefinition的接口比上次多了几个方法:

import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

public interface BeanDefinition {
    String SCOPE_SINGLETON = "singleton";

    String SCOPE_PROTOTYPE = "prototype";

    /**
     * 类
     */
    Class<?> getBeanClass();

    /**
     * Scope
     */
    String getScope();

    /**
     * 是否单例
     */
    boolean isSingleton();

    /**
     * 是否原型
     */
    boolean isPrototype();

    /**
     * 工厂bean名
     */
    String getFactoryBeanName();

    /**
     * 工厂方法名
     */
    String getFactoryMethodName();

    /**
     * 初始化方法
     */
    String getInitMethodName();

    /**
     * 销毁方法
     */
    String getDestroyMethodName();

    /* 下面的四个方法是供beanFactory中使用的 */

    public Constructor<?> getConstructor();

    public void setConstructor(Constructor<?> constructor);

    public Method getFactoryMethod();

    public void setFactoryMethod(Method factoryMethod);

    /**
     * 校验bean定义的合法性
     */
    default boolean validate() {
        // 没定义class,工厂bean或工厂方法没指定,则不合法。
        if (this.getBeanClass() == null) {
            if (StringUtils.isBlank(getFactoryBeanName()) || StringUtils.isBlank(getFactoryMethodName())) {
                return false;
            }
        }

        // 定义了类,又定义工厂bean,不合法
        if (this.getBeanClass() != null && StringUtils.isNotBlank(getFactoryBeanName())) {
            return false;
        }

        return true;
    }

    /**
     * 获得构造参数定义 <br>
     */
    List<?> getConstructorArgumentValues();

    /**
     * 属性依赖<br>
     * @return
     */
    List<PropertyValue> getPropertyValues();

}
public class PropertyValue {
    private String name;

    private Object value;

    public PropertyValue(String name, Object value) {
        super();
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

GenericBeanDefinition是BeanDefinition的实现类,看一下这个实现类代码:

import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;

public class GenericBeanDefinition implements BeanDefinition {

    private Class<?> beanClass;

    private String scope = BeanDefinition.SCOPE_SINGLETON;

    private String factoryBeanName;

    private String factoryMethodName;

    private String initMethodName;

    private String destroyMethodName;

    private Constructor<?> constructor;

    private Method factoryMethod;

    private List<?> constructorArgumentValues;

    private List<PropertyValue> propertyValues;

    public void setBeanClass(Class<?> beanClass) {
        this.beanClass = beanClass;
    }

    public void setScope(String scope) {
        if (StringUtils.isNotBlank(scope)) {
            this.scope = scope;
        }
    }

    public void setFactoryBeanName(String factoryBeanName) {
        this.factoryBeanName = factoryBeanName;
    }

    public void setFactoryMethodName(String factoryMethodName) {
        this.factoryMethodName = factoryMethodName;
    }

    public void setInitMethodName(String initMethodName) {
        this.initMethodName = initMethodName;
    }

    public void setDestroyMethodName(String destroyMethodName) {
        this.destroyMethodName = destroyMethodName;
    }

    @Override
    public Class<?> getBeanClass() {
        return this.beanClass;
    }

    @Override
    public String getScope() {
        return this.scope;
    }

    @Override
    public boolean isSingleton() {
        return BeanDefinition.SCOPE_SINGLETON.equals(this.scope);
    }

    @Override
    public boolean isPrototype() {
        return BeanDefinition.SCOPE_PROTOTYPE.equals(this.scope);
    }

    @Override
    public String getFactoryBeanName() {
        return this.factoryBeanName;
    }

    @Override
    public String getFactoryMethodName() {
        return this.factoryMethodName;
    }

    @Override
    public String getInitMethodName() {
        return this.initMethodName;
    }

    @Override
    public String getDestroyMethodName() {
        return this.destroyMethodName;
    }

    public List<?> getConstructorArgumentValues() {
        return constructorArgumentValues;
    }

    public void setConstructorArgumentValues(List<?> constructorArgumentValues) {
        this.constructorArgumentValues = constructorArgumentValues;
    }

    public List<PropertyValue> getPropertyValues() {
        return propertyValues;
    }

    public void setPropertyValues(List<PropertyValue> propertyValues) {
        this.propertyValues = propertyValues;
    }

    public Constructor<?> getConstructor() {
        return constructor;
    }

    public void setConstructor(Constructor<?> constructor) {
        this.constructor = constructor;
    }

    public Method getFactoryMethod() {
        return factoryMethod;
    }

    public void setFactoryMethod(Method factoryMethod) {
        this.factoryMethod = factoryMethod;
    }

    @Override
    public String toString() {
        return "GenericBeanDefinition [beanClass=" + beanClass + ", scope=" + scope + ", factoryBeanName="
                + factoryBeanName + ", factoryMethodName=" + factoryMethodName + ", initMethodName=" + initMethodName
                + ", destroyMethodName=" + destroyMethodName + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((beanClass == null) ? 0 : beanClass.hashCode());
        result = prime * result + ((destroyMethodName == null) ? 0 : destroyMethodName.hashCode());
        result = prime * result + ((factoryBeanName == null) ? 0 : factoryBeanName.hashCode());
        result = prime * result + ((factoryMethodName == null) ? 0 : factoryMethodName.hashCode());
        result = prime * result + ((initMethodName == null) ? 0 : initMethodName.hashCode());
        result = prime * result + ((scope == null) ? 0 : scope.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        GenericBeanDefinition other = (GenericBeanDefinition) obj;
        if (beanClass == null) {
            if (other.beanClass != null)
                return false;
        } else if (!beanClass.equals(other.beanClass))
            return false;
        if (destroyMethodName == null) {
            if (other.destroyMethodName != null)
                return false;
        } else if (!destroyMethodName.equals(other.destroyMethodName))
            return false;
        if (factoryBeanName == null) {
            if (other.factoryBeanName != null)
                return false;
        } else if (!factoryBeanName.equals(other.factoryBeanName))
            return false;
        if (factoryMethodName == null) {
            if (other.factoryMethodName != null)
                return false;
        } else if (!factoryMethodName.equals(other.factoryMethodName))
            return false;
        if (initMethodName == null) {
            if (other.initMethodName != null)
                return false;
        } else if (!initMethodName.equals(other.initMethodName))
            return false;
        if (scope == null) {
            if (other.scope != null)
                return false;
        } else if (!scope.equals(other.scope))
            return false;
        return true;
    }

}

在DefaultBeanFactory中增加查找构造方法的方法:

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

	private final Log logger = LogFactory.getLog(getClass());

	private Map<String, BeanDefinition> beanDefintionMap = new ConcurrentHashMap<>(256);

	private Map<String, Object> beanMap = new ConcurrentHashMap<>(256);

	private ThreadLocal<Set<String>> buildingBeans = new ThreadLocal<>();

	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionRegistException {
		Objects.requireNonNull(beanName, "注册bean需要给入beanName");
		Objects.requireNonNull(beanDefinition, "注册bean需要给入beanDefinition");

		// 校验给入的bean是否合法
		if (!beanDefinition.validate()) {
			throw new BeanDefinitionRegistException("名字为[" + beanName + "] 的bean定义不合法:" + beanDefinition);
		}

		if (this.containsBeanDefinition(beanName)) {
			throw new BeanDefinitionRegistException(
					"名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));
		}

		this.beanDefintionMap.put(beanName, beanDefinition);
	}

	@Override
	public BeanDefinition getBeanDefinition(String beanName) {
		return this.beanDefintionMap.get(beanName);
	}

	@Override
	public boolean containsBeanDefinition(String beanName) {

		return this.beanDefintionMap.containsKey(beanName);
	}

	@Override
	public Object getBean(String name) throws Exception {
		return this.doGetBean(name);
	}

	protected Object doGetBean(String beanName) throws Exception {
		Objects.requireNonNull(beanName, "beanName不能为空");

		Object instance = beanMap.get(beanName);

		if (instance != null) {
			return instance;
		}

		BeanDefinition bd = this.getBeanDefinition(beanName);
		Objects.requireNonNull(bd, "不存在name为:" + beanName + "beean 定义!");

		// 记录正在创建的Bean
		Set<String> ingBeans = this.buildingBeans.get();
		if (ingBeans == null) {
			ingBeans = new HashSet<>();
			this.buildingBeans.set(ingBeans);
		}

		// 检测循环依赖
		if (ingBeans.contains(beanName)) {
			throw new Exception(beanName + " 循环依赖!" + ingBeans);
		}

		// 记录正在创建的Bean
		ingBeans.add(beanName);

		Class<?> type = bd.getBeanClass();
		if (type != null) {
			if (StringUtils.isBlank(bd.getFactoryMethodName())) {
				// 构造方法来构造对象
				instance = this.createInstanceByConstructor(bd);
			} else {
				// 静态工厂方法
				instance = this.createInstanceByStaticFactoryMethod(bd);
			}
		} else {
			// 工厂bean方式来构造对象
			instance = this.createInstanceByFactoryBean(bd);
		}

		// 创建好实例后,移除创建中记录
		ingBeans.remove(beanName);

		// 给入属性依赖
		this.setPropertyDIValues(bd, instance);

		// 执行初始化方法
		this.doInit(bd, instance);

		if (bd.isSingleton()) {
			beanMap.put(beanName, instance);
		}

		return instance;
	}

	private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception {
		if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
			return;
		}
		for (PropertyValue pv : bd.getPropertyValues()) {
			if (StringUtils.isBlank(pv.getName())) {
				continue;
			}
			Class<?> clazz = instance.getClass();
			Field p = clazz.getDeclaredField(pv.getName());

			p.setAccessible(true);

			Object rv = pv.getValue();
			Object v = null;
			if (rv == null) {
				v = null;
			} else if (rv instanceof BeanReference) {
				v = this.doGetBean(((BeanReference) rv).getBeanName());
			} else if (rv instanceof Object[]) {
				// TODO 处理集合中的bean引用
			} else if (rv instanceof Collection) {
				// TODO 处理集合中的bean引用
			} else if (rv instanceof Properties) {
				// TODO 处理properties中的bean引用
			} else if (rv instanceof Map) {
				// TODO 处理Map中的bean引用
			} else {
				v = rv;
			}

			p.set(instance, v);

		}
	}

	// 构造方法来构造对象
	private Object createInstanceByConstructor(BeanDefinition bd) throws Exception {
		try {
			Object[] args = this.getConstructorArgumentValues(bd);
			if (args == null) {
				return bd.getBeanClass().newInstance();
			} else {
				// 决定构造方法
				return this.determineConstructor(bd, args).newInstance(args);
			}
		} catch (SecurityException e1) {
			logger.error("创建bean的实例异常,beanDefinition:" + bd, e1);
			throw e1;
		}
	}

	private Object[] getConstructorArgumentValues(BeanDefinition bd) throws Exception {

		return this.getRealValues(bd.getConstructorArgumentValues());

	}

	private Object[] getRealValues(List<?> defs) throws Exception {
		if (CollectionUtils.isEmpty(defs)) {
			return null;
		}

		Object[] values = new Object[defs.size()];
		int i = 0;
		Object v = null;
		for (Object rv : defs) {
			if (rv == null) {
				v = null;
			} else if (rv instanceof BeanReference) {
				v = this.doGetBean(((BeanReference) rv).getBeanName());
			} else if (rv instanceof Object[]) {
				// TODO 处理集合中的bean引用
			} else if (rv instanceof Collection) {
				// TODO 处理集合中的bean引用
			} else if (rv instanceof Properties) {
				// TODO 处理properties中的bean引用
			} else if (rv instanceof Map) {
				// TODO 处理Map中的bean引用
			} else {
				v = rv;
			}

			values[i++] = v;
		}

		return values;
	}

	private Constructor<?> determineConstructor(BeanDefinition bd, Object[] args) throws Exception {

		Constructor<?> ct = null;

		if (args == null) {
			return bd.getBeanClass().getConstructor(null);
		}

		// 对于原型bean,从第二次开始获取bean实例时,可直接获得第一次缓存的构造方法。
		ct = bd.getConstructor();
		if (ct != null) {
			return ct;
		}

		// 根据参数类型获取精确匹配的构造方法
		Class<?>[] paramTypes = new Class[args.length];
		int j = 0;
		for (Object p : args) {
			paramTypes[j++] = p.getClass();
		}
		try {
			ct = bd.getBeanClass().getConstructor(paramTypes);
		} catch (Exception e) {
			// 这个异常不需要处理
		}

		if (ct == null) {

			// 没有精确参数类型匹配的,则遍历匹配所有的构造方法
			// 判断逻辑:先判断参数数量,再依次比对形参类型与实参类型
			outer: for (Constructor<?> ct0 : bd.getBeanClass().getConstructors()) {
				Class<?>[] paramterTypes = ct0.getParameterTypes();
				if (paramterTypes.length == args.length) {
					for (int i = 0; i < paramterTypes.length; i++) {
						if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
							continue outer;
						}
					}

					ct = ct0;
					break outer;
				}
			}
		}

		if (ct != null) {
			// 对于原型bean,可以缓存找到的构造方法,方便下次构造实例对象。在BeanDefinfition中获取设置所用构造方法的方法。
			// 同时在上面增加从beanDefinition中获取的逻辑。
			if (bd.isPrototype()) {
				bd.setConstructor(ct);
			}
			return ct;
		} else {
			throw new Exception("不存在对应的构造方法!" + bd);
		}
	}

	private Method determineFactoryMethod(BeanDefinition bd, Object[] args, Class<?> type) throws Exception {
		if (type == null) {
			type = bd.getBeanClass();
		}

		String methodName = bd.getFactoryMethodName();

		if (args == null) {
			return type.getMethod(methodName, null);
		}

		Method m = null;
		// 对于原型bean,从第二次开始获取bean实例时,可直接获得第一次缓存的构造方法。
		m = bd.getFactoryMethod();
		if (m != null) {
			return m;
		}

		// 根据参数类型获取精确匹配的方法
		Class[] paramTypes = new Class[args.length];
		int j = 0;
		for (Object p : args) {
			paramTypes[j++] = p.getClass();
		}
		try {
			m = type.getMethod(methodName, paramTypes);
		} catch (Exception e) {
			// 这个异常不需要处理
		}

		if (m == null) {

			// 没有精确参数类型匹配的,则遍历匹配所有的方法
			// 判断逻辑:先判断参数数量,再依次比对形参类型与实参类型
			outer: for (Method m0 : type.getMethods()) {
				if (!m0.getName().equals(methodName)) {
					continue;
				}
				Class<?>[] paramterTypes = m.getParameterTypes();
				if (paramterTypes.length == args.length) {
					for (int i = 0; i < paramterTypes.length; i++) {
						if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
							continue outer;
						}
					}

					m = m0;
					break outer;
				}
			}
		}

		if (m != null) {
			// 对于原型bean,可以缓存找到的方法,方便下次构造实例对象。在BeanDefinfition中获取设置所用方法的方法。
			// 同时在上面增加从beanDefinition中获取的逻辑。
			if (bd.isPrototype()) {
				bd.setFactoryMethod(m);
			}
			return m;
		} else {
			throw new Exception("不存在对应的构造方法!" + bd);
		}
	}

	// 静态工厂方法
	private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {

		Class<?> type = bd.getBeanClass();
		Object[] realArgs = this.getRealValues(bd.getConstructorArgumentValues());
		Method m = this.determineFactoryMethod(bd, realArgs, null);
		return m.invoke(type, realArgs);
	}

	// 工厂bean方式来构造对象
	private Object createInstanceByFactoryBean(BeanDefinition bd) throws Exception {

		Object factoryBean = this.doGetBean(bd.getFactoryBeanName());
		Object[] realArgs = this.getRealValues(bd.getConstructorArgumentValues());
		Method m = this.determineFactoryMethod(bd, realArgs, factoryBean.getClass());

		return m.invoke(factoryBean, realArgs);
	}

	/**
	 * 执行初始化方法
	 * 
	 * @param bd
	 * @param instance
	 * @throws Exception
	 */
	private void doInit(BeanDefinition bd, Object instance) throws Exception {
		// 执行初始化方法
		if (StringUtils.isNotBlank(bd.getInitMethodName())) {
			Method m = instance.getClass().getMethod(bd.getInitMethodName(), null);
			m.invoke(instance, null);
		}
	}

	@Override
	public void close() throws IOException {
		// 执行单例实例的销毁方法
		for (Entry<String, BeanDefinition> e : this.beanDefintionMap.entrySet()) {
			String beanName = e.getKey();
			BeanDefinition bd = e.getValue();

			if (bd.isSingleton() && StringUtils.isNotBlank(bd.getDestroyMethodName())) {
				Object instance = this.beanMap.get(beanName);
				try {
					Method m = instance.getClass().getMethod(bd.getDestroyMethodName(), null);
					m.invoke(instance, null);
				} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
						| InvocationTargetException e1) {
					logger.error("执行bean[" + beanName + "] " + bd + " 的 销毁方法异常!", e1);
				}
			}
		}
	}
}

BeanReference:

public class BeanReference {
    private String beanName;

    public BeanReference(String beanName) {
        super();
        this.beanName = beanName;
    }

    /**
     * 获得引用的beanName
     *
     * @return
     */
    public String getBeanName() {
        return this.beanName;
    }

}

PreBuildBeanFactory:

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class PreBuildBeanFactory extends DefaultBeanFactory {

    private final Log logger = LogFactory.getLog(getClass());

    private List<String> beanNames = new ArrayList<>();

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionRegistException {
        super.registerBeanDefinition(beanName, beanDefinition);
        synchronized (beanNames) {
            beanNames.add(beanName);
        }
    }

    public void preInstantiateSingletons() throws Exception {
        synchronized (beanNames) {
            for (String name : beanNames) {
                BeanDefinition bd = this.getBeanDefinition(name);
                if (bd.isSingleton()) {
                    this.doGetBean(name);
                    if (logger.isDebugEnabled()) {
                        logger.debug("preInstantiate: name=" + name + " " + bd);
                    }
                }
            }
        }
    }
}

PropertyValue:

public class PropertyValue {
    private String name;

    private Object value;

    public PropertyValue(String name, Object value) {
        super();
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

}

剩下的其他的类都不变,话说依赖注入代码挺烦的。而且不debug根本看不懂。

我回头在后续文章放上代码的连接,大家有兴趣去看一下。我就不贴代码了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值