Spring与Quartz集成-源码分析

在阅读以下文章之前,如果对Quartz任务调度不是很熟悉,请看以下文章
Quartz任务调度分析:[url]http://donald-draper.iteye.com/blog/2323118[/url]
Spring与Quartz集成详解:[url]http://donald-draper.iteye.com/blog/2323591[/url]
ThreadLocal彻底理解:[url]http://blog.csdn.net/lufeng20/article/details/24314381/[/url]
public class SchedulerFactoryBean extends SchedulerAccessor
implements FactoryBean, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle
{

public SchedulerFactoryBean()
{
//Quartz的标准调度工场类
schedulerFactoryClass = org/quartz/impl/StdSchedulerFactory;
jobFactorySet = false;
autoStartup = true;
startupDelay = 0;
phase = 2147483647;
exposeSchedulerInRepository = false;
waitForJobsToCompleteOnShutdown = false;
}
//设置调度器工厂类
public void setSchedulerFactoryClass(Class schedulerFactoryClass)
{
Assert.isAssignable(org/quartz/SchedulerFactory, schedulerFactoryClass);
this.schedulerFactoryClass = schedulerFactoryClass;
}
//设置调度器Name
public void setSchedulerName(String schedulerName)
{
this.schedulerName = schedulerName;
}
//Quartz属性
public void setQuartzProperties(Properties quartzProperties)
{
this.quartzProperties = quartzProperties;
}
//设置任务执行器
public void setTaskExecutor(Executor taskExecutor)
{
this.taskExecutor = taskExecutor;
}
//设置beanName
public void setBeanName(String name)
{
if(schedulerName == null)
schedulerName = name;
}
//设置应用上下文
public void setApplicationContext(ApplicationContext applicationContext)
{
this.applicationContext = applicationContext;
}
//设置job工厂
public void setJobFactory(JobFactory jobFactory)
{
this.jobFactory = jobFactory;
jobFactorySet = true;
}
public void setAutoStartup(boolean autoStartup)
{
this.autoStartup = autoStartup;
}
public void afterPropertiesSet()
throws Exception
{
SchedulerFactory schedulerFactory;
if(dataSource == null && nonTransactionalDataSource != null)
dataSource = nonTransactionalDataSource;
if(applicationContext != null && resourceLoader == null)
resourceLoader = applicationContext;
//实例化调度器工厂
schedulerFactory = (SchedulerFactory)BeanUtils.instantiateClass(schedulerFactoryClass);
//初始化调度器工厂
initSchedulerFactory(schedulerFactory);
if(resourceLoader != null)
configTimeResourceLoaderHolder.set(resourceLoader);
if(taskExecutor != null)
configTimeTaskExecutorHolder.set(taskExecutor);
if(dataSource != null)
configTimeDataSourceHolder.set(dataSource);
if(nonTransactionalDataSource != null)
configTimeNonTransactionalDataSourceHolder.set(nonTransactionalDataSource);
//创建调度器
scheduler = createScheduler(schedulerFactory, schedulerName);
//初始化调度器上下文
populateSchedulerContext();
if(!jobFactorySet && !(scheduler instanceof RemoteScheduler))
jobFactory = new AdaptableJobFactory();
if(jobFactory != null)
{
if(jobFactory instanceof SchedulerContextAware)
((SchedulerContextAware)jobFactory).setSchedulerContext(scheduler.getContext());
//设置调度器job工厂类
scheduler.setJobFactory(jobFactory);
}
if(resourceLoader != null)
configTimeResourceLoaderHolder.remove();
if(taskExecutor != null)
configTimeTaskExecutorHolder.remove();
if(dataSource != null)
configTimeDataSourceHolder.remove();
if(nonTransactionalDataSource != null)
configTimeNonTransactionalDataSourceHolder.remove();
break MISSING_BLOCK_LABEL_334;
if(resourceLoader != null)
configTimeResourceLoaderHolder.remove();
if(taskExecutor != null)
configTimeTaskExecutorHolder.remove();
if(dataSource != null)
configTimeDataSourceHolder.remove();
if(nonTransactionalDataSource != null)
configTimeNonTransactionalDataSourceHolder.remove();
//注册监听器
registerListeners();
//注册job&Trriger
registerJobsAndTriggers();
return;
}
//初始化调度工厂类
private void initSchedulerFactory(SchedulerFactory schedulerFactory)
throws SchedulerException, IOException
{
//判断标准调度工厂的属性是否完整
if(!(schedulerFactory instanceof StdSchedulerFactory))
if(configLocation != null || quartzProperties != null || taskExecutor != null || dataSource != null)
throw new IllegalArgumentException((new StringBuilder()).append("StdSchedulerFactory required for applying Quartz properties: ").append(schedulerFactory).toString());
Properties mergedProps = new Properties();
if(resourceLoader != null)
//设置classLoadHelper类
mergedProps.setProperty("org.quartz.scheduler.classLoadHelper.class", org/springframework/scheduling/quartz/ResourceLoaderClassLoadHelper.getName());
if(taskExecutor != null)
{ //设置线程池类
mergedProps.setProperty("org.quartz.threadPool.class", org/springframework/scheduling/quartz/LocalTaskExecutorThreadPool.getName());
} else
{
mergedProps.setProperty("org.quartz.threadPool.class", org/quartz/simpl/SimpleThreadPool.getName());
mergedProps.setProperty("org.quartz.threadPool.threadCount", Integer.toString(10));
}
if(configLocation != null)
{
PropertiesLoaderUtils.fillProperties(mergedProps, configLocation);
}
//调度器工厂属性合并重组
CollectionUtils.mergePropertiesIntoMap(quartzProperties, mergedProps);
if(dataSource != null)
mergedProps.put("org.quartz.jobStore.class", org/springframework/scheduling/quartz/LocalDataSourceJobStore.getName());
if(schedulerName != null)
mergedProps.put("org.quartz.scheduler.instanceName", schedulerName);
//标准调度工厂初始化
((StdSchedulerFactory)schedulerFactory).initialize(mergedProps);
}
//创建调度器
protected Scheduler createScheduler(SchedulerFactory schedulerFactory, String schedulerName)
throws SchedulerException
{
Thread currentThread;
ClassLoader threadContextClassLoader;
boolean overrideClassLoader;
currentThread = Thread.currentThread();
threadContextClassLoader = currentThread.getContextClassLoader();
overrideClassLoader = resourceLoader != null && !resourceLoader.getClassLoader().equals(threadContextClassLoader);
if(overrideClassLoader)
//如果资源加载器不为空,则设置当前线程上下文加载器
currentThread.setContextClassLoader(resourceLoader.getClassLoader());
//获取调度器仓库实例
SchedulerRepository repository = SchedulerRepository.getInstance();
Scheduler scheduler1;
synchronized(repository)
{
//从调度器仓库寻找schedulerName的调度器
Scheduler existingScheduler = schedulerName == null ? null : repository.lookup(schedulerName);
//从调度器工厂获取调度器
Scheduler newScheduler = schedulerFactory.getScheduler();
if(newScheduler == existingScheduler)
throw new IllegalStateException((new StringBuilder()).append("Active Scheduler of name '").append(schedulerName).append("' already registered ").append("in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!").toString());
if(!exposeSchedulerInRepository)
//从仓库中移除newScheduler
SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName());
scheduler1 = newScheduler;
}
if(overrideClassLoader)
currentThread.setContextClassLoader(threadContextClassLoader);
return scheduler1;
}
//初始化调度器上下文,SchedulerContext,实际上是Map
private void populateSchedulerContext()
throws SchedulerException
{
//如果schedulerContextMap不为空,则将schedulerContextMap放入调度器上下文中
if(schedulerContextMap != null)
scheduler.getContext().putAll(schedulerContextMap);
//如果applicationContextSchedulerContextKey不为空,
//则将applicationContext放入到调度器上下文中
if(applicationContextSchedulerContextKey != null)
{
if(applicationContext == null)
throw new IllegalStateException("SchedulerFactoryBean needs to be set up in an ApplicationContext to be able to handle an 'applicationContextSchedulerContextKey'");
scheduler.getContext().put(applicationContextSchedulerContextKey, applicationContext);
}
}
//启动调度器
protected void startScheduler(final Scheduler scheduler, final int startupDelay)
throws SchedulerException
{
if(startupDelay <= 0)
{
logger.info("Starting Quartz Scheduler now");
scheduler.start();
} else
{
if(logger.isInfoEnabled())
logger.info((new StringBuilder()).append("Will start Quartz Scheduler [").append(scheduler.getSchedulerName()).append("] in ").append(startupDelay).append(" seconds").toString());
Thread schedulerThread = new Thread() {

public void run()
{
try
{
//如果延迟加载,则休眠startupDelay秒钟
Thread.sleep(startupDelay * 1000);
}
if(logger.isInfoEnabled())
logger.info((new StringBuilder()).append("Starting Quartz Scheduler now, after delay of ").append(startupDelay).append(" seconds").toString());
try
{
scheduler.start();
}
}
final int val$startupDelay;
final Scheduler val$scheduler;
final SchedulerFactoryBean this$0;
{
this.this$0 = SchedulerFactoryBean.this;
startupDelay = i;
scheduler = scheduler1;
super();
}
};
schedulerThread.setName((new StringBuilder()).append("Quartz Scheduler [").append(scheduler.getSchedulerName()).append("]").toString());
schedulerThread.setDaemon(true);
schedulerThread.start();
}
}

