Analysis of Ctrip Apollo Hot Release Mechanism

Hits: 0

background

When using [apollo] recently , I found that some of the values ​​annotated with @Value can be updated hotly, while others cannot; for the configuration class of ConfigurationProperties, updating is not supported at all. So I want to understand the hot release mechanism of apollo and see how to realize the hot release.

Official introduction

Apollo Configuration Center Design

As can be seen from the above three figures: [hot update] is based on http long connection. When the configuration is modified on the apollo management interface, the client will be notified of the change, and then the client will pull the latest configuration.

The official document only introduces the hot update mechanism, but it does not mention how to update the properties of spring beans after the client gets the latest configuration.

SpringValueProcessor code

The classes in the com.ctrip.framework.apollo.spring.annotation directory are as follows:
You can see that there is a SpringValueProcessor in it. Judging from the name, the role of this class seems to be to process beans annotated with @Value in spring.

public class SpringValueProcessor extends ApolloProcessor implements BeanFactoryPostProcessor, BeanFactoryAware {

  private static final Logger logger = LoggerFactory.getLogger(SpringValueProcessor.class);

  private final ConfigUtil configUtil;
  private final PlaceholderHelper placeholderHelper;
  private final SpringValueRegistry springValueRegistry;

  private BeanFactory beanFactory;
  private Multimap<String, SpringValueDefinition> beanName2SpringValueDefinitions;

  public SpringValueProcessor() {
    configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    placeholderHelper = SpringInjector.getInstance(PlaceholderHelper.class);
    springValueRegistry = SpringInjector.getInstance(SpringValueRegistry.class);
    beanName2SpringValueDefinitions = LinkedListMultimap.create();
  }

  ......
  ......

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
      super.postProcessBeforeInitialization(bean, beanName);
      processBeanPropertyValues(bean, beanName);
    }
    return bean;
  }


  @Override
  protected void processField(Object bean, String beanName, Field field) {
    // register @Value on field
    Value value = field.getAnnotation(Value.class);
    if (value == null) {
      return;
    }
    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());

    if (keys.isEmpty()) {
      return;
    }

    for (String key : keys) {
      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
      springValueRegistry.register(beanFactory, key, springValue);
      logger.debug("Monitoring {}", springValue);
    }
  }

  @Override
  protected void processMethod(Object bean, String beanName, Method method) {
    //register @Value on method
    Value value = method.getAnnotation(Value.class);
    if (value == null) {
      return;
    }
    //skip Configuration bean methods
    if (method.getAnnotation(Bean.class) != null) {
      return;
    }
    if (method.getParameterTypes().length != 1) {
      logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
          bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
      return;
    }

    Set<String> keys = placeholderHelper.extractPlaceholderKeys(value.value());

    if (keys.isEmpty()) {
      return;
    }

    for (String key : keys) {
      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
      springValueRegistry.register(beanFactory, key, springValue);
      logger.info("Monitoring {}", springValue);
    }
  }

  ......
  ......

}

SpringValueProcessor.postProcessBeforeInitialization

The important thing about SpringValueProcessor is the postProcessBeforeInitialization method:

@Override 
  public Object postProcessBeforeInitialization (Object bean, String beanName) 
      throws BeansException {
       // Whether automatic update is supported, if not, do nothing 
    if (configUtil.isAutoUpdateInjectedSpringPropertiesEnabled()) {
       // Call postProcessBeforeInitialization of the parent class 
      super .postProcessBeforeInitialization( bean, beanName);
       // Some processing of PropertyValues, do not need to care
      processBeanPropertyValues(bean, beanName);
    }
    return bean;
  }

The SpringValueProcessor.postProcessBeforeInitialization method calls the postProcessBeforeInitialization method of the parent class. The content of the parent class method is as follows:

public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {
  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName)
      throws BeansException {
    Class clazz = bean.getClass();
    for (Field field : findAllField(clazz)) {
      processField(bean, beanName, field);
    }
    for (Method method : findAllMethod(clazz)) {
      processMethod(bean, beanName, method);
    }
    return bean;
  }

  ......

  protected abstract void processField(Object bean, String beanName, Field field);
  protected abstract void processMethod(Object bean, String beanName, Method method);

  ......

  private List<Field> findAllField(Class clazz) {
    final List<Field> res = new LinkedList<>();
    ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
      @Override
      public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
        res.add(field);
      }
    });
    return res;
  }

  private List<Method> findAllMethod(Class clazz) {
    final List<Method> res = new LinkedList<>();
    ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
      @Override
      public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
        res.add(method);
      }
    });
    return res;
  }
}

