实现效果

@EnabledSchedule(enableString = "${ECIFTask.addIssueOrgInfoList.enable}",cron = "${ECIFTask.addIssueOrgInfoList.cron}")
ECIFTask:addIssueOrgInfoList:enable: falsecron: 0 0/1 9-21 * * *

如何使用

启动类上面新增这个注解

自定义注解EnabledSchedule

完完全全的copy@Scheduled注解,新增两个参数:enable和enableString,enable是boolean类型,enableString是string类型,enableString支持从配置文件配置任务的启停,修改的代码很少,可以先把这四个文件复制,然后扫一眼就完事了,在修改的地方添加了注释

package com.pkyou.Sample.annotations;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnabledSchedule {/*** 配置任务的启停* @return true/false*/boolean enable() default true;/*** 支持从配置文件启停定时任务* @return true/false*/String enableString() default "true";String cron() default "";String zone() default "";long fixedDelay() default -1;String fixedDelayString() default "";long fixedRate() default -1;String fixedRateString() default "";long initialDelay() default -1;String initialDelayString() default "";
}
package com.pkyou.Sample.annotations;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.NamedBeanHolder;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.MethodIntrospector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.config.IntervalTask;
import org.springframework.scheduling.config.ScheduledTask;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
/*** @author luzh32717* @version 1.0* @date 2020/12/27 17:25* @since 3.0* @see EnabledSchedule* @see org.springframework.scheduling.annotation.EnableScheduling* @see SchedulingConfigurer* @see org.springframework.scheduling.TaskScheduler* @see org.springframework.scheduling.config.ScheduledTaskRegistrar* @see org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor*/
public class EnabledScheduledAnnotationBeanPostProcessorimplements MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {/*** The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".* <p>Note that the initial lookup happens by type; this is just the fallback* in case of multiple scheduler beans found in the context.* @since 4.2*/public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";protected final Log logger = LogFactory.getLog(getClass());private Object scheduler;private StringValueResolver embeddedValueResolver;private String beanName;private BeanFactory beanFactory;private ApplicationContext applicationContext;private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();private final Set<Class<?>> nonAnnotatedClasses =Collections.newSetFromMap(new ConcurrentHashMap<Class<?>, Boolean>(64));private final Map<Object, Set<ScheduledTask>> scheduledTasks =new IdentityHashMap<Object, Set<ScheduledTask>>(16);@Overridepublic int getOrder() {return LOWEST_PRECEDENCE;}/*** Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke* the scheduled methods, or a {@link java.util.concurrent.ScheduledExecutorService}* to be wrapped as a TaskScheduler.* <p>If not specified, default scheduler resolution will apply: searching for a* unique {@link TaskScheduler} bean in the context, or for a {@link TaskScheduler}* bean named "taskScheduler" otherwise; the same lookup will also be performed for* a {@link ScheduledExecutorService} bean. If neither of the two is resolvable,* a local single-threaded default scheduler will be created within the registrar.* @see #DEFAULT_TASK_SCHEDULER_BEAN_NAME*/public void setScheduler(Object scheduler) {this.scheduler = scheduler;}@Overridepublic void setEmbeddedValueResolver(StringValueResolver resolver) {this.embeddedValueResolver = resolver;}@Overridepublic void setBeanName(String beanName) {this.beanName = beanName;}/*** Making a {@link BeanFactory} available is optional; if not set,* {@link SchedulingConfigurer} beans won't get autodetected and* a {@link #setScheduler scheduler} has to be explicitly configured.*/@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}/*** Setting an {@link ApplicationContext} is optional: If set, registered* tasks will be activated in the {@link ContextRefreshedEvent} phase;* if not set, it will happen at {@link #afterSingletonsInstantiated} time.*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {this.applicationContext = applicationContext;if (this.beanFactory == null) {this.beanFactory = applicationContext;}}@Overridepublic void afterSingletonsInstantiated() {// Remove resolved singleton classes from cachethis.nonAnnotatedClasses.clear();if (this.applicationContext == null) {// Not running in an ApplicationContext -> register tasks early...finishRegistration();}}@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.applicationContext) {// Running in an ApplicationContext -> register tasks this late...// giving other ContextRefreshedEvent listeners a chance to perform// their work at the same time (e.g. Spring Batch's job registration).finishRegistration();}}private void finishRegistration() {if (this.scheduler != null) {this.registrar.setScheduler(this.scheduler);}if (this.beanFactory instanceof ListableBeanFactory) {Map<String, SchedulingConfigurer> configurers =((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);for (SchedulingConfigurer configurer : configurers.values()) {configurer.configureTasks(this.registrar);}}if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {Assert.state(this.beanFactory != null, "BeanFactory must be set to find scheduler by type");try {// Search for TaskScheduler bean...this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, false));}catch (NoUniqueBeanDefinitionException ex) {logger.debug("Could not find unique TaskScheduler bean", ex);try {this.registrar.setTaskScheduler(resolveSchedulerBean(TaskScheduler.class, true));}catch (NoSuchBeanDefinitionException ex2) {if (logger.isInfoEnabled()) {logger.info("More than one TaskScheduler bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex) {logger.debug("Could not find default TaskScheduler bean", ex);// Search for ScheduledExecutorService bean next...try {this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, false));}catch (NoUniqueBeanDefinitionException ex2) {logger.debug("Could not find unique ScheduledExecutorService bean", ex2);try {this.registrar.setScheduler(resolveSchedulerBean(ScheduledExecutorService.class, true));}catch (NoSuchBeanDefinitionException ex3) {if (logger.isInfoEnabled()) {logger.info("More than one ScheduledExecutorService bean exists within the context, and " +"none is named 'taskScheduler'. Mark one of them as primary or name it 'taskScheduler' " +"(possibly as an alias); or implement the SchedulingConfigurer interface and call " +"ScheduledTaskRegistrar#setScheduler explicitly within the configureTasks() callback: " +ex2.getBeanNamesFound());}}}catch (NoSuchBeanDefinitionException ex2) {logger.debug("Could not find default ScheduledExecutorService bean", ex2);// Giving up -> falling back to default scheduler within the registrar...logger.info("No TaskScheduler/ScheduledExecutorService bean found for scheduled processing");}}}this.registrar.afterPropertiesSet();}private <T> T resolveSchedulerBean(Class<T> schedulerType, boolean byName) {if (byName) {T scheduler = this.beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);if (this.beanFactory instanceof ConfigurableBeanFactory) {((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);}return scheduler;}else if (this.beanFactory instanceof AutowireCapableBeanFactory) {NamedBeanHolder<T> holder = ((AutowireCapableBeanFactory) this.beanFactory).resolveNamedBean(schedulerType);if (this.beanFactory instanceof ConfigurableBeanFactory) {((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);}return holder.getBeanInstance();}else {return this.beanFactory.getBean(schedulerType);}}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}/*** 在这个方法里面获取被注解的方法*/@Overridepublic Object postProcessAfterInitialization(final Object bean, String beanName) {Class<?> targetClass = AopUtils.getTargetClass(bean);if (!this.nonAnnotatedClasses.contains(targetClass)) {//修改为返回Map<Method, Set<EnabledSchedule>>Map<Method, Set<EnabledSchedule>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,new MethodIntrospector.MetadataLookup<Set<EnabledSchedule>>() {@Overridepublic Set<EnabledSchedule> inspect(Method method) {Set<EnabledSchedule> scheduledMethods = AnnotatedElementUtils.getAllMergedAnnotations(method, EnabledSchedule.class);return (!scheduledMethods.isEmpty() ? scheduledMethods : null);}});if (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());}}else {// Non-empty set of methodsfor (Map.Entry<Method, Set<EnabledSchedule>> entry : annotatedMethods.entrySet()) {Method method = entry.getKey();for (EnabledSchedule scheduled : entry.getValue()) {processScheduled(scheduled, method, bean);}}if (logger.isDebugEnabled()) {logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +"': " + annotatedMethods);}}}return bean;}/*** 处理注解*/protected void processScheduled(EnabledSchedule scheduled, Method method, Object bean) {try {Assert.isTrue(method.getParameterTypes().length == 0,"Only no-arg methods may be annotated with @Scheduled");Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);boolean processedSchedule = false;String errorMessage ="Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";Set<ScheduledTask> tasks = new LinkedHashSet<ScheduledTask>(4);//20201227 luzh add 新增enableif (!scheduled.enable()){return;}else {String enable = scheduled.enableString();boolean enableFlag = false;if(StringUtils.hasText(enable)){if(this.embeddedValueResolver != null) {enable = this.embeddedValueResolver.resolveStringValue(enable);}if ("true".equals(enable)){enableFlag = true;}}if (!enableFlag){return;}}//20201227 luzh add 新增end// Determine initial delaylong initialDelay = scheduled.initialDelay();String initialDelayString = scheduled.initialDelayString();if (StringUtils.hasText(initialDelayString)) {Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");if (this.embeddedValueResolver != null) {initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);}try {initialDelay = Long.parseLong(initialDelayString);}catch (NumberFormatException ex) {throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");}}// Check cron expressionString cron = scheduled.cron();if (StringUtils.hasText(cron)) {Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");processedSchedule = true;String zone = scheduled.zone();if (this.embeddedValueResolver != null) {cron = this.embeddedValueResolver.resolveStringValue(cron);zone = this.embeddedValueResolver.resolveStringValue(zone);}TimeZone timeZone;if (StringUtils.hasText(zone)) {timeZone = StringUtils.parseTimeZoneString(zone);}else {timeZone = TimeZone.getDefault();}tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));}// At this point we don't need to differentiate between initial delay set or not anymoreif (initialDelay < 0) {initialDelay = 0;}// Check fixed delaylong fixedDelay = scheduled.fixedDelay();if (fixedDelay >= 0) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule = true;tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));}String fixedDelayString = scheduled.fixedDelayString();if (StringUtils.hasText(fixedDelayString)) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule = true;if (this.embeddedValueResolver != null) {fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);}try {fixedDelay = Long.parseLong(fixedDelayString);}catch (NumberFormatException ex) {throw new IllegalArgumentException("Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");}tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));}// Check fixed ratelong fixedRate = scheduled.fixedRate();if (fixedRate >= 0) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule = true;tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));}String fixedRateString = scheduled.fixedRateString();if (StringUtils.hasText(fixedRateString)) {Assert.isTrue(!processedSchedule, errorMessage);processedSchedule = true;if (this.embeddedValueResolver != null) {fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);}try {fixedRate = Long.parseLong(fixedRateString);}catch (NumberFormatException ex) {throw new IllegalArgumentException("Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");}tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));}// Check whether we had any attribute setAssert.isTrue(processedSchedule, errorMessage);// Finally register the scheduled taskssynchronized (this.scheduledTasks) {Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);if (registeredTasks == null) {registeredTasks = new LinkedHashSet<ScheduledTask>(4);this.scheduledTasks.put(bean, registeredTasks);}registeredTasks.addAll(tasks);}}catch (IllegalArgumentException ex) {throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());}}@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) {Set<ScheduledTask> tasks;synchronized (this.scheduledTasks) {tasks = this.scheduledTasks.remove(bean);}if (tasks != null) {for (ScheduledTask task : tasks) {task.cancel();}}}@Overridepublic boolean requiresDestruction(Object bean) {synchronized (this.scheduledTasks) {return this.scheduledTasks.containsKey(bean);}}@Overridepublic void destroy() {synchronized (this.scheduledTasks) {Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();for (Set<ScheduledTask> tasks : allTasks) {for (ScheduledTask task : tasks) {task.cancel();}}this.scheduledTasks.clear();}this.registrar.destroy();}}
package com.pkyou.Sample.annotations;import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.annotation.SchedulingConfiguration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.lang.annotation.*;
import java.util.concurrent.Executor;/*** @author luzh32717* @version 1.0* @date 2020/12/27 17:20* @see EnabledSchedule* @see EnabledSchedulingConfiguration* @see SchedulingConfigurer* @see ScheduledTaskRegistrar* @see Trigger* @see ScheduledAnnotationBeanPostProcessor*/@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnabledSchedulingConfiguration.class)
@Documented
public @interface EnableMyScheduling {
}
package com.pkyou.Sample.annotations;import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.scheduling.config.TaskManagementConfigUtils;/*** @author luzh32717* @version 1.0* @date 2020/12/27 17:22*/
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class EnabledSchedulingConfiguration {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public EnabledScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new EnabledScheduledAnnotationBeanPostProcessor();}
}

