好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringBoot2.动态@Value的实现方式

title: SpringBoot2.动态@Value实现

前言

前面文章有详细描述过各个不同阶段对于bean的扩展接口

所以今天就基于BeanPostProcessor实现Spring中的@Value注解值动态变化

基于上面也可以实现一个配置中心,比如说Apollo

具体的实现步骤分为如下几步

1.通过BeanPostProcessor取得有使用@Value注解的bean,并存储到map中

2.动态修改map中的bean字段的值

获取bean

首先写一个类实现BeanPostProcessor接口,只需要使用其中的一个函数就可以。前后都可以用来实现,并不影响最终的使用,因为咱们只是需要bean的实例。

接下来看一下具体实现代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

package com.allen.apollo;

import org.springframework.beans.BeansException;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.beans.factory.config.BeanPostProcessor;

import org.springframework.context.annotation.Configuration;

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;

import java.util.LinkedList;

import java.util.List;

import java.util.Set;

@Configuration

public class SpringValueProcessor implements BeanPostProcessor {

     private final PlaceholderHelper placeholderHelper = new PlaceholderHelper();

     @Override

     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

         if (beanName.equals( "springValueController" )) {

             Class obj = bean.getClass();

             List<Field> fields = findAllField(obj);

             for (Field field : fields) {

                 Value value = field.getAnnotation(Value. class );

                 if (value != null ) {

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

                     for (String key : keys) {

                         SpringValue springValue = new SpringValue(key, value.value(), bean, beanName, field, false );

                         SpringValueCacheMap.map.put(key, springValue);

                     }

                 }

             }

         }

         return bean;

     }

     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;

     }

}

上面的代码咱们就已经拿到了SpringValueController这个实例bean并存储到了map当中,下面看一下测试代码

?

1

2

3

4

5

6

7

8

9

   /**

    * cache  field,存储bean 字段

    */

package com.allen.apollo;

import com.google测试数据mon.collect.LinkedListMultimap;

import com.google测试数据mon.collect.Multimap;

public class SpringValueCacheMap {

     public static final Multimap<String, SpringValue> map = LinkedListMultimap.create();

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

package com.allen.apollo;

import java.lang.ref.WeakReference;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.lang.reflect.Type;

import org.springframework.core.MethodParameter;

public class SpringValue {

     private MethodParameter methodParameter;

     private Field field;

     private WeakReference<Object> beanRef;

     private String beanName;

     private String key;

     private String placeholder;

     private Class<?> targetType;

     private Type genericType;

     private boolean isJson;

     public SpringValue(String key, String placeholder, Object bean, String beanName, Field field, boolean isJson) {

         this .beanRef = new WeakReference<>(bean);

         this .beanName = beanName;

         this .field = field;

         this .key = key;

         this .placeholder = placeholder;

         this .targetType = field.getType();

         this .isJson = isJson;

         if (isJson) {

             this .genericType = field.getGenericType();

         }

     }

     public SpringValue(String key, String placeholder, Object bean, String beanName, Method method, boolean isJson) {

         this .beanRef = new WeakReference<>(bean);

         this .beanName = beanName;

         this .methodParameter = new MethodParameter(method, 0 );

         this .key = key;

         this .placeholder = placeholder;

         Class<?>[] paramTps = method.getParameterTypes();

         this .targetType = paramTps[ 0 ];

         this .isJson = isJson;

         if (isJson) {

             this .genericType = method.getGenericParameterTypes()[ 0 ];

         }

     }

     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);

     }

     public String getBeanName() {

         return beanName;

     }

     public Class<?> getTargetType() {

         return targetType;

     }

     public String getPlaceholder() {

         return this .placeholder;

     }

     public MethodParameter getMethodParameter() {

         return methodParameter;

     }

     public boolean isField() {

         return this .field != null ;

     }

     public Field getField() {

         return field;

     }

     public Type getGenericType() {

         return genericType;

     }

     public boolean isJson() {

         return isJson;

     }

     boolean isTargetBeanValid() {

         return beanRef.get() != null ;

     }

     @Override

     public String toString() {

         Object bean = beanRef.get();

         if (bean == null ) {

             return "" ;

         }

         if (isField()) {

             return String

                     .format( "key: %s, beanName: %s, field: %s.%s" , key, beanName, bean.getClass().getName(), field.getName());

         }

         return String.format( "key: %s, beanName: %s, method: %s.%s" , key, beanName, bean.getClass().getName(),

                 methodParameter.getMethod().getName());

     }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