What the postProcessBeforeInitialization method of the parent class does is to get all the properties of the bean, traverse and call processField; get all the methods, traverse and call processMethod.

The two methods processField and processMethod are implemented by subclasses.

SpringValueProcessor.proccessField

The proccessField of the SpringValueProcessor class does the following:

@Override protected void processField ( Object bean, String beanName, Field field ) {
     // Get the @Value annotation on the property Value

    value = field.getAnnotation(Value.class);
     // If there is no @Value annotation, no need to process if ( value == null ) {
       return ;

    }
    // According to the value of the @Value annotation, generate the configuration string that apollo needs to pay attention to 
    Set<String> keys = placeholderHelper.extractPlaceholderKeys( value . value ());

    if (keys.isEmpty()) {
      return;
    }
    // Register each concerned configuration string in the springValueRegistry object 
    for (String key : keys) {
      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false);
      springValueRegistry.register(beanFactory, key, springValue);
      logger.debug("Monitoring {}", springValue);
    }
  }

There is a point to pay attention to here:
Why does placeholderHelper.extractPlaceholderKeys return a Set, not a String? Isn’t @Value only one value can be specified?
@Value supports such usage:

@Value("${first.config:${second.config}}")

The default value mechanism of @Value can support variables, not just literal values. When the default value is a variable,
there will be multiple configurations that need to be concerned. Set just starts the role of deduplication to avoid repeated setting of values.

SpringValueProcessor.proccessMethod

SpringValueProcessor.proccessMethod does the following

@ Override
   protected  void  processMethod ( Object bean, String beanName, Method method ) {
     // Get the @Value annotation on the method 
    Value value = method. getAnnotation(Value. class);
     // Methods without @Value annotation directly ignore 
    if ( value == null ) {
       return ;
    }
    // If the method is annotated with @Bean, just ignore 
    if (method.getAnnotation(Bean.class) != null ) {
       return ;
    }
    // The method annotated with @Value is expected to be a setter method, and the setter method should only have one parameter 
    if (method.getParameterTypes().length != 1 ) {
      logger.error("Ignore @Value setter {}.{}, expecting 1 parameter, actual {} parameters",
          bean.getClass().getName(), method.getName(), method.getParameterTypes().length);
      return;
    }
    // Same as processField, according to the value of @Value annotation, get the configuration that needs attention 
    Set<String> keys = placeholderHelper.extractPlaceholderKeys( value . value ());

    if (keys.isEmpty()) {
      return;
    }
    // Traverse, register the concerned configuration to springValueRegistry 
    for (String key : keys) {
      SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, method, false);
      springValueRegistry.register(beanFactory, key, springValue);
      logger.info("Monitoring {}", springValue);
    }
  }

The code logic is basically the same as the processField method~

Preliminary Conclusions

apollo uses the postProcessBeforeInitialization method of BeanFactoryPostProcessor to traverse the spring beans, and register the fields and methods annotated with @Value in the bean in the SpringValueRegistry in the form of SpringValue.

SpringValue contains the information needed to update the value. For example, if @Value is added to the field, then Spring contains the bean object and the annotated Field. When @Value is added to a method, the annotated Method will be included.

At this point, you can basically guess the logic of the hot update:
1. The configuration changes
2. Find the SpringValue object that needs to be updated from the SpringValueRegistry
3. Use reflection to set the Field of the bean object held by SpringValue or call the method method

RemoteConfigRepository

The constructor of RemoteConfigRepository is as follows:

public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;
    m_configCache = new AtomicReference<>();
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    m_httpUtil = ApolloInjector.getInstance(HttpUtil.class);
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
    // Long-round training services that monitor configuration changes, obtain instances by injecting the framework, singletons
    remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
    m_longPollServiceDto = new AtomicReference<>();
    m_remoteMessages = new AtomicReference<>();
    m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
    m_configNeedForceRefresh = new AtomicBoolean(true);
    m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
        m_configUtil.getOnErrorRetryInterval() * 8 );
     // When a new RemoteConfigRepository object is created, it will try to synchronize the configuration once 
    this .trySync();
     // Periodically pull the configuration to update 
    this .schedulePeriodicRefresh();
     // See the method name You know, regular long polling, monitoring configuration changes 
    this .scheduleLongPollingRefresh();
  }

  private  void  scheduleLongPollingRefresh ()  {
     // It is to submit the long polling task of specifying namespace 
    remoteConfigLongPollService.submit(m_namespace, this );
  }

One thing worth noting:
The constructor of RemoteConfigRepository has one parameter, namespace. That is to say, the configuration update of each namespace is independent~