public Scheduler getScheduler()
{
return scheduler;
}
//返回bean对象为Scheduler
public Scheduler getObject()
{
return scheduler;
}

public Class getObjectType()
{
return scheduler == null ? org/quartz/Scheduler : scheduler.getClass();
}
//是否是单例模式,是
public boolean isSingleton()
{
return true;
}
//启动
public void start()
throws SchedulingException
{
if(scheduler != null)
try
{
startScheduler(scheduler, startupDelay);
}
catch(SchedulerException ex)
{
throw new SchedulingException("Could not start Quartz Scheduler", ex);
}
}
//关闭操作
public void stop()
throws SchedulingException
{
if(scheduler != null)
try
{
scheduler.standby();
}
}
//关闭操作
public void stop(Runnable callback)
throws SchedulingException
{
stop();
callback.run();
}
//获取调度器是否运行信息
public boolean isRunning()
throws SchedulingException
{
if(scheduler == null)
break MISSING_BLOCK_LABEL_28;
return !scheduler.isInStandbyMode();
return false;

}
//bean销毁,关闭调度器
public void destroy()
throws SchedulerException
{
logger.info("Shutting down Quartz Scheduler");
scheduler.shutdown(waitForJobsToCompleteOnShutdown);
}
public volatile Object getObject()
throws Exception
{
return getObject();
}
public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount";
public static final int DEFAULT_THREAD_COUNT = 10;
private static final ThreadLocal configTimeResourceLoaderHolder = new ThreadLocal();
private static final ThreadLocal configTimeTaskExecutorHolder = new ThreadLocal();
private static final ThreadLocal configTimeDataSourceHolder = new ThreadLocal();
private static final ThreadLocal configTimeNonTransactionalDataSourceHolder = new ThreadLocal();
private Class schedulerFactoryClass;//调度工厂类
private String schedulerName;//调度器名
private Resource configLocation;
private Properties quartzProperties;//quartz属性
private Executor taskExecutor;//任务执行器
private DataSource dataSource;//数据源
private DataSource nonTransactionalDataSource;//无事务数据源
private Map schedulerContextMap;//调度器上下文
private ApplicationContext applicationContext;//应用上下文
private String applicationContextSchedulerContextKey;
private JobFactory jobFactory;//job工厂
private boolean jobFactorySet;
private boolean autoStartup;
private int startupDelay;//延时加载
private int phase;
private boolean exposeSchedulerInRepository;//是否将调度器存储在调度器仓库
private boolean waitForJobsToCompleteOnShutdown;//是否等job完成,才关闭调度器
private Scheduler scheduler;//调度器

}