package com.allen.apollo;

import com.google测试数据mon.base.Strings;

import com.google测试数据mon.collect.Sets;

import org.springframework.beans.factory.config.BeanDefinition;

import org.springframework.beans.factory.config.BeanExpressionContext;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;

import org.springframework.beans.factory.config.Scope;

import org.springframework.util.StringUtils;

import java.util.Set;

import java.util.Stack;

/**

  * Placeholder helper functions.

  */

public class PlaceholderHelper {

   private static final String PLACEHOLDER_PREFIX = "${" ;

   private static final String PLACEHOLDER_SUFFIX = "}" ;

   private static final String VALUE_SEPARATOR = ":" ;

   private static final String SIMPLE_PLACEHOLDER_PREFIX = "{" ;

   private static final String EXPRESSION_PREFIX = "#{" ;

   private static final String EXPRESSION_SUFFIX = "}" ;

   /**

    * Resolve placeholder property values, e.g.

    * <br />

    * <br />

    * "${somePropertyValue}" -> "the actual property value"

    */

   public Object resolvePropertyValue(ConfigurableBeanFactory beanFactory, String beanName, String placeholder) {

     // resolve string value

     String strVal = beanFactory.resolveEmbeddedValue(placeholder);

     BeanDefinition bd = (beanFactory.containsBean(beanName) ? beanFactory

         .getMergedBeanDefinition(beanName) : null );

     // resolve expressions like "#{systemProperties.myProp}"

     return evaluateBeanDefinitionString(beanFactory, strVal, bd);

   }

   private Object evaluateBeanDefinitionString(ConfigurableBeanFactory beanFactory, String value,

                                               BeanDefinition beanDefinition) {

     if (beanFactory.getBeanExpressionResolver() == null ) {

       return value;

     }

     Scope scope = (beanDefinition != null ? beanFactory

         .getRegisteredScope(beanDefinition.getScope()) : null );

     return beanFactory.getBeanExpressionResolver()

         .evaluate(value, new BeanExpressionContext(beanFactory, scope));

   }

   /**

    * Extract keys from placeholder, e.g.

    * <ul>

    * <li>${some.key} => "some.key"</li>

    * <li>${some.key:${some.other.key:100}} => "some.key", "some.other.key"</li>

    * <li>${${some.key}} => "some.key"</li>

    * <li>${${some.key:other.key}} => "some.key"</li>

    * <li>${${some.key}:${another.key}} => "some.key", "another.key"</li>

    * <li>#{new java.text.SimpleDateFormat('${some.key}').parse('${another.key}')} => "some.key", "another.key"</li>

    * </ul>

    */

   public Set<String> extractPlaceholderKeys(String propertyString) {

     Set<String> placeholderKeys = Sets.newHashSet();

     if (!isNormalizedPlaceholder(propertyString) && !isExpressionWithPlaceholder(propertyString)) {

       return placeholderKeys;

     }

     Stack<String> stack = new Stack<>();

     stack.push(propertyString);

     while (!stack.isEmpty()) {

       String strVal = stack.pop();

       int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);

       if (startIndex == - 1 ) {

         placeholderKeys.add(strVal);

         continue ;

       }

       int endIndex = findPlaceholderEndIndex(strVal, startIndex);

       if (endIndex == - 1 ) {

         // invalid placeholder?

         continue ;

       }

       String placeholderCandidate = strVal.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);

