栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

如何在Java EE和Spring Boot中热重载属性?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何在Java EE和Spring Boot中热重载属性?

我想到了许多内部解决方案。就像在数据库中拥有属性并每N秒轮询一次。然后还要检查.properties文件的时间戳修改并重新加载。

但是我一直在寻找Java EE标准和Spring Boot文档,但似乎找不到最佳的方法。

我需要我的应用程序读取属性文件(或环境变量或数据库参数),然后能够重新读取它们。生产中使用的最佳实践是什么?

正确的答案至少可以解决一种情况(Spring Boot或Java EE),并提供有关如何使其在另一种情况下工作的概念性提示用于Spring和Java EE的抽象类不是干净代码的最佳示例。但是它易于使用,并且确实满足了以下基本初始要求:

  • 除Java 8类外,不使用任何外部库。
  • 仅一个文件即可解决问题(Java EE版本约为160行)。
  • 文件系统中提供的标准Java属性UTF-8编码文件的使用。
  • 支持加密的属性。

For Spring Boot

此代码有助于在不使用Spring Cloud Config服务器的情况下热重载application.properties文件(在某些情况下可能会过分使用)

你可以只复制并粘贴此抽象类(SO goodies:D),这是从该SO答案派生的代码

// imports from java/spring/lombokpublic abstract class ReloadableProperties {  @Autowired  protected StandardEnvironment environment;  private long lastModTime = 0L;  private Path configPath = null;  private PropertySource<?> appConfigPropertySource = null;  @PostConstruct  private void stopIfProblemsCreatingContext() {    System.out.println("reloading");    MutablePropertySources propertySources = environment.getPropertySources();    Optional<PropertySource<?>> appConfigPsOp =        StreamSupport.stream(propertySources.spliterator(), false) .filter(ps -> ps.getName().matches("^.*applicationConfig.*file:.*$")) .findFirst();    if (!appConfigPsOp.isPresent())  {      // this will stop context initialization       // (i.e. kill the spring boot program before it initializes)      throw new RuntimeException("Unable to find property Source as file");    }    appConfigPropertySource = appConfigPsOp.get();    String filename = appConfigPropertySource.getName();    filename = filename        .replace("applicationConfig: [file:", "")        .replaceAll("\]$", "");    configPath = Paths.get(filename);  }  @Scheduled(fixedRate=2000)  private void reload() throws IOException {      System.out.println("reloading...");      long currentModTs = Files.getLastModifiedTime(configPath).toMillis();      if (currentModTs > lastModTime) {        lastModTime = currentModTs;        Properties properties = new Properties();        @Cleanup InputStream inputStream = Files.newInputStream(configPath);        properties.load(inputStream);        environment.getPropertySources() .replace(     appConfigPropertySource.getName(),     new PropertiesPropertySource(         appConfigPropertySource.getName(),         properties     ) );        System.out.println("Reloaded.");        propertiesReloaded();      }    }    protected abstract void propertiesReloaded();}

然后,创建一个bean类,该类允许从使用抽象类的applicatoin.properties中检索属性值