From the official documentation, we know that the hot release mechanism is implemented through the long polling mechanism, so the corresponding code should be in RemoteConfigLongPollService.

RemoteConfigLongPollService

The more important code is as follows:

public class RemoteConfigLongPollService {
  ......
  ......
  public  boolean  submit (String namespace, RemoteConfigRepository remoteConfigRepository)  {
     // Through the constructor of RemoteConfigRepository, we know that RemoteConfigLongPollService is a singleton, and RemoteConfigRepository is independent of each namespace, so the namespace that needs to be monitored and its corresponding RemoteConfigRepository are cached through map 
    boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
     // set the initial notification id
    m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
    // When the task is submitted for the first time, start polling 
    if (!m_longPollStarted.get()) {
      startLongPolling();
    }
    return added;
  }

  private  void  startLongPolling ()  {
     // Concurrency control 
    if (!m_longPollStarted.compareAndSet( false , true )) {
       //already started 
      return ;
    }
    try {
      final String appId = m_configUtil.getAppId();
      final String cluster = m_configUtil.getCluster();
      final String dataCenter = m_configUtil.getDataCenter();
      final String secret = m_configUtil.getAccessKeySecret();
      final long longPollingInitialDelayInMills = m_configUtil.getLongPollingInitialDelayInMills();
      m_longPollingService.submit(new Runnable() {
        @Override
        public void run() {
          if (longPollingInitialDelayInMills > 0) {
            try {
              logger.debug( "Long polling will start in {} ms." , longPollingInitialDelayInMills);
               // Request every longPollingInitialDelayInMills milliseconds
              TimeUnit.MILLISECONDS.sleep(longPollingInitialDelayInMills);
            } catch (InterruptedException e) {
              //ignore
            }
          }
          // The real long polling is in this method
          doLongPollingRefresh(appId, cluster, dataCenter, secret);
        }
      });
    } catch (Throwable ex) {
      m_longPollStarted.set(false);
      ApolloConfigException exception =
          new ApolloConfigException("Schedule long polling refresh failed", ex);
      Tracer.logError(exception);
      logger.warn(ExceptionUtil.getDetailMessage(exception));
    }
  }

 private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
    final Random random = new Random();
    ServiceDTO lastServiceDto = null;
    while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
      if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
        //wait at most 5 seconds
        try {
          TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
        }
      }
      Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "pollNotification");
      String url = null ;
       try {
         if (lastServiceDto == null ) {
           // get all service addresses
          List<ServiceDTO> configServices = getConfigServices();
          // Play the role of load balancing through random.nextInt
          lastServiceDto = configServices.get(random.nextInt(configServices.size()));
        }
        // Build the request address
        url =
            assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter,
                m_notifications);

        logger.debug("Long polling from {}", url);

        HttpRequest request = new HttpRequest(url);
        request.setReadTimeout(LONG_POLLING_READ_TIMEOUT);
        if (!StringUtils.isBlank(secret)) {
          Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
          request.setHeaders(headers);
        }

        transaction.addData( "Url" , url);
         // Send polling request 
        final HttpResponse<List<ApolloConfigNotification>> response =
            m_httpUtil.doGet(request, m_responseType);

        logger.debug( "Long polling response: {}, url: {}" , response.getStatusCode(), url);
         // The http status code is 200 and the body is not empty, which means there is an update 
        if (response.getStatusCode() = = 200 && response.getBody() != null ) {

          // Update the notification id in m_notifications
          updateNotifications(response.getBody());
          // Update the content that already exists in the m_remoteNotificationMessages cache. The update method is merge, whichever is larger.
          updateRemoteNotifications(response.getBody());
          transaction.addData( "Result" , response.getBody().toString());
           // The real update action is this method
          notify(lastServiceDto, response.getBody());
        }

        //The status code is 304. Whether or not lastServiceDto is set to null and lastServiceDto to null will trigger a new round of load balancing and randomly select a new service address 
        if (response.getStatusCode() == 304 && random.nextBoolean ()) {
          lastServiceDto = null;
        }

        m_longPollFailSchedulePolicyInSecond.success();
        transaction.addData("StatusCode", response.getStatusCode());
        transaction.setStatus(Transaction.SUCCESS);
      } catch (Throwable ex) {
        lastServiceDto = null;
        Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
        transaction.setStatus(ex);
        long sleepTimeInSecond = m_longPollFailSchedulePolicyInSecond.fail();
        logger.warn(
            "Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, long polling url: {}, reason: {}",
            sleepTimeInSecond, appId, cluster, assembleNamespaces(), url, ExceptionUtil.getDetailMessage(ex));
        try {
          TimeUnit.SECONDS.sleep(sleepTimeInSecond);
        } catch (InterruptedException ie) {
          //ignore
        }
      } finally {
        transaction.complete();
      }
    }
  }

 private void notify(ServiceDTO lastServiceDto, List<ApolloConfigNotification> notifications) {
    if (notifications == null || notifications.isEmpty()) {
      return;
    }
    for (ApolloConfigNotification notification : notifications) {
      String namespaceName = notification.getNamespaceName();
      //create a new list to avoid ConcurrentModificationException 
      // Get the RemoteConfigRepository corresponding to the namespace from m_longPollNamespaces and wrap it into a List. The official remark is to avoid the ConcurrentModificationException exception, but I don't quite understand how to use it, this exception will be thrown
      List<RemoteConfigRepository> toBeNotified =
          Lists.newArrayList(m_longPollNamespaces.get(namespaceName));
      // Call the updateRemoteNotifications method in the doLongPollingRefresh method, set ApolloNotificatiobMessages to m_remoteNotificationMessages, and now take it out
      ApolloNotificationMessages originalMessages = m_remoteNotificationMessages.get(namespaceName);
      ApolloNotificationMessages remoteMessages = originalMessages == null ? null : originalMessages.clone();
       //since .properties are filtered out by default, so we need to check if there is any listener for it 
      // updateRemoteNotifications and here are the same, for namespace For additional checks, such as namespace is zidongxiangxi, then also check zidongxiangxi.properties. This mechanism may need to be further understood.
      toBeNotified.addAll(m_longPollNamespaces
          .get(String.format( "%s.%s" , namespaceName, ConfigFileFormat.Properties.getValue())));
       for (RemoteConfigRepository remoteConfigRepository : toBeNotified) {
         try {
           // Finally, the onLongPollNotified method of RemoteConfigRepository is called to perform the update
          remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
        } catch (Throwable ex) {
          Tracer.logError(ex);
        }
      }
    }
  }
  ......
  ......
}