//SchedulerAccessor
public abstract class SchedulerAccessor
implements ResourceLoaderAware
{
public SchedulerAccessor()
{
overwriteExistingJobs = false;
if(jobKeyClass == null && logger.isInfoEnabled())
logger.info("Spring's Quartz 1.x support is deprecated - please upgrade to Quartz 2.0+");
}
protected void registerListeners()
throws SchedulerException
{
Object target;
boolean quartz2;
try
{
//调度器获取监听管理器方法
Method getListenerManager = org/quartz/Scheduler.getMethod("getListenerManager", new Class[0]);
//获取调度器监听管理器
target = ReflectionUtils.invokeMethod(getListenerManager, getScheduler());
quartz2 = true;
}
catch(NoSuchMethodException ex)
{
target = getScheduler();
quartz2 = false;
}
Class targetClass = target.getClass();
try
{
if(schedulerListeners != null)
{
Method addSchedulerListener = targetClass.getMethod("addSchedulerListener", new Class[] {
org/quartz/SchedulerListener
});
SchedulerListener aschedulerlistener[] = schedulerListeners;
int k = aschedulerlistener.length;
for(int l1 = 0; l1 < k; l1++)
{
SchedulerListener listener = aschedulerlistener[l1];
//添加调度器监听器
ReflectionUtils.invokeMethod(addSchedulerListener, target, new Object[] {
listener
});
}

}
//添加全局job监听器
if(globalJobListeners != null)
{
Method addJobListener;
if(quartz2)
//添加job监听器方法
addJobListener = targetClass.getMethod("addJobListener", new Class[] {
org/quartz/JobListener, java/util/List
});
else
//添加全局job监听器方法
addJobListener = targetClass.getMethod("addGlobalJobListener", new Class[] {
org/quartz/JobListener
});
JobListener ajoblistener1[] = globalJobListeners;
int l = ajoblistener1.length;
for(int i2 = 0; i2 < l; i2++)
{
JobListener listener = ajoblistener1[i2];
if(quartz2)
{
List emptyMatchers = new LinkedList();
//添加job监听器
ReflectionUtils.invokeMethod(addJobListener, target, new Object[] {
listener, emptyMatchers
});
} else
{
//添加全局job监听器
ReflectionUtils.invokeMethod(addJobListener, target, new Object[] {
listener
});
}
}

}
if(jobListeners != null)
{
JobListener ajoblistener[] = jobListeners;
int i = ajoblistener.length;
for(int i1 = 0; i1 < i; i1++)
{
JobListener listener = ajoblistener[i1];
if(quartz2)
throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - manually register a Matcher against the Quartz ListenerManager instead");
//添加job监听器
getScheduler().addJobListener(listener);
}

}
//添加全局触发器监听器
if(globalTriggerListeners != null)
{
Method addTriggerListener;
if(quartz2)
//添加触发器监听器方法
addTriggerListener = targetClass.getMethod("addTriggerListener", new Class[] {
org/quartz/TriggerListener, java/util/List
});
else
//添加全局触发器监听器方法
addTriggerListener = targetClass.getMethod("addGlobalTriggerListener", new Class[] {
org/quartz/TriggerListener
});
TriggerListener atriggerlistener1[] = globalTriggerListeners;
int j1 = atriggerlistener1.length;
for(int j2 = 0; j2 < j1; j2++)
{
TriggerListener listener = atriggerlistener1[j2];
if(quartz2)
{
List emptyMatchers = new LinkedList();
//添加触发器监听器
ReflectionUtils.invokeMethod(addTriggerListener, target, new Object[] {
listener, emptyMatchers
});
} else
{
//添加全局触发器监听器
ReflectionUtils.invokeMethod(addTriggerListener, target, new Object[] {
listener
});
}
}

}
if(triggerListeners != null)
{
TriggerListener atriggerlistener[] = triggerListeners;
int j = atriggerlistener.length;
for(int k1 = 0; k1 < j; k1++)
{
TriggerListener listener = atriggerlistener[k1];
if(quartz2)
throw new IllegalStateException("Non-global TriggerListeners not supported on Quartz 2 - manually register a Matcher against the Quartz ListenerManager instead");
//添加触发器监听器
getScheduler().addTriggerListener(listener);
}

}
}
catch(NoSuchMethodException ex)
{
throw new IllegalStateException((new StringBuilder()).append("Expected Quartz API not present: ").append(ex).toString());
}
}
//添加job和trriger
protected void registerJobsAndTriggers()
throws SchedulerException
{
TransactionStatus transactionStatus = null;
if(transactionManager != null)
transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
try
{
if(jobSchedulingDataLocations != null)
{
ClassLoadHelper clh = new ResourceLoaderClassLoadHelper(resourceLoader);
clh.initialize();
XMLSchedulingDataProcessor dataProcessor = new XMLSchedulingDataProcessor(clh);
String as[] = jobSchedulingDataLocations;
int i = as.length;
for(int j = 0; j < i; j++)
{
String location = as[j];
dataProcessor.processFileAndScheduleJobs(location, getScheduler());
}

}
if(jobDetails != null)
{
JobDetail jobDetail;
//如果jobDetails不为空,则添加jobDetails到调度器
for(Iterator iterator = jobDetails.iterator(); iterator.hasNext(); addJobToScheduler(jobDetail))
jobDetail = (JobDetail)iterator.next();

} else
{
jobDetails = new LinkedList();
}
if(calendars != null)
{
String calendarName;
Calendar calendar;
for(Iterator iterator1 = calendars.keySet().iterator(); iterator1.hasNext(); getScheduler().addCalendar(calendarName, calendar, true, true))
{
calendarName = (String)iterator1.next();
calendar = (Calendar)calendars.get(calendarName);
}

}
if(triggers != null)
{
//添加TrrigerWapprer触发任务到调度器
Trigger trigger;
for(Iterator iterator2 = triggers.iterator(); iterator2.hasNext(); addTriggerToScheduler(trigger))
trigger = (Trigger)iterator2.next();

}
}
catch(Throwable ex)
{
if(transactionStatus != null)
try
{
transactionManager.rollback(transactionStatus);
}
catch(TransactionException tex)
{
logger.error("Job registration exception overridden by rollback exception", ex);
throw tex;
}
if(ex instanceof SchedulerException)
throw (SchedulerException)ex;
if(ex instanceof Exception)
throw new SchedulerException((new StringBuilder()).append("Registration of jobs and triggers failed: ").append(ex.getMessage()).toString(), ex);
else
throw new SchedulerException((new StringBuilder()).append("Registration of jobs and triggers failed: ").append(ex.getMessage()).toString());
}
if(transactionStatus != null)
transactionManager.commit(transactionStatus);
}
//添加jobDetail到调度器
private boolean addJobToScheduler(JobDetail jobDetail)
throws SchedulerException
{
if(overwriteExistingJobs || !jobDetailExists(jobDetail))
{
getScheduler().addJob(jobDetail, true);
return true;
} else
{
return false;
}
}
//添加TrrigerWapprer触发任务到调度器
private boolean addTriggerToScheduler(Trigger trigger)
throws SchedulerException
{
boolean triggerExists = triggerExists(trigger);
if(!triggerExists || overwriteExistingJobs)
{
JobDetail jobDetail = findJobDetail(trigger);
if(jobDetail != null && !jobDetails.contains(jobDetail) && addJobToScheduler(jobDetail))
jobDetails.add(jobDetail);
if(!triggerExists)
try
{
//关键在这里,调度触发任务
getScheduler().scheduleJob(trigger);
}
catch(ObjectAlreadyExistsException ex)
{
if(logger.isDebugEnabled())
logger.debug((new StringBuilder()).append("Unexpectedly found existing trigger, assumably due to cluster race condition: ").append(ex.getMessage()).append(" - can safely be ignored").toString());
if(overwriteExistingJobs)
//如果允许重写存在的job,则重新调度触发任务
rescheduleJob(trigger);
}
else
rescheduleJob(trigger);
return true;
} else
{
return false;
}
}
//根据trigger获取JobDetail
private JobDetail findJobDetail(Trigger trigger)
{
if(trigger instanceof JobDetailAwareTrigger)
return ((JobDetailAwareTrigger)trigger).getJobDetail();
Map jobDataMap = (Map)ReflectionUtils.invokeMethod(org/quartz/Trigger.getMethod("getJobDataMap", new Class[0]), trigger);
return (JobDetail)jobDataMap.remove("jobDetail");
}
//设置JobDetail
public transient void setJobDetails(JobDetail jobDetails[])
{
this.jobDetails = new ArrayList(Arrays.asList(jobDetails));
}
//设置Trigger
public transient void setTriggers(Trigger triggers[])
{
this.triggers = Arrays.asList(triggers);
}
//设置SchedulerListener
public transient void setSchedulerListeners(SchedulerListener schedulerListeners[])
{
this.schedulerListeners = schedulerListeners;
}

protected abstract Scheduler getScheduler();
private static Class jobKeyClass;
private static Class triggerKeyClass;
protected final Log logger = LogFactory.getLog(getClass());
private boolean overwriteExistingJobs;
private String jobSchedulingDataLocations[];
private List jobDetails;
private Map calendars;
private List triggers;
private SchedulerListener schedulerListeners[];
private JobListener globalJobListeners[];
private JobListener jobListeners[];
private TriggerListener globalTriggerListeners[];
private TriggerListener triggerListeners[];
private PlatformTransactionManager transactionManager;
protected ResourceLoader resourceLoader;
static
{
try
{
jobKeyClass = ClassUtils.forName("org.quartz.JobKey", org/springframework/scheduling/quartz/SchedulerAccessor.getClassLoader());
triggerKeyClass = ClassUtils.forName("org.quartz.TriggerKey", org/springframework/scheduling/quartz/SchedulerAccessor.getClassLoader());
}
}
}

