天天看點

RefreshScope源碼分析@RefreshScope

springcloud的git配置中可以使用

@RefreshScope

+

POST [/actuator/refresh]

調用actuator的refresh來實作配置的熱更新。

@RefreshScope

springcloud描述:
A Spring @Bean that is marked as @RefreshScope will get special treatment when there is a configuration change. This addresses 
the problem of stateful beans that only get their configuration injected when they are initialized. For instance if a DataSource has open connections when the 
database URL is changed via the Environment, we probably want the holders of those connections to be able to complete what they are doing. Then the next time someone borrows a 
connection from the pool he gets one with the new URL.

Refresh scope beans are lazy proxies that initialize when they are used (i.e. when a method is called), and the scope acts as a cache of initialized values. To force a 
bean to re-initialize on the next method call you just need to invalidate its cache entry.           

@RefreshScope

标記的Bean是延遲加載的,每次通路時都會被強制初始化,這一點在

RefreshScope

類的doc中也有說明:

A Scope implementation that allows for beans to be refreshed dynamically at runtime
 * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then
 * the next time the bean is accessed (i.e. a method is executed) a new instance is
 * created.            

refresh scope bean則是在使用時(即調用方法時)初始化的惰性代理,充當已初始化值的緩存。如果要強制被标記bean在下一個方法調用上重新初始化,隻需使其緩存項無效即可。

RefreshScope類:

/**
 * <p>
 * A Scope implementation that allows for beans to be refreshed dynamically at runtime
 * (see {@link #refresh(String)} and {@link #refreshAll()}). If a bean is refreshed then
 * the next time the bean is accessed (i.e. a method is executed) a new instance is
 * created. All lifecycle methods are applied to the bean instances, so any destruction
 * callbacks that were registered in the bean factory are called when it is refreshed, and
 * then the initialization callbacks are invoked as normal when the new instance is
 * created. A new bean instance is created from the original bean definition, so any
 * externalized content (property placeholders or expressions in string literals) is
 * re-evaluated when it is created.
 * </p>
 *
 * <p>
 * Note that all beans in this scope are <em>only</em> initialized when first accessed, so
 * the scope forces lazy initialization semantics. The implementation involves creating a
 * proxy for every bean in the scope, so there is a flag
 * {@link #setProxyTargetClass(boolean) proxyTargetClass} which controls the proxy
 * creation, defaulting to JDK dynamic proxies and therefore only exposing the interfaces
 * implemented by a bean. If callers need access to other methods then the flag needs to
 * be set (and CGLib present on the classpath). Because this scope automatically proxies
 * all its beans, there is no need to add <code>&lt;aop:auto-proxy/&gt;</code> to any bean
 * definitions.
 * </p>
 *
 * <p>
 * The scoped proxy approach adopted here has a side benefit that bean instances are
 * automatically {@link Serializable}, and can be sent across the wire as long as the
 * receiver has an identical application context on the other side. To ensure that the two
 * contexts agree that they are identical they have to have the same serialization id. One
 * will be generated automatically by default from the bean names, so two contexts with
 * the same bean names are by default able to exchange beans by name. If you need to
 * override the default id then provide an explicit {@link #setId(String) id} when the
 * Scope is declared.
 * </p>
 *
 * @author Dave Syer
 *
 * @since 3.1
 *
 */
@ManagedResource
public class RefreshScope extends GenericScope
        implements ApplicationContextAware, Ordered {

    private ApplicationContext context;
    private BeanDefinitionRegistry registry;
    private boolean eager = true;
    private int order = Ordered.LOWEST_PRECEDENCE - 100;

    /**
     * Create a scope instance and give it the default name: "refresh".
     */
    public RefreshScope() {
        super.setName("refresh");
    }

    @ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
    public boolean refresh(String name) {
        if (!name.startsWith(SCOPED_TARGET_PREFIX)) {
            // User wants to refresh the bean with this name but that isn't the one in the
            // cache...
            name = SCOPED_TARGET_PREFIX + name;
        }
        // Ensure lifecycle is finished if bean was disposable
        if (super.destroy(name)) {
            this.context.publishEvent(new RefreshScopeRefreshedEvent(name));
            return true;
        }
        return false;
    }

    @ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
    public void refreshAll() {
        super.destroy();
        this.context.publishEvent(new RefreshScopeRefreshedEvent());
    }
}           

當調用

refresh

方法時會調用

super.destroy(String name)

方法,父類實作如下:

public class GenericScope implements Scope, BeanFactoryPostProcessor,
        BeanDefinitionRegistryPostProcessor, DisposableBean {
        
    private BeanLifecycleWrapperCache cache = new BeanLifecycleWrapperCache(
            new StandardScopeCache());
            
    /**
     * Destroy the named bean (i.e. flush it from the cache by default).
     *
     * @param name the bean name to flush
     * @return true if the bean was already cached, false otherwise
     */
    protected boolean destroy(String name) {
        BeanLifecycleWrapper wrapper = this.cache.remove(name);
        if (wrapper != null) {
            Lock lock = locks.get(wrapper.getName()).writeLock();
            lock.lock();
            try {
                wrapper.destroy();
            }
            finally {
                lock.unlock();
            }
            this.errors.remove(name);
            return true;
        }
        return false;
    }
    
    @Override
    public void destroy() {
        List<Throwable> errors = new ArrayList<Throwable>();
        Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
        for (BeanLifecycleWrapper wrapper : wrappers) {
            try {
                Lock lock = 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();
    }
}

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        BeanLifecycleWrapper value = this.cache.put(name,
                new BeanLifecycleWrapper(name, objectFactory));
        locks.putIfAbsent(name, new ReentrantReadWriteLock());
        try {
            return value.getBean();
        }
        catch (RuntimeException e) {
            this.errors.put(name, e);
            throw e;
        }
    }

    @Override
    public Object remove(String name) {
        BeanLifecycleWrapper value = this.cache.remove(name);
        if (value == null) {
            return null;
        }
        // Someone might have added another object with the same key, but we
        // keep the method contract by removing the
        // value we found anyway
        return value.getBean();
    }           

BeanLifecycleWrapperCache

用于緩存被标記的bean,當destroy執行時,會從

BeanLifecycleWrapperCache

中删除或全部清除bean。在調用get方法時會重新new一個對象放入緩存中。

跟蹤代碼,可得到調用

refresh

時:

/**
 * @author Dave Syer
 * @author Venil Noronha
 */
@Endpoint(id = "refresh")
public class RefreshEndpoint {

    private ContextRefresher contextRefresher;

    public RefreshEndpoint(ContextRefresher contextRefresher) {
        this.contextRefresher = contextRefresher;
    }

    @WriteOperation
    public Collection<String> refresh() {
        Set<String> keys = contextRefresher.refresh();
        return keys;
    }
}           

得到:

public class ContextRefresher {
    public synchronized Set<String> refresh() {
        Map<String, Object> before = extract(
                this.context.getEnvironment().getPropertySources());
        addConfigFilesToEnvironment();
        Set<String> keys = changes(before,
                extract(this.context.getEnvironment().getPropertySources())).keySet();
        this.context.publishEvent(new EnvironmentChangeEvent(context, keys));
        this.scope.refreshAll();
        return keys;
    }
}           

是此處調用了RefreshScope的refreshAll方法。

Endpoint

@Endpoint

用于标記為actuator終端,提供應用運作資訊:

Identifies a type as being an actuator endpoint that provides information about the
 * running application. Endpoints can be exposed over a variety of technologies including
 * JMX and HTTP.           

例如:

/**
 * @author Dave Syer
 * @author Venil Noronha
 */
@Endpoint(id = "refresh")
public class RefreshEndpoint {

    private ContextRefresher contextRefresher;

    public RefreshEndpoint(ContextRefresher contextRefresher) {
        this.contextRefresher = contextRefresher;
    }

    @WriteOperation
    public Collection<String> refresh() {
        Set<String> keys = contextRefresher.refresh();
        return keys;
    }

}           

再看

@Endpoint

注解:

/**
 * Identifies a type as being an actuator endpoint that provides information about the
 * running application. Endpoints can be exposed over a variety of technologies including
 * JMX and HTTP.
 * <p>
 * Most {@code @Endpoint} classes will declare one or more
 * {@link ReadOperation @ReadOperation}, {@link WriteOperation @WriteOperation},
 * {@link DeleteOperation @DeleteOperation} annotated methods which will be automatically
 * adapted to the exposing technology (JMX, Spring MVC, Spring WebFlux, Jersey etc.).
 * <p>
 * {@code @Endpoint} represents the lowest common denominator for endpoints and
 * intentionally limits the sorts of operation methods that may be defined in order to
 * support the broadest possible range of exposure technologies. If you need deeper
 * support for a specific technology you can either write an endpoint that is
 * {@link FilteredEndpoint filtered} to a certain technology, or provide
 * {@link EndpointExtension extension} for the broader endpoint.
 *
 * @author Andy Wilkinson
 * @author Phillip Webb
 * @since 2.0.0
 * @see EndpointExtension
 * @see FilteredEndpoint
 * @see EndpointDiscoverer
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {

    /**
     * The id of the endpoint.
     * @return the id
     */
    String id() default "";

    /**
     * If the endpoint should be enabled or disabled by default.
     * @return {@code true} if the endpoint is enabled by default
     */
    boolean enableByDefault() default true;

}