       // ${some.key:other.key}

       if (placeholderCandidate.startsWith(PLACEHOLDER_PREFIX)) {

         stack.push(placeholderCandidate);

       } else {

         // some.key:${some.other.key:100}

         int separatorIndex = placeholderCandidate.indexOf(VALUE_SEPARATOR);

         if (separatorIndex == - 1 ) {

           stack.push(placeholderCandidate);

         } else {

           stack.push(placeholderCandidate.substring( 0 , separatorIndex));

           String defaultValuePart =

               normalizeToPlaceholder(placeholderCandidate.substring(separatorIndex + VALUE_SEPARATOR.length()));

           if (!Strings.isNullOrEmpty(defaultValuePart)) {

             stack.push(defaultValuePart);

           }

         }

       }

       // has remaining part, e.g. ${a}.${b}

       if (endIndex + PLACEHOLDER_SUFFIX.length() < strVal.length() - 1 ) {

         String remainingPart = normalizeToPlaceholder(strVal.substring(endIndex + PLACEHOLDER_SUFFIX.length()));

         if (!Strings.isNullOrEmpty(remainingPart)) {

           stack.push(remainingPart);

         }

       }

     }

     return placeholderKeys;

   }

   private boolean isNormalizedPlaceholder(String propertyString) {

     return propertyString.startsWith(PLACEHOLDER_PREFIX) && propertyString.endsWith(PLACEHOLDER_SUFFIX);

   }

   private boolean isExpressionWithPlaceholder(String propertyString) {

     return propertyString.startsWith(EXPRESSION_PREFIX) && propertyString.endsWith(EXPRESSION_SUFFIX)

         && propertyString.contains(PLACEHOLDER_PREFIX);

   }

   private String normalizeToPlaceholder(String strVal) {

     int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);

     if (startIndex == - 1 ) {

       return null ;

     }

     int endIndex = strVal.lastIndexOf(PLACEHOLDER_SUFFIX);

     if (endIndex == - 1 ) {

       return null ;

     }

     return strVal.substring(startIndex, endIndex + PLACEHOLDER_SUFFIX.length());

   }

   private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {

     int index = startIndex + PLACEHOLDER_PREFIX.length();

     int withinNestedPlaceholder = 0 ;

     while (index < buf.length()) {

       if (StringUtils.substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {

         if (withinNestedPlaceholder > 0 ) {

           withinNestedPlaceholder--;

           index = index + PLACEHOLDER_SUFFIX.length();

         } else {

           return index;

         }

       } else if (StringUtils.substringMatch(buf, index, SIMPLE_PLACEHOLDER_PREFIX)) {

         withinNestedPlaceholder++;

         index = index + SIMPLE_PLACEHOLDER_PREFIX.length();

       } else {

         index++;

       }

     }

     return - 1 ;

   }

}

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

package com.allen.apollo;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.util.StringUtils;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.InvocationTargetException;

@RestController

@Slf4j

public class SpringValueController {

     @Value ( "${test:123}" )

     public String zax;

     @Value ( "${test:123}" )

     public String test;

     @Value (( "${zed:zed}" ))

     public String zed;

     @GetMapping ( "/test" )

     public String test(String a, String b) {

         if (!StringUtils.isEmpty(a)) {

             try {

                 for (SpringValue springValue : SpringValueCacheMap.map.get( "test" )) {

                     springValue.update(a);

                 }

                 for (SpringValue springValue : SpringValueCacheMap.map.get( "zed" )) {

                     springValue.update(b);

                 }

             } catch (IllegalAccessException | InvocationTargetException e) {

                 e.printStackTrace();

             }

         }

         return String.format( "test: %s, zax: %s, zed: %s" , test, zax, zed);

     }

}

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

原文链接:https://blog.csdn.net/u013310115/article/details/87966865

查看更多关于SpringBoot2.动态@Value的实现方式的详细内容...

  阅读:28次