将JEE @Produces数据库配置注入机制迁移至Spring Boot方案咨询
迁移JEE数据库动态配置注入至Spring Boot的实现方案
问题背景
原JEE应用通过@Produces配合自定义@Configurable注解,实现从数据库动态注入配置值,每次调用Instance.get()都能获取最新的数据库实时值。迁移至Spring Boot后,希望保留类似的注入流程,避免每次手动调用配置服务。
实现方案
1. 复用自定义@Configurable注解
直接沿用原注解定义,无需修改:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Configurable { String key() default ""; String defaultValue() default ""; }
2. 实现Spring风格的动态配置代理
Spring没有直接对应JEE@Produces的注解,但可以通过BeanPostProcessor结合代理模式,实现延迟加载+实时获取的效果,替代JEE的Instance。
第一步:创建配置代理类
该类封装配置值的获取与类型转换逻辑,每次调用get()时都会查询最新的数据库值:
public class ConfigProxy<T> { private final String key; private final String defaultValue; private final Class<T> type; private final ConfigurationService configService; public ConfigProxy(String key, String defaultValue, Class<T> type, ConfigurationService configService) { this.key = key; this.defaultValue = defaultValue; this.type = type; this.configService = configService; } public T get() { String value = configService.getValue(key, defaultValue); return value == null ? getDefaultValue() : convertValue(value); } private T getDefaultValue() { return switch (type.getSimpleName()) { case "Boolean" -> type.cast(Boolean.FALSE); case "Integer" -> type.cast(0); case "String" -> type.cast(defaultValue); default -> throw new IllegalArgumentException("不支持的配置类型:" + type); }; } private T convertValue(String value) { return switch (type.getSimpleName()) { case "Boolean" -> type.cast(Boolean.parseBoolean(value)); case "Integer" -> type.cast(Integer.parseInt(value)); case "String" -> type.cast(value); default -> throw new IllegalArgumentException("不支持的配置类型:" + type); }; } }
第二步:实现Bean后置处理器
通过BeanPostProcessor扫描带有@Configurable注解的字段,自动注入ConfigProxy实例:
@Component public class ConfigurableBeanPostProcessor implements BeanPostProcessor { private final ConfigurationService configService; public ConfigurableBeanPostProcessor(ConfigurationService configService) { this.configService = configService; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Configurable.class)) { Configurable anno = field.getAnnotation(Configurable.class); // 生成默认key:类名.字段名 String key = StringUtils.isEmpty(anno.key()) ? field.getDeclaringClass().getName() + "." + field.getName() : anno.key(); String defaultValue = anno.defaultValue(); field.setAccessible(true); // 处理ConfigProxy类型字段 if (ConfigProxy.class.isAssignableFrom(field.getType())) { ParameterizedType genericType = (ParameterizedType) field.getGenericType(); Class<?> actualType = (Class<?>) genericType.getActualTypeArguments()[0]; ConfigProxy<?> proxy = new ConfigProxy<>(key, defaultValue, actualType, configService); try { field.set(bean, proxy); } catch (IllegalAccessException e) { throw new RuntimeException("注入配置代理失败:" + field.getName(), e); } } } } return bean; } }
3. 实现配置查询服务
负责从数据库读取最新配置值的核心服务:
@Service public class ConfigurationService { @Autowired private JdbcTemplate jdbcTemplate; public String getValue(String key, String defaultValue) { String sql = "SELECT config_value FROM system_config WHERE config_key = ?"; try { return jdbcTemplate.queryForObject(sql, new Object[]{key}, String.class); } catch (EmptyResultDataAccessException e) { return defaultValue; } } }
4. 使用方式
与原JEE代码风格一致,注入ConfigProxy后调用get()获取实时配置:
@Autowired @Configurable(key = "active-configuration-key") private ConfigProxy<Boolean> active; @Autowired @Configurable(key = "url-configuration-key") private ConfigProxy<String> url; // 业务逻辑中使用 if (active.get()) { String targetUrl = url.get(); // ...执行业务操作 }
兼容原JEE的Instance接口
如果需要完全复用原代码中的Instance类型,可以创建Spring版的Instance实现类:
public class SpringInstance<T> implements Instance<T> { private final Supplier<T> valueSupplier; public SpringInstance(Supplier<T> valueSupplier) { this.valueSupplier = valueSupplier; } @Override public T get() { return valueSupplier.get(); } // 按需实现Instance接口的其他方法,无需功能可默认抛出异常 @Override public boolean isUnsatisfied() { throw new UnsupportedOperationException(); } @Override public boolean isAmbiguous() { throw new UnsupportedOperationException(); } }
然后修改ConfigurableBeanPostProcessor中的字段处理逻辑,注入SpringInstance:
// 新增处理Instance类型字段的分支 else if (Instance.class.isAssignableFrom(field.getType())) { ParameterizedType genericType = (ParameterizedType) field.getGenericType(); Class<?> actualType = (Class<?>) genericType.getActualTypeArguments()[0]; SpringInstance<?> instance = new SpringInstance<>(() -> { String value = configService.getValue(key, defaultValue); return value == null ? getDefaultValue(actualType, defaultValue) : convertValue(value, actualType); }); try { field.set(bean, instance); } catch (IllegalAccessException e) { throw new RuntimeException("注入Instance失败:" + field.getName(), e); } } // 抽离默认值和转换方法到处理器中 private <T> T getDefaultValue(Class<T> type, String defaultValue) { return switch (type.getSimpleName()) { case "Boolean" -> type.cast(Boolean.FALSE); case "Integer" -> type.cast(0); case "String" -> type.cast(defaultValue); default -> throw new IllegalArgumentException("不支持的配置类型:" + type); }; } private <T> T convertValue(String value, Class<T> type) { return switch (type.getSimpleName()) { case "Boolean" -> type.cast(Boolean.parseBoolean(value)); case "Integer" -> type.cast(Integer.parseInt(value)); case "String" -> type.cast(value); default -> throw new IllegalArgumentException("不支持的配置类型:" + type); }; }
这样原代码中的@Inject @Configurable private Instance<Boolean> active;可以直接复用,无需修改。
内容的提问来源于stack exchange,提问作者Daniel Rodríguez
相关产品推荐
相关产品推荐

