SpringCloud中动态配置刷新@RefreshScope的源码解析

本文主要是介绍SpringCloud中动态配置刷新@RefreshScope的源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

@RefreshScope
public class App {
}@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
// 作用域为刷新作用域
@Scope("refresh")
@Documented
public @interface RefreshScope注解 {/*** 作用域代理的模式,默认为CGLIB*/@AliasFor(annotation = Scope.class)ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}// 导入刷新配置的核心处理类
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(ConfigurationPropertiesBindingPostProcessor.class)
public class ConfigurationPropertiesRebinderAutoConfiguration implements ApplicationContextAware, SmartInitializingSingleton {// Spring上下文private ApplicationContext context;// 创建ConfigurationPropertiesBeans的Bean,它保存了所有标注在当前上下文中所有的ConfigurationProperties类@Bean@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)public static ConfigurationPropertiesBeans configurationPropertiesBeans() {return new ConfigurationPropertiesBeans();}// 注入将指定ConfigurationProperties类进行重新绑定@Bean@ConditionalOnMissingBean(search = SearchStrategy.CURRENT)public ConfigurationPropertiesRebinder configurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder(beans);return rebinder;}// 所有的Bena都初始化完成@Overridepublic void afterSingletonsInstantiated() {// 如果存在父容器if (this.context.getParent() != null) {// 获取ConfigurationPropertiesRebinder该BeanConfigurationPropertiesRebinder rebinder = this.context.getBean(ConfigurationPropertiesRebinder.class);// 获取父容器中所有的Bean进行重新加载for (String name : this.context.getParent().getBeanDefinitionNames()) {rebinder.rebind(name);}}}}@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED, matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
@EnableConfigurationProperties(RefreshAutoConfiguration.RefreshProperties.class)
public class RefreshAutoConfiguration {// 注册刷新作用域@Bean@ConditionalOnMissingBean(RefreshScope.class)public static RefreshScope refreshScope() {return new RefreshScope();}// 配置数据上下文的刷新器,它负责刷新该作用域下的所有bean@Bean@ConditionalOnMissingBean@ConditionalOnBootstrapDisabledpublic ConfigDataContextRefresher configDataContextRefresher(ConfigurableApplicationContext context, RefreshScope scope, RefreshProperties properties) {return new ConfigDataContextRefresher(context, scope, properties);}/*** {@link com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListener(java.lang.String, java.lang.String)}* 配置刷新的监听器,该RefreshEvent是由对应的配置中心发布的事件,例如Nacos发生匹配变化的时候,就会触发一个RefreshEvent事件*/@Beanpublic RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {return new RefreshEventListener(contextRefresher);}
}// 配置数据上下文的刷新器,它负责刷新该作用域下的所有bean
public class ConfigDataContextRefresher extends ContextRefresher implements ApplicationListener<ContextRefreshedWithApplicationEvent> {// Spring上下文对象private ConfigurableApplicationContext context;// 刷新作用域的实现private RefreshScope scope;// SpringBoot的配置为了private SpringApplication application;@Overridepublic void onApplicationEvent(ContextRefreshedWithApplicationEvent event) {application = event.getSpringApplication();}// 更新环境对象,返回一个新的环境对象@Overrideprotected void updateEnvironment() {// 拷贝一份上下文环境存在的属性对象StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());ConfigurableBootstrapContext bootstrapContext = getContext().getBeanProvider(ConfigurableBootstrapContext.class).getIfAvailable(DefaultBootstrapContext::new);// 加载所有的EnvironmentPostProcessor后置处理器List<String> classNames = SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class, getClass().getClassLoader());Instantiator<EnvironmentPostProcessor> instantiator = new Instantiator<>(EnvironmentPostProcessor.class,(parameters) -> {parameters.add(DeferredLogFactory.class, logFactory);parameters.add(Log.class, logFactory::getLog);parameters.add(ConfigurableBootstrapContext.class, bootstrapContext);parameters.add(BootstrapContext.class, bootstrapContext);parameters.add(BootstrapRegistry.class, bootstrapContext);});// 实例化所有EnvironmentPostProcessor的类List<EnvironmentPostProcessor> postProcessors = instantiator.instantiate(classNames);for (EnvironmentPostProcessor postProcessor : postProcessors) {// 对拷贝的环境对象进行后置处理操作postProcessor.postProcessEnvironment(environment, application);}// 如果包含refreshArgs属性,将该属性移除if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);}// 获取环境对象中的所有属性MutablePropertySources target = getContext().getEnvironment().getPropertySources();String targetName = null;// 保持属性的顺序for (PropertySource<?> source : environment.getPropertySources()) {String name = source.getName();if (target.contains(name)) {targetName = name;}if (!this.standardSources.contains(name)) {if (target.contains(name)) {target.replace(name, source);} else {if (targetName != null) {target.addAfter(targetName, source);targetName = name;} else {target.addFirst(source);targetName = name;}}}}}// 刷新配置以及对应的配置为了public synchronized Set<String> refresh() {// 刷新环境对象Set<String> keys = this.refreshEnvironment();// 刷新标注了@RefreshScope的类this.scope.refreshAll();return keys;}// 刷新环境对象,返回发生变化的属性public synchronized Set<String> refreshEnvironment() {Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());// 更新环境对象,返回一个新的环境对象this.updateEnvironment();// 获取发生变化的属性名Set<String> keys = this.changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();/**发布一个事件,该事件会由{@link ConfigurationPropertiesRebinder#onApplicationEvent(EnvironmentChangeEvent)}处理,从而刷新标注了**/this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));// 返回发生变化的属性return keys;}// 将原始的属性与当前的属性做对比,提取出发生变化的属性private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) {Map<String, Object> result = new HashMap<>();for (String key : before.keySet()) {if (!after.containsKey(key)) {result.put(key, null);} else if (!equal(before.get(key), after.get(key))) {result.put(key, after.get(key));}}for (String key : after.keySet()) {if (!before.containsKey(key)) {result.put(key, after.get(key));}}return result;}
}@ManagedResource
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {// 刷新当前作用域的所有Beanpublic void refreshAll() {/*** 创建该作用域Bean的流程{@link RefreshScope#get(String, ObjectFactory)}*/// 销毁所有refresh作用域的@Bean,下次需要发现该作用域没有缓存该Bean,则又会触发Bean的创建super.destroy();// 发布作用域刷新事件,这个事件我们自己可以监听的做一些事情this.context.publishEvent(new RefreshScopeRefreshedEvent());}/*** <pre>*     获取Bean对象,如果Bean对象不存在,则创建Bean*    // 根据不同的作用域来创建对象*    if (mbd.isSingleton()) {*        // 从单例池中获取,不存在则创建*        sharedInstance = getSingleton(beanName, () -> {* 	    	return createBean(beanName, mbd, args);*        });* 	    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);*    }*    else if (mbd.isPrototype()) {*       // 直接创建*       prototypeInstance = createBean(beanName, mbd, args);*       beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);*    }*    // 其他作用域*    else {*        // 获取自定义作用域的Scope对象*        String scopeName = mbd.getScope();* 		  Scope scope = this.scopes.get(scopeName);* 		  // 调用Scope的get方法,从该作用域中获取Bean对象,如果不存在则创建对象*          {@link BeanLifecycleWrapper#get(String, ObjectFactory)}* 		  Object scopedInstance = scope.get(beanName, () -> {* 		  		return createBean(beanName, mbd, args);*          });* 		  beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);*    }* </pre>*/// 自定义作用域的get方法,从指定位置返回该作用域的Bean对象,这个Bean对象由自己管理@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {// 缓存当前BeanBeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));// 加读写锁this.locks.putIfAbsent(name, new ReentrantReadWriteLock());try {return value.getBean();} catch (RuntimeException e) {this.errors.put(name, e);throw e;}}
}// Bean的Scope生命周期包装类
public class BeanLifecycleWrapper {// BeanNameprivate final String name;// 创建Bean的工厂private final ObjectFactory<?> objectFactory;// Bean对象private volatile Object bean;// 销毁Bean执行的任务/*** 在创建的Bean的时候,如果Bean是DisposableBean类型,会自动注册一个任务,就是将该Bean执行所有的DestructionAwareBeanPostProcessor,处理一些销毁逻辑* {@link AbstractBeanFactory#registerDisposableBeanIfNecessary(String, Object, RootBeanDefinition)}*/private Runnable callback;BeanLifecycleWrapper(String name, ObjectFactory<?> objectFactory) {this.name = name;this.objectFactory = objectFactory;}public String getName() {return this.name;}public void setDestroyCallback(Runnable callback) {this.callback = callback;}// 获取Bean对象public Object getBean() {// 是否存在Bean对象if (this.bean == null) {synchronized (this.name) {// 使用对象工厂创建Bean对象if (this.bean == null) {// 保存Bean对象this.bean = this.objectFactory.getObject();}}}return this.bean;}// 销毁Bean对象public void destroy() {// 如果没有设置销毁时需要执行的任务,则不处理任何东西if (this.callback == null) {return;}synchronized (this.name) {// 执行指定的销毁任务Runnable callback = this.callback;if (callback != null) {callback.run();}// 清空当前Bean对象this.callback = null;this.bean = null;}}
}public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {// 所有继承该类的作用域保存对应Bean的缓存对象private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(new StandardScopeCache());// 销毁对象@Overridepublic void destroy() {// 清空所有该作用域的缓存BeanCollection<BeanLifecycleWrapper> wrappers = this.cache.clear();// 遍历所有该作用域的Bean的包装对象for (BeanLifecycleWrapper wrapper : wrappers) {// 调用每一个Bean包装器的销毁方法wrapper.destroy();}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;// 注册当前作用域为refreshbeanFactory.registerScope(this.name, this);}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {// 获取所有的Beanfor (String name : registry.getBeanDefinitionNames()) {BeanDefinition definition = registry.getBeanDefinition(name);if (definition instanceof RootBeanDefinition root) {// 如果是作用域代理的Beanif (root.getDecoratedDefinition() != null && root.hasBeanClass() && root.getBeanClass() == ScopedProxyFactoryBean.class) {// 并且当前Bean的作用域正好是refreshif (getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {// 修改当前Bean的BeanClass,增强一下,从ScopedProxyFactoryBean变成LockedScopedProxyFactoryBean// 它是继承ScopedProxyFactoryBean,就是在执行的过程中添加了读写锁root.setBeanClass(LockedScopedProxyFactoryBean.class);// 添加构造参数root.getConstructorArgumentValues().addGenericArgumentValue(this);root.setSynthetic(true);}}}}}
}/*** {@link com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListener(java.lang.String, java.lang.String)}* 配置刷新的监听器,该RefreshEvent是由对应的配置中心发布的事件,例如Nacos发生匹配变化的时候,就会触发一个RefreshEvent事件*/
public class RefreshEventListener implements SmartApplicationListener {// 配置数据上下文的刷新器private ConfigDataContextRefresher refresh;// 配置是否就绪private AtomicBoolean ready = new AtomicBoolean(false);public RefreshEventListener(ContextRefresher refresh) {this.refresh = refresh;}@Overridepublic boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {return ApplicationReadyEvent.class.isAssignableFrom(eventType) || RefreshEvent.class.isAssignableFrom(eventType);}@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationReadyEvent) {// 标记已就绪this.ready.compareAndSet(false, true);}if (event instanceof RefreshEvent) {// 已就绪才会处理if (this.ready.get()) {// 使用配置数据的上下文对象进行刷新Set<String> keys = this.refresh.refresh();}}}
}@Component
public class ConfigurationPropertiesBeans implements BeanPostProcessor, ApplicationContextAware {// 标注了ConfigurationProperties的Bean对象private Map<String, ConfigurationPropertiesBean> beans = new HashMap<>();// Spring上下文private ApplicationContext applicationContext;// Bean工厂private ConfigurableListableBeanFactory beanFactory;// 刷新作用域private String refreshScope;// 刷新作用域是否初始化了private boolean refreshScopeInitialized;// 父容器中的ConfigurationPropertiesBeansprivate ConfigurationPropertiesBeans parent;// 初始化当前上下文中父容器的配置类ConfigurationProperties的Bean对象@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;if (applicationContext.getAutowireCapableBeanFactory() instanceof ConfigurableListableBeanFactory) {this.beanFactory = (ConfigurableListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();}AutowireCapableBeanFactory bf = applicationContext.getParent().getAutowireCapableBeanFactory();// 如果存在父容器if (applicationContext.getParent() != null && bf instanceof ConfigurableListableBeanFactory listable) {// 从父容器中获取获取ConfigurationPropertiesBeansString[] names = listable.getBeanNamesForType(ConfigurationPropertiesBeans.class);// 如果只存在一个if (names.length == 1) {// 将该父容器中的ConfigurationPropertiesBeans保存this.parent = (ConfigurationPropertiesBeans) listable.getBean(names[0]);// 将父容器中的ConfigurationProperties的Bean对象保存this.beans.putAll(this.parent.beans);}}}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 判断容器中的bean是不是refresh作用域// 如果是,不处理if (this.isRefreshScoped(beanName)) {return bean;}// 如果不是refresh作用域,获取执行beanName的Bean对象,标注了ConfigurationProperties才会返回对象// 如果不存在ConfigurationProperties注解,返回nullConfigurationPropertiesBean propertiesBean = ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName);// 如果是属性配置Beanif (propertiesBean != null) {// 保存当前配置Bean信息this.beans.put(beanName, propertiesBean);}return bean;}// 判断容器中的bean是不是refresh作用域private boolean isRefreshScoped(String beanName) {// 如果刷新作用域为空(默认)并且没有被初始化过if (this.refreshScope == null && !this.refreshScopeInitialized) {// 开始初始化,标记初始化this.refreshScopeInitialized = true;// 遍历当前容器中的保存的所有作用域(例如,单例,原型,会话等等for (String scope : this.beanFactory.getRegisteredScopeNames()) {// 从容器中获取RefreshScope的Bean对象,如果当前作用域是RefreshScope,则保存该作用域if (this.beanFactory.getRegisteredScope(scope) instanceof org.springframework.cloud.context.scope.refresh.RefreshScope) {this.refreshScope = scope;break;}}}// 如果beanName为空或者没找到RefreshScope的Bean,表示不是RefreshScopeif (beanName == null || this.refreshScope == null) {return false;}// 如果当前Bean工厂包含指定的Bean,并且该Bean的作用域正是refresh(该Bean标注了@RefreshScope),返回truereturn this.beanFactory.containsBeanDefinition(beanName) && this.refreshScope.equals(this.beanFactory.getBeanDefinition(beanName).getScope());}// 获取所有标注了ConfigurationProperties的Bean对象的BeanNamepublic Set<String> getBeanNames() {return new HashSet<>(this.beans.keySet());}}// 配置属性Bean的重新绑定器
@Component
@ManagedResource
public class ConfigurationPropertiesRebinder implements ApplicationContextAware, ApplicationListener<EnvironmentChangeEvent> {// 需要重新绑定的配置类private ConfigurationPropertiesBeans beans;// Spring上下文对象private ApplicationContext applicationContext;// 重新绑定过程中的异常private Map<String, Exception> errors = new ConcurrentHashMap<>();// 需要将哪些配置Bean进行重新绑定public ConfigurationPropertiesRebinder(ConfigurationPropertiesBeans beans) {this.beans = beans;}// 重新绑定所有配置类@ManagedOperationpublic void rebind() {// 清空所有的异常信息this.errors.clear();// 遍历当前需要重新绑定的配置类Bean名称for (String name : this.beans.getBeanNames()) {// 重新绑定this.rebind(name);}}// 重新绑定指定配置类@ManagedOperationpublic boolean rebind(String name) {// 如果当前名称不在需要重新绑定的名单中,不处理if (!this.beans.getBeanNames().contains(name)) {return false;}ApplicationContext appContext = this.applicationContext;// 遍历所有层级的Spring上下文while (appContext != null) {// 如果包含该配置Bean,进行重新绑定if (appContext.containsLocalBean(name)) {return rebind(name, appContext);}// 如果当前容器不包含当前配置Bean,则查找父容器else {appContext = appContext.getParent();}}return false;}// 根据指定类型绑定public boolean rebind(Class type) {// 获取指定类型的配置BeanString[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.applicationContext, type);if (beanNamesForType.length > 0) {// 获取到该Bean最原始的BeanName,因为该Bean可能设置了作用域代理String name = beanNamesForType[0];// 如果是作用域代理的Beanif (ScopedProxyUtils.isScopedTarget(name)) {// 获取最原始的bean名称,因为作用域代理的beanName经过加工name = ScopedProxyUtils.getOriginalBeanName(name);}// 开始绑定return rebind(name, this.applicationContext);}return false;}// 真正的开始查询绑定private boolean rebind(String name, ApplicationContext appContext) {try {// 获取需要绑定的BeanObject bean = appContext.getBean(name);// 获取到原始对象if (AopUtils.isAopProxy(bean)) {bean = ProxyUtils.getTargetObject(bean);}if (bean != null) {// 如果设置了不支持刷新,不处理if (getNeverRefreshable().contains(bean.getClass().getName())) {return false;}// 销毁该BeanappContext.getAutowireCapableBeanFactory().destroyBean(bean);// 重新初始化该BeanappContext.getAutowireCapableBeanFactory().initializeBean(bean, name);return true;}} catch (RuntimeException e) {// 保存绑定的异常信息this.errors.put(name, e);throw e;} catch (Exception e) {this.errors.put(name, e);throw new IllegalStateException("Cannot rebind to " + name, e);}return false;}// 不支持刷新@ManagedAttributepublic Set<String> getNeverRefreshable() {String neverRefresh = this.applicationContext.getEnvironment().getProperty("spring.cloud.refresh.never-refreshable", "com.zaxxer.hikari.HikariDataSource");return StringUtils.commaDelimitedListToSet(neverRefresh);}@ManagedAttributepublic Set<String> getBeanNames() {return new HashSet<>(this.beans.getBeanNames());}// 如何监听到环境对象发生了改变,需要重新绑定@Overridepublic void onApplicationEvent(EnvironmentChangeEvent event) {// 在配置属性发生改变的时候,会发布这个事件/*** {@link ConfigDataContextRefresher#refreshEnvironment}*/if (this.applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {this.rebind();}}}

这篇关于SpringCloud中动态配置刷新@RefreshScope的源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007360

相关文章

SQL server配置管理器找不到如何打开它

《SQLserver配置管理器找不到如何打开它》最近遇到了SQLserver配置管理器打不开的问题,尝试在开始菜单栏搜SQLServerManager无果,于是将自己找到的方法总结分享给大家,对SQ... 目录方法一:桌面图标进入方法二:运行窗口进入方法三:查找文件路径方法四:检查 SQL Server 安

SpringMVC获取请求参数的方法

《SpringMVC获取请求参数的方法》:本文主要介绍SpringMVC获取请求参数的方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下... 目录1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、@RequestParam4、@

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

springboot项目中常用的工具类和api详解

《springboot项目中常用的工具类和api详解》在SpringBoot项目中,开发者通常会依赖一些工具类和API来简化开发、提高效率,以下是一些常用的工具类及其典型应用场景,涵盖Spring原生... 目录1. Spring Framework 自带工具类(1) StringUtils(2) Coll

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

SpringQuartz定时任务核心组件JobDetail与Trigger配置

《SpringQuartz定时任务核心组件JobDetail与Trigger配置》Spring框架与Quartz调度器的集成提供了强大而灵活的定时任务解决方案,本文主要介绍了SpringQuartz定... 目录引言一、Spring Quartz基础架构1.1 核心组件概述1.2 Spring集成优势二、J