RemoteConfigLongPollService is only responsible for monitoring whether the configuration is updated, and the configuration update is finally completed by RemoteConfigRepository.
The RemoteConfigRepository.onLongPollNotified method code is very simple, as follows:

public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
    m_longPollServiceDto.set(longPollNotifiedServiceDto);
    m_remoteMessages.set(remoteMessages);
    m_executorService.submit(new Runnable() {
      @Override
      public void run() {
        m_configNeedForceRefresh.set(true);
        trySync();
      }
    });
  }

In fact, it is to set the forced refresh flag, and then perform an attempt to configure synchronization. The bottom layer of the trySync method is the sync method, which just does some exception handling.
The RemoteConfigRepository.sync code is as follows:

@Override
  protected synchronized void sync() {
    Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

    try {
      ApolloConfig previous = m_configCache.get();
      ApolloConfig current = loadApolloConfig();

      // The last version number and the current version number are not equal to need to update 
      if (previous != current) {
        logger.debug( "Remote Config refreshed!" );
         // Update version number 
        m_configCache.set (current);
         // Trigger update this .fireRepositoryChange (m_namespace, this .getConfig());

      }

      if (current != null) {
        Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
            current.getReleaseKey());
      }

      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      transaction.setStatus(ex);
      throw ex;
    } finally {
      transaction.complete();
    }
  }

fireRepositoryChange is a method of RemoteConfigRepository’s parent class AbstractConfigRepository:

protected void fireRepositoryChange(String namespace, Properties newProperties) {
    for (RepositoryChangeListener listener : m_listeners) {
      try {
        listener.onRepositoryChange(namespace, newProperties);
      } catch (Throwable ex) {
        Tracer.logError(ex);
        logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
      }
    }
  }

Configuration update is to traverse all RepositoryChangeListeners and call the onRepositoryChange method of these RepositoryChangeListeners.

DefaultConfig

DefaultConfig implements the RepositoryChangeListener interface. In the initialize method, it registers itself in the time monitor of ConfigRepository.

