当前位置: 代码网 > it编程>编程语言>Java > Spring Boot中@RefreshScope注解怎么用?动态刷新配置详解

Spring Boot中@RefreshScope注解怎么用?动态刷新配置详解

2026年08月15日 Java 我要评论
@value注解可以在项目启动时获取到配置中心的值,但是如果在nacos配置中心后台修改了值,此时项目是无法动态感知修改后的值,需要利用@refreshscope注解来实现动态感知。@refreshs

@value注解可以在项目启动时获取到配置中心的值,但是如果在nacos配置中心后台修改了值,此时项目是无法动态感知修改后的值,需要利用@refreshscope注解来实现动态感知。

@refreshscope实现动态感知的使用

只需要在类上加上@refreshscope注解即可。

@restcontroller
@requestmapping("order")
@refreshscope
public class ordercontroller {

    @value("${user.age}")
    private integer age;

    @getmapping("age")
    public integer getage() {
        return age;
    }
}

@refreshscope导致@scheduled定时任务失效问题

演示问题

开启定时任务功能

@springbootapplication
@enablescheduling // 开启定时任务功能
public class orderserviceapplication {

    public static void main(string[] args) throws interruptedexception {
        springapplication.run(orderserviceapplication.class, args);
    }

}

当在配置中心变更属性后,定时任务失效,当再次访问/order/age3地址后,定时任务又生效。

@restcontroller
@requestmapping("/order")
@refreshscope  // 动态感知修改后的值
public class scheduledcontroller {

    @value("${user.age}")
    private integer age;

    @getmapping("/age3")
    public integer getage() {
        return age;
    }

    //触发@refreshscope执行逻辑会导致@scheduled定时任务失效
    @scheduled(cron = "*/3 * * * * ?")  //定时任务每隔3s执行一次
    public void execute() {
        system.out.println("定时任务正常执行。。。。。。");
    }
}

解决方案

实现spring事件监听器,监听refreshscoperefreshedevent事件,监听方法中进行一次定时方法的调用,这样bean就会被创建。

@restcontroller
@requestmapping("/order")
@refreshscope  //动态感知修改后的值
public class scheduledcontroller implements applicationlistener<refreshscoperefreshedevent> {

    @value("${user.age}")
    private integer age;

    @getmapping("/age3")
    public integer getage() {
        return age;
    }

    //触发@refreshscope执行逻辑会导致@scheduled定时任务失效
    @scheduled(cron = "*/3 * * * * ?")  //定时任务每隔3s执行一次
    public void execute() {
        system.out.println("定时任务正常执行。。。。。。");
    }

    @override
    public void onapplicationevent(refreshscoperefreshedevent refreshscoperefreshedevent) {
        // 不要下面这行也行
		execute();
    }
}

@refreshscope实现原理

  1. 怎么实现属性的动态刷新的?
  2. @refreshscope和@scheduled一起使用,为什么定时任务会停止?

@refreshscope注解

@refreshscope上面有@scope注解,其内部就一个属性默认scopedproxymode.target_class。

@target({ elementtype.type, elementtype.method })
@retention(retentionpolicy.runtime)
@scope("refresh")
@documented
public @interface refreshscope {

	/**
	 * @see scope#proxymode()
	 * @return proxy mode
	 */
	scopedproxymode proxymode() default scopedproxymode.target_class;

}

bean的实例化过程

被@refreshscope注解的类,最终会调用refreshscope的get()方法实例化bean。

org.springframework.beans.factory.support.abstractbeanfactory#dogetbean

... ...
if (mbd.issingleton()) {
	// 单例bean的实例化
}
else if (mbd.isprototype()) {
	// 多例bean的实例化
}
else {
	// 自定义作用域的bean的实例化
	// scopename为refresh
	string scopename = mbd.getscope();
	if (!stringutils.haslength(scopename)) {
		throw new illegalstateexception("no scope name defined for bean ´" + beanname + "'");
	}
	// scope为refreshscope
	scope scope = this.scopes.get(scopename);
	if (scope == null) {
		throw new illegalstateexception("no scope registered for scope name '" + scopename + "'");
	}
	try {
		object scopedinstance = scope.get(beanname, () -> {
			beforeprototypecreation(beanname);
			try {
				return createbean(beanname, mbd, args);
			}
			finally {
				afterprototypecreation(beanname);
			}
		});
		bean = getobjectforbeaninstance(scopedinstance, name, beanname, mbd);
	}
	catch (illegalstateexception ex) {
		throw new beancreationexception(beanname,
										"scope '" + scopename + "' is not active for the current thread; consider " +
										"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
										ex);
	}
}

refreshscope.get()

@refreshscope作用域的bean会在第一次创建时进行缓存,包装了一个内部类 beanlifecyclewrappercache来对加了@refreshscope从而创建的对象进行缓存,使其在不刷新时获取的都是同一个对象。