//资源加载器
public interface ResourceLoaderAware
extends Aware
{
public abstract void setResourceLoader(ResourceLoader resourceloader);
}

//事务状态
public interface TransactionStatus
extends SavepointManager
{
public abstract boolean isNewTransaction();
public abstract boolean hasSavepoint();
public abstract void setRollbackOnly();
public abstract boolean isRollbackOnly();
public abstract boolean isCompleted();
}

//bean工场
public interface FactoryBean
{
public abstract Object getObject()
throws Exception;
public abstract Class getObjectType();
public abstract boolean isSingleton();
}

//beanName设置
public interface BeanNameAware
extends Aware
{
public abstract void setBeanName(String s);
}

//应用上下文
public interface ApplicationContextAware
extends Aware
{
public abstract void setApplicationContext(ApplicationContext applicationcontext)
throws BeansException;
}

//bean初始化
public interface InitializingBean
{
public abstract void afterPropertiesSet()
throws Exception;
}

//bean销毁
public interface DisposableBean
{
public abstract void destroy()
throws Exception;
}

//生命周期管理
public interface SmartLifecycle
extends Lifecycle, Phased
{
public abstract boolean isAutoStartup();
public abstract void stop(Runnable runnable);
}

//job工厂
public interface JobFactory
{
//TriggerFiredBundle,触发任务包装类,Scheduler,调度器
public abstract Job newJob(TriggerFiredBundle triggerfiredbundle, Scheduler scheduler)
throws SchedulerException;
}

// JobDetailAwareTrigger-Spring
public interface JobDetailAwareTrigger
{
public abstract JobDetail getJobDetail();
public static final String JOB_DETAIL_KEY = "jobDetail";
}


总结:
[color=green]Spring与Quartz的整合关键在于SchedulerFactoryBean,我们在XML中配SchedulerFactoryBean时候,如要需要任务持久化,我们需要配置datasource,配置Quartz属性(线程池, 调度器名,线程池优先级,持久化类等信息),配置jobDetails,配置触发任务trrigers(TrrigerWapper),延迟加载属性,监听器,重写属性;SchedulerFactoryBean实现了InitializingBean,在SchedulerFactoryBean属性初始化完毕,调用afterPropertiesSet方法,完成数据源,Quartz-schedulerFactory初始化,创建调度器,初始化调度器上下文,注册监听器,注册job,Trrigers。[/color]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值