SpringBoot版本

不同版本的spring可能@Scheduled的实现有一丢丢出入
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.8.RELEASE</version>
</parent>

修改@Scheduled注解增加enable参数使其支持在配置文件中打开和关闭相关推荐

  1. 用ffmpeg修改MP4文件头信息,使其支持流式加载及播放

    经常有用户反映,有些网页中加载的mp4文件,有的可以加载一点就开始播放,有的就必须全部加载完才能播. 经核实,主要是头信息的数据顺序有关,用工具:mp4info.exe可以查看mp4文件的结构信息: ...

  2. 如何修改 SAP ABAP OData 模型,使其支持 $expand 操作试读版

    正如本教程的开篇介绍文章SAP OData 开发教程 - 从入门到提高(包含 SEGW, RAP 和 CDP)所提到的,SAP OData 服务开发,从实现技术上来说,可以分为三大类.因此本教程也分为 ...

  3. Atom 编辑器安装 linter-eslint 插件,并配置使其支持 vue 文件中的 js 格式校验

    安装linter-eslint插件方式有如下几种. 1. 最简单的方式就是  点击 File -Settings -Install ,搜索linter-eslint ,安装即可 2. # 进入atom ...

  4. web页在微信中访问增加遮罩层 右上角弹出在浏览器中打开

    web页在微信中访问增加遮罩层 右上角弹出在浏览器中打开 <style type="text/css"> * {margin: 0;padding: 0; }a {te ...

  5. 使链接在新窗口中打开

    已经在CSS表中添加了"在新窗口中打开所有链接"代码的朋友就不需要再往下看了. 在百度空间的博客里发表文章时,不管添加文字链接还是网址链接,默认的打开方式都是覆盖原窗口.这使的看有 ...

  6. 16. 如何修改 SAP ABAP OData 模型,使其支持 $expand 操作

    文章目录 OData 服务里 $expand 操作的应用场景 步骤1:创建一个新的 Entity Type Author 步骤2:创建一对 EntitySet 步骤3:创建一个新的 Associati ...

  7. android studio增加一个界面,Android Studio在同一个窗口中打开多个Project【附效果图附源码...

    Android Studio在同一个窗口中打开多个Project[附效果图附源码 Android Studio在同一个窗口中打开多个Project[附效果图附源码]

  8. 如何设置使chrome新标签页中打开链接自动跳转到新标签页?

    在新标签打开链接的时候这样点选 Ctrl+左键 或者 鼠标中键 或者 右键链接选择'新标签页中打开链接', 可实现出现新标签页但不自动跳转 但是这个有问题, 即, 新标签只是在背景打开, 操作后并不会 ...

  9. 修改sqlarchemy源码使其支持jdbc连接mysql

    注意:本文不会将所有完整源码贴出,只是将具体的思路以及部分源码贴出,需要感兴趣的读者自己实验然后实现吆. 缘起 公司最近的项目需要将之前的部分业务的数据库连接方式改为jdbc,但由于之前的项目都使用s ...

最新文章

  1. c语言多线程转python多线程,真正的python 多线程!一个修饰符让你的多线程和C语言一样快...
  2. python 签名计算 请求参数签名
  3. 小程序获取openid保存缓存吗_微信小程序把openid放到缓存里
  4. 什么是微调(Fine Tune)?什么时候使用什么样的微调?【数据量和数据相似度决定】
  5. [转]MySQL实现over partition by(分组后对组内数据排序)
  6. 开课吧里的python学习是真的吗-做客李晨nic淘宝直播 胡海泉胡彦斌带货开课吧Python...
  7. 软考程序员-C专题(1)
  8. mate20pro换鸿蒙系统,鸿蒙2.0下载
  9. 安装VMware tools好处
  10. CAD中通过用户交互来选择对象
  11. 【个人学习文章收集】
  12. hdu 2044 一只小蜜蜂...
  13. [Unity][Crowd]学习人群模拟资源分享以及相关的问题
  14. 跳蚤市场应用市场现状研究分析-
  15. printf(\033[1;33m Hello World. \033[0m \n);有趣的串口之超级终端的玩法
  16. 2016/10/31 宝贝儿蛋,万圣节快乐。
  17. RTThread从底层AT组件到上层SAL之间的关系
  18. 1年时间强势进阶,百度财报公布好看视频成长秘密
  19. 【软件部署】Linux系统yum方式安装Jenkins
  20. Mac OS X 键盘快捷键 --- 很全面

热门文章

  1. Linux命令ip addr详解
  2. fastboot工具使用说明-海思hi3531文档 《Fastboot工具使用说明 Application Notes》补充
  3. 孙强:IT治理需六方结合
  4. 新年礼盒营销背后,是大企的逐利与流量竞争
  5. Lombok之@ToString使用
  6. Solidity合约中签名验证的一点实践
  7. 《程序员的修炼——从优秀到卓越》-摘要
  8. 一个程序员眼里的茶行业
  9. java如何通过if判断字符_java如何用if判断字符串是否相等
  10. 今年台北电脑大展说明了什么?