org.springframework.cloud.context.scope.genericscope#get

public object get(string name, objectfactory<?> objectfactory) {
	beanlifecyclewrapper 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;
	}
}

知道了对象是缓存的,所以在配置修改后只需要清除缓存,重新创建就好了。

refresheventlistener处理容器的刷新事件

当配置中心的内容变更后,nacos客户端收到变更会触发refreshevent事件。

org.springframework.cloud.endpoint.event.refresheventlistener#handle(org.springframework.cloud.endpoint.event.refreshevent)

public void handle(refreshevent event) {
	if (this.ready.get()) { // don't handle events before app is ready
		log.debug("event received " + event.geteventdesc());
		set<string> keys = this.refresh.refresh();
		log.info("refresh keys changed: " + keys);
	}
}

org.springframework.cloud.context.refresh.contextrefresher#refresh

public synchronized set<string> refresh() {
	set<string> keys = refreshenvironment();
	this.scope.refreshall();
	return keys;
}

org.springframework.cloud.context.scope.refresh.refreshscope#refreshall

public void refreshall() {
	super.destroy();
	this.context.publishevent(new refreshscoperefreshedevent());
}

org.springframework.cloud.context.scope.genericscope#destroy()

public void destroy() {
	list<throwable> errors = new arraylist<throwable>();
	// 清理缓存
	collection<beanlifecyclewrapper> wrappers = this.cache.clear();
	for (beanlifecyclewrapper wrapper : wrappers) {
		try {
			lock lock = this.locks.get(wrapper.getname()).writelock();
			lock.lock();
			try {
				// 销毁实例
				wrapper.destroy();
			}
			finally {
				lock.unlock();
			}
		}
		catch (runtimeexception e) {
			errors.add(e);
		}
	}
	if (!errors.isempty()) {
		throw wrapifnecessary(errors.get(0));
	}
	this.errors.clear();
}

在下一次使用对象的时候,代理对象中获取目标对象的时候会调用genericscope.get()方法创建一个新的对象,并存入缓存中,此时新对象因为spring的装配机制就是新的属性了。

定时任务停止的原因

@refreshscope和@scheduled一起使用,定时任务会停止并不是因为缓存失效了,而是因为容器刷新时会进行定时任务的取消。

在上面容器刷新时会调用beanlifecyclewrapper的destroy()方法。

org.springframework.cloud.context.scope.genericscope.beanlifecyclewrapper#destroy

public void destroy() {
	if (this.callback == null) {
		return;
	}
	synchronized (this.name) {
		runnable callback = this.callback;
		if (callback != null) {
			callback.run();
		}
		this.callback = null;
		this.bean = null;
	}
}

看似这个方法啥也没干,callback对象为null,实际上callback是有值的,在bean实例化是会设置值。

org.springframework.beans.factory.support.abstractbeanfactory#registerdisposablebeanifnecessary

protected void registerdisposablebeanifnecessary(string beanname, object bean, rootbeandefinition mbd) {
	accesscontrolcontext acc = (system.getsecuritymanager() != null ? getaccesscontrolcontext() : null);
	if (!mbd.isprototype() && requiresdestruction(bean, mbd)) {
		if (mbd.issingleton()) {
			// register a disposablebean implementation that performs all destruction
			// work for the given bean: destructionawarebeanpostprocessors,
			// disposablebean interface, custom destroy method.
			registerdisposablebean(beanname,
								   new disposablebeanadapter(bean, beanname, mbd, getbeanpostprocessors(), acc));
		}
		else {
			// a bean with a custom scope...
			scope scope = this.scopes.get(mbd.getscope());
			if (scope == null) {
				throw new illegalstateexception("no scope registered for scope name '" + mbd.getscope() + "'");
			}
			// callback为disposablebeanadapter
			scope.registerdestructioncallback(beanname,
											  new disposablebeanadapter(bean, beanname, mbd, getbeanpostprocessors(), acc));
		}
	}
}

callback为disposablebeanadapter,当容器刷新时会调用disposablebeanadapter的run()方法。

org.springframework.beans.factory.support.disposablebeanadapter#run

public void run() {
	destroy();
}

@override
public void destroy() {
	if (!collectionutils.isempty(this.beanpostprocessors)) {
		for (destructionawarebeanpostprocessor processor : this.beanpostprocessors) {
			// scheduledannotationbeanpostprocessor
			processor.postprocessbeforedestruction(this.bean, this.beanname);
		}
	}
... ...
}
public 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();
		}
	}
}

最后任务被删了,取消了,所以不会执行了。

那为什么监听了refreshscoperefreshedevent事件,定时任务又正常启动了呢?

因为容器刷新后会触发refreshscoperefreshedevent事件,spring容器会查找所有监听refreshscoperefreshedevent事件的bean,并调用其onapplicationevent()方法,这样会触发scheduledcontroller的实例化过程。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com