public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
  ......
   public DefaultConfig(String namespace, ConfigRepository configRepository) {
    m_namespace = namespace;
    m_resourceProperties = loadFromResource(m_namespace);
    m_configRepository = configRepository;
    m_configProperties = new AtomicReference<>();
    m_warnLogRateLimiter = RateLimiter.create(0.017); // 1 warning log output per minute
    initialize();
  }

  private void initialize() {
    try {
      updateConfig(m_configRepository.getConfig(), m_configRepository.getSourceType());
    } catch (Throwable ex) {
      Tracer.logError(ex);
      logger.warn("Init Apollo Local Config failed - namespace: {}, reason: {}.",
          m_namespace, ExceptionUtil.getDetailMessage(ex));
    } finally {
       //This is it! ! ! Register yourself in the event listener of 
      ConfigRepository m_configRepository.addChangeListener( this );
    }
  }

  // Implement the onRepositoryChange method of the RepositoryChangeListener interface 
  @Override 
  public  synchronized  void  onRepositoryChange (String namespace, Properties newProperties)  {
     // The old and new configurations are the same, no processing 
    if (newProperties.equals(m_configProperties.get())) {
       return ;
    }

    ConfigSourceType sourceType = m_configRepository.getSourceType();
    Properties newConfigProperties = propertiesFactory.getPropertiesInstance();
    newConfigProperties.putAll (newProperties);
    // Calculate the difference between the old and new configuration
    Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties, sourceType);

    //No difference, no processing 
    if (actualChanges.isEmpty()) {
       return ;
    }
    // fireConfigChange triggers configuration update 
    this .fireConfigChange( new ConfigChangeEvent(m_namespace, actualChanges));

    Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
  }

  protected  void  fireConfigChange ( final ConfigChangeEvent changeEvent)  {
     // Finally, call ConfigChangeListener to update the configuration. . . .       
    for ( final ConfigChangeListener listener : m_listeners) {
       // uninteresting events are not handled 
      if (!isConfigChangeListenerInterested(listener, changeEvent)) {
         continue ;
      }
      m_executorService.submit(new Runnable() {
        @Override
        public void run() {
          String listenerName = listener.getClass().getName();
          Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);
          try {
            listener.onChange(changeEvent);
            transaction.setStatus(Transaction.SUCCESS);
          } catch (Throwable ex) {
            transaction.setStatus(ex);
            Tracer.logError(ex);
            logger.error("Failed to invoke config change listener {}", listenerName, ex);
          } finally {
            transaction.complete();
          }
        }
      });
    }
  }
  ......
}

Nested a bit deep, DefaultConfig itself does not directly perform configuration updates, but triggers the ConfigChangeListener registered to them. . . .

AutoUpdateConfigChangeListener

In apollo, automatic updates are implemented by AutoUpdateConfigChangeListener.

public class AutoUpdateConfigChangeListener implements ConfigChangeListener{

  ......

  @Override
  public void onChange(ConfigChangeEvent changeEvent) {
    Set<String> keys = changeEvent.changedKeys();
    if (CollectionUtils.isEmpty(keys)) {
      return;
    }
    for (String key : keys) {
       // According to the newly configured key, find the corresponding SpringValue collection 
      Collection<SpringValue> targetValues ​​= springValueRegistry.get ( beanFactory, key);
       if (targetValues ​​== null || targetValues.isEmpty() ) {
         continue ;
      }

      // Traverse the update configuration 
      for (SpringValue val : targetValues) {
        updateSpringValue(val);
      }
    }
  }

  private  void  updateSpringValue ( SpringValue springValue ) {
     try {
       // Get the value that needs to be updated 
      Object value = resolvePropertyValue(springValue);
       // Update 
      springValue.update( value );

      logger.info("Auto update apollo changed value successfully, new value: {}, {}", value,
          springValue);
    } catch (Throwable ex) {
      logger.error("Auto update apollo changed value failed, {}", springValue.toString(), ex);
    }
  }

  ......

}

At this point, it can be determined that it is consistent with the preliminary conclusions drawn at the beginning~

SpringValue

public class SpringValue {
  ......
  public void update(Object newVal) throws IllegalAccessException, InvocationTargetException {
    if (isField()) {
      injectField(newVal);
    } else {
      injectMethod(newVal);
    }
  }

  private void injectField(Object newVal) throws IllegalAccessException {
    Object bean = beanRef.get();
    if (bean == null) {
      return;
    }
    boolean accessible = field.isAccessible();
    field.setAccessible(true);
    field.set(bean, newVal);
    field.setAccessible(accessible);
  }

  private void injectMethod(Object newVal)
      throws InvocationTargetException, IllegalAccessException {
    Object bean = beanRef.get();
    if (bean == null) {
      return;
    }
    methodParameter.getMethod().invoke(bean, newVal);
  }
  ......
}

The code for the SpringValue method is also simple:

  • Is the SpringValue added to the @Value property, use reflection to call Field to set the value
  • It is the SpringValue added to the setter method, and the corresponding method is called by reflection.

You may also like...

Leave a Reply

Your email address will not be published.