@Componentpublic class AppProperties extends ReloadableProperties {    public String dynamicProperty() {        return environment.getProperty("dynamic.prop");    }    public String anotherDynamicProperty() {        return environment.getProperty("another.dynamic.prop");        }    @Override    protected void propertiesReloaded() {        // do something after a change in property values was done    }}

确保将@EnableScheduling添加到你的@SpringBootApplication中

@SpringBootApplication@EnableSchedulingpublic class MainApp  {   public static void main(String[] args) {      SpringApplication.run(MainApp.class, args);   }}

现在,你可以在需要的地方自动装配AppProperties Bean。只需确保始终调用其中的方法,而不是将其值保存在变量中即可。并确保重新配置任何使用可能具有不同属性值初始化的资源或bean。

目前,我仅使用外部默认找到的

./config/application.properties
文件对此进行了测试。

对于Java EE

我做了一个普通的Java SE抽象类来完成这项工作。

你可以复制并粘贴以下内容:

// imports from java.* and javax.crypto.*public abstract class ReloadableProperties {  private volatile Properties properties = null;  private volatile String propertiesPassword = null;  private volatile long lastModTimeOfFile = 0L;  private volatile long lastTimeChecked = 0L;  private volatile Path propertyFileAddress;  abstract protected void propertiesUpdated();  public class DynProp {    private final String propertyName;    public DynProp(String propertyName) {      this.propertyName = propertyName;    }    public String val() {      try {        return ReloadableProperties.this.getString(propertyName);      } catch (Exception e) {        e.printStackTrace();        throw new RuntimeException(e);      }    }  }  protected void init(Path path) {    this.propertyFileAddress = path;    initOrReloadIfNeeded();  }  private synchronized void initOrReloadIfNeeded() {    boolean firstTime = lastModTimeOfFile == 0L;    long currentTs = System.currentTimeMillis();    if ((lastTimeChecked + 3000) > currentTs)      return;    try {      File fa = propertyFileAddress.toFile();      long currModTime = fa.lastModified();      if (currModTime > lastModTimeOfFile) {        lastModTimeOfFile = currModTime;        InputStreamReader isr = new InputStreamReader(new FileInputStream(fa), StandardCharsets.UTF_8);        Properties prop = new Properties();        prop.load(isr);        properties = prop;        isr.close();        File passwordFiles = new File(fa.getAbsolutePath() + ".key");        if (passwordFiles.exists()) {          byte[] bytes = Files.readAllBytes(passwordFiles.toPath());          propertiesPassword = new String(bytes,StandardCharsets.US_ASCII);          propertiesPassword = propertiesPassword.trim();          propertiesPassword = propertiesPassword.replaceAll("(\r|\n)", "");        }      }      updateProperties();      if (!firstTime)        propertiesUpdated();    } catch (Exception e) {      e.printStackTrace();    }  }  private void updateProperties() {    List<DynProp> dynProps = Arrays.asList(this.getClass().getDeclaredFields())        .stream()        .filter(f -> f.getType().isAssignableFrom(DynProp.class))        .map(f-> fromField(f))        .collect(Collectors.toList());    for (DynProp dp :dynProps) {      if (!properties.containsKey(dp.propertyName)) {        System.out.println("propertyName: "+ dp.propertyName + " does not exist in property file");      }    }    for (Object key : properties.keySet()) {      if (!dynProps.stream().anyMatch(dp->dp.propertyName.equals(key.toString()))) {        System.out.println("property in file is not used in application: "+ key);      }    }  }  private DynProp fromField(Field f) {    try {      return (DynProp) f.get(this);    } catch (IllegalAccessException e) {      e.printStackTrace();    }    return null;  }  protected String getString(String param) throws Exception {    initOrReloadIfNeeded();    String value = properties.getProperty(param);    if (value.startsWith("ENC(")) {      String cipheredText = value          .replace("ENC(", "")          .replaceAll("\)$", "");      value =  decrypt(cipheredText, propertiesPassword);    }    return value;  }  public static String encrypt(String plainText, String key)      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {    SecureRandom secureRandom = new SecureRandom();    byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");    KeySpec spec = new PBEKeySpec(key.toCharArray(), new byte[]{0,1,2,3,4,5,6,7}, 65536, 128);    SecretKey tmp = factory.generateSecret(spec);    SecretKey secretKey = new SecretKeySpec(tmp.getEnpred(), "AES");    byte[] iv = new byte[12];    secureRandom.nextBytes(iv);    final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");    GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length    cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);    byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));    ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);    byteBuffer.putInt(iv.length);    byteBuffer.put(iv);    byteBuffer.put(cipherText);    byte[] cipherMessage = byteBuffer.array();    String cyphertext = base64.getEnprer().enpreToString(cipherMessage);    return cyphertext;  }  public static String decrypt(String cypherText, String key)      throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException {    byte[] cipherMessage = base64.getDeprer().depre(cypherText);    ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);    int ivLength = byteBuffer.getInt();    if(ivLength < 12 || ivLength >= 16) { // check input parameter      throw new IllegalArgumentException("invalid iv length");    }    byte[] iv = new byte[ivLength];    byteBuffer.get(iv);    byte[] cipherText = new byte[byteBuffer.remaining()];    byteBuffer.get(cipherText);    byte[] keyBytes = key.getBytes(StandardCharsets.US_ASCII);    final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");    KeySpec spec = new PBEKeySpec(key.toCharArray(), new byte[]{0,1,2,3,4,5,6,7}, 65536, 128);    SecretKey tmp = factory.generateSecret(spec);    SecretKey secretKey = new SecretKeySpec(tmp.getEnpred(), "AES");    cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv));    byte[] plainText= cipher.doFinal(cipherText);    String plain = new String(plainText, StandardCharsets.UTF_8);    return plain;  }}

然后,你可以通过以下方式使用它:

public class AppProperties extends ReloadableProperties {  public static final AppProperties INSTANCE; static {    INSTANCE = new AppProperties();    INSTANCE.init(Paths.get("application.properties"));  }  @Override  protected void propertiesUpdated() {    // run pre every time a property is updated  }  public final DynProp wsUrl = new DynProp("ws.url");  public final DynProp hiddenText = new DynProp("hidden.text");}

如果要使用编码的属性,可以将其值括在ENC()中,然后将在属性文件的相同路径和名称中搜索解密密码,并添加.key扩展名。在此示例中,它将在application.properties.key文件中查找密码。

application.properties->

ws.url=http://some websidehidden.text=ENC(AAAADCzaasd9g61MI4l5sbCXrFNaQfQrgkxygNmFa3UuB9Y+YzRuBGYj+A==)

aplication.properties.key->

password aca

对于Java EE解决方案的属性值的加密,我查阅了Patrick Favre-Bulle的出色文章,内容涉及Java和Android中的AES对称加密。然后在关于AES / GCM / NoPadding的 SO问题中检查了密码,块模式和填充。最后,我使AES位元从@erickson的密码中获得了一个很好的答案,有关SO 密码的加密。关于Spring中的值属性加密,我认为它们与Java简化加密集成在一起

是否可以将此视为最佳做法,否则可能不在范围之内。该答案显示了如何在Spring Boot和Java EE中具有可重载的属性。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/395194.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号