From b2e7160ce79f0fca554ed25dcf4d2ec5d5e2236f Mon Sep 17 00:00:00 2001 From: Redkale Date: Thu, 1 Dec 2022 20:31:28 +0800 Subject: [PATCH] =?UTF-8?q?ResourceListener=E6=94=AF=E6=8C=81=E5=AF=B9Envi?= =?UTF-8?q?ronment=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/redkale/boot/PropertiesAgent.java | 6 +- .../java/org/redkale/util/Environment.java | 1 + .../org/redkale/util/ResourceFactory.java | 163 ++++++++++++------ .../org/redkale/util/ResourceListener.java | 5 +- .../test/util/ResourceListenerTest.java | 129 ++++++++++++++ 5 files changed, 244 insertions(+), 60 deletions(-) create mode 100644 src/test/java/org/redkale/test/util/ResourceListenerTest.java diff --git a/src/main/java/org/redkale/boot/PropertiesAgent.java b/src/main/java/org/redkale/boot/PropertiesAgent.java index 881137bfd..0709b3ab3 100644 --- a/src/main/java/org/redkale/boot/PropertiesAgent.java +++ b/src/main/java/org/redkale/boot/PropertiesAgent.java @@ -53,9 +53,9 @@ public abstract class PropertiesAgent { public abstract void destroy(AnyValue conf); protected void updateEnvironmentProperties(Application application, Properties props) { - Properties notifyCache = new Properties(); - props.forEach((k, v) -> application.updateEnvironmentProperty(k.toString(), v, notifyCache)); - application.resourceFactory.register(notifyCache); + Properties changeCache = new Properties(); + props.forEach((k, v) -> application.updateEnvironmentProperty(k.toString(), v, changeCache)); + application.resourceFactory.register(changeCache, "", Environment.class); } protected void putEnvironmentProperty(Application application, String key, Object value) { diff --git a/src/main/java/org/redkale/util/Environment.java b/src/main/java/org/redkale/util/Environment.java index ca365c114..e25515fff 100644 --- a/src/main/java/org/redkale/util/Environment.java +++ b/src/main/java/org/redkale/util/Environment.java @@ -8,6 +8,7 @@ import java.util.function.BiConsumer; /** * 环境变量, 只读版Properties * 只存放system.property.、mimetype.property.、redkale.cachesource(.|[)、redkale.datasource(.|[)和其他非redkale.开头的配置项 + * 只有ResourceFactory.register(Properties properties, String environmentName, Class environmentType) 方法才能是Environment的ResourceListener起作用 * * 详情见: https://redkale.org * diff --git a/src/main/java/org/redkale/util/ResourceFactory.java b/src/main/java/org/redkale/util/ResourceFactory.java index 8a5abf02b..b8902341b 100644 --- a/src/main/java/org/redkale/util/ResourceFactory.java +++ b/src/main/java/org/redkale/util/ResourceFactory.java @@ -347,12 +347,12 @@ public final class ResourceFactory { * * @param 泛型 * @param name 资源名 - * @param rs 资源对象 + * @param val 资源对象 * * @return 旧资源对象 */ - public A register(final String name, final A rs) { - return register(true, name, rs); + public A register(final String name, final A val) { + return register(true, name, val); } /** @@ -361,19 +361,19 @@ public final class ResourceFactory { * @param 泛型 * @param autoSync 是否同步已被注入的资源 * @param name 资源名 - * @param rs 资源对象 + * @param val 资源对象 * * @return 旧资源对象 */ - public A register(final boolean autoSync, final String name, final A rs) { + public A register(final boolean autoSync, final String name, final A val) { checkResourceName(name); - final Class claz = rs.getClass(); + final Class claz = val.getClass(); ResourceType rtype = claz.getAnnotation(ResourceType.class); if (rtype == null) { - return (A) register(autoSync, name, claz, rs); + return (A) register(autoSync, name, claz, val); } else { A old = null; - A t = (A) register(autoSync, name, rtype.value(), rs); + A t = (A) register(autoSync, name, rtype.value(), val); if (t != null) old = t; return old; } @@ -385,12 +385,12 @@ public final class ResourceFactory { * @param 泛型 * @param name 资源名 * @param clazz 资源类型 - * @param rs 资源对象 + * @param val 资源对象 * * @return 旧资源对象 */ - public A register(final String name, final Class clazz, final A rs) { - return register(true, name, clazz, rs); + public A register(final String name, final Class clazz, final A val) { + return register(true, name, clazz, val); } /** @@ -399,12 +399,12 @@ public final class ResourceFactory { * @param 泛型 * @param name 资源名 * @param clazz 资源类型 - * @param rs 资源对象 + * @param val 资源对象 * * @return 旧资源对象 */ - public A register(final String name, final Type clazz, final A rs) { - return register(true, name, clazz, rs); + public A register(final String name, final Type clazz, final A val) { + return register(true, name, clazz, val); } /** @@ -414,12 +414,12 @@ public final class ResourceFactory { * @param autoSync 是否同步已被注入的资源 * @param name 资源名 * @param clazz 资源类型 - * @param rs 资源对象 + * @param val 资源对象 * * @return 旧资源对象 */ - public A register(final boolean autoSync, final String name, final Type clazz, final A rs) { - return register(autoSync, name, clazz, rs, null); + public A register(final boolean autoSync, final String name, final Type clazz, final A val) { + return register(autoSync, name, clazz, val, null); } /** @@ -429,50 +429,85 @@ public final class ResourceFactory { * */ public void register(Properties properties) { + register(properties, null, null); + } + + /** + * 将多个以指定资源名的String对象注入到资源池中 + * + * @param properties 资源键值对 + * @param environmentName 额外的资源名 + * @param environmentType 额外的类名 + * + */ + public void register(Properties properties, String environmentName, Class environmentType) { if (properties == null) return; List wrappers = new ArrayList<>(); - properties.forEach((k, v) -> register(true, k.toString(), String.class, v, wrappers)); - if (wrappers.isEmpty()) return; + List environmentEventList = new ArrayList<>(); + properties.forEach((k, v) -> { + Object old = register(true, k.toString(), String.class, v, wrappers); + if (!Objects.equals(v, old)) { + environmentEventList.add(new ResourceChangeEvent(k.toString(), v, old)); + } + }); + Map envListenMap = new LinkedHashMap<>(); + if (!environmentEventList.isEmpty() && environmentName != null && environmentType != null) { + ResourceEntry entry = findEntry(environmentName, environmentType); + if (entry != null && entry.elements != null) { + for (ResourceElement element : entry.elements) { + Object dest = element.dest.get(); + if (dest != null && element.listener != null) { + envListenMap.put(dest, element.listener); + } + } + } + } + if (wrappers.isEmpty() && envListenMap.isEmpty()) return; Map> map = new LinkedHashMap<>(); for (ResourceChangeWrapper wrapper : wrappers) { map.computeIfAbsent(wrapper.dest, k -> new ArrayList<>()).add(wrapper); } - map.forEach((dest, list) -> { - Method listener = list.get(0).listener; - try { - ResourceEvent[] events = new ResourceEvent[list.size()]; - for (int i = 0; i < list.size(); i++) { - events[i] = list.get(i).event; + if (!map.isEmpty()) { + map.forEach((dest, list) -> { + if (envListenMap.containsKey(dest)) return; //跳过含有@Resource Environment字段的对象 + Method listener = list.get(0).listener; + try { + ResourceEvent[] events = new ResourceEvent[list.size()]; + for (int i = 0; i < list.size(); i++) { + events[i] = list.get(i).event; + } + Object[] ps = new Object[]{events}; + listener.invoke(dest, ps); + } catch (Exception e) { + logger.log(Level.SEVERE, dest + " resource change listener error", e); } - Object[] ps = new Object[]{events}; - listener.invoke(dest, ps); - } catch (Exception e) { - logger.log(Level.SEVERE, dest + " resource change listener error", e); - } - }); + }); + } + if (!envListenMap.isEmpty()) { //含有@Resource Environment字段的对象进行变更响应 + ResourceEvent[] environmentEvents = environmentEventList.toArray(new ResourceEvent[environmentEventList.size()]); + envListenMap.forEach((dest, listener) -> { + try { + Object[] ps = new Object[]{environmentEvents}; + listener.invoke(dest, ps); + } catch (Exception e) { + logger.log(Level.SEVERE, dest + " resource change listener error", e); + } + }); + } } - private A register(final boolean autoSync, final String name, final Type clazz, final A rs, List wrappers) { + private A register(final boolean autoSync, final String name, final Type clazz, final A val, List wrappers) { checkResourceName(name); Class clz = TypeToken.typeToClass(clazz); - if (clz != null && !clz.isPrimitive() && rs != null && !clz.isAssignableFrom(rs.getClass())) { - throw new RuntimeException(clz + "not isAssignableFrom (" + rs + ") class " + rs.getClass()); - } - ConcurrentHashMap map = this.store.get(clazz); - if (map == null) { - synchronized (clazz) { - map = this.store.get(clazz); - if (map == null) { - map = new ConcurrentHashMap(); - store.put(clazz, map); - } - } + if (clz != null && !clz.isPrimitive() && val != null && !clz.isAssignableFrom(val.getClass())) { + throw new RuntimeException(clz + "not isAssignableFrom (" + val + ") class " + val.getClass()); } + ConcurrentHashMap map = this.store.computeIfAbsent(clazz, k -> new ConcurrentHashMap()); ResourceEntry re = map.get(name); if (re == null) { - map.put(name, new ResourceEntry(name, rs)); + map.put(name, new ResourceEntry(name, val)); } else { - map.put(name, new ResourceEntry(name, rs, re.elements, wrappers, autoSync)); + map.put(name, new ResourceEntry(name, val, re.elements, wrappers, autoSync)); } return re == null ? null : (A) re.value; } @@ -630,11 +665,11 @@ public final class ResourceFactory { int pos = name.indexOf("{system.property."); if (pos < 0) return (name.contains(RESOURCE_PARENT_NAME) && parent != null) ? name.replace(RESOURCE_PARENT_NAME, parent) : name; String prefix = name.substring(0, pos); - String subname = name.substring(pos + "{system.property.".length()); - pos = subname.lastIndexOf('}'); + String subName = name.substring(pos + "{system.property.".length()); + pos = subName.lastIndexOf('}'); if (pos < 0) return (name.contains(RESOURCE_PARENT_NAME) && parent != null) ? name.replace(RESOURCE_PARENT_NAME, parent) : name; - String postfix = subname.substring(pos + 1); - String property = subname.substring(0, pos); + String postfix = subName.substring(pos + 1); + String property = subName.substring(0, pos); return formatResourceName(parent, prefix + System.getProperty(property, "") + postfix); } @@ -704,7 +739,7 @@ public final class ResourceFactory { } } - boolean autoregnull = true; + boolean autoRegNull = true; final String rcname = formatResourceName(srcResourceName, tname); Object rs; if (rcname.startsWith("system.property.")) { @@ -722,7 +757,7 @@ public final class ResourceFactory { ResourceTypeLoader it = findTypeLoader(genctype, field); if (it != null) { it.load(this, srcResourceName, srcObj, rcname, field, attachment); - autoregnull = it.autoNone(); + autoRegNull = it.autoNone(); re = findEntry(rcname, genctype); } } @@ -739,12 +774,12 @@ public final class ResourceFactory { ResourceTypeLoader it = findTypeLoader(classtype, field); if (it != null) { it.load(this, srcResourceName, srcObj, rcname, field, attachment); - autoregnull = it.autoNone(); + autoRegNull = it.autoNone(); re = findEntry(rcname, classtype); } } } - if (re == null && autoregnull) { + if (re == null && autoRegNull) { register(rcname, genctype, null); //自动注入null的值 re = findEntry(rcname, genctype); } @@ -867,7 +902,7 @@ public final class ResourceFactory { } //wrappers=null时才会触发listener的ResourceChangeEvent事件 - public ResourceEntry(final String name, T value, final List elements, List wrappers, boolean sync) { + public ResourceEntry(final String name, T value, final List elements, Collection wrappers, boolean sync) { this.name = name; this.value = value; this.elements = elements == null ? new CopyOnWriteArrayList<>() : elements; @@ -1001,6 +1036,24 @@ public final class ResourceFactory { return dest; } + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + Objects.hashCode(this.dest); + hash = 97 * hash + Objects.hashCode(this.listener); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final ResourceChangeWrapper other = (ResourceChangeWrapper) obj; + if (!Objects.equals(this.dest, other.dest)) return false; + return Objects.equals(this.listener, other.listener); + } + } private static class ResourceChangeEvent implements ResourceEvent { diff --git a/src/main/java/org/redkale/util/ResourceListener.java b/src/main/java/org/redkale/util/ResourceListener.java index ff03934b7..b5d497459 100644 --- a/src/main/java/org/redkale/util/ResourceListener.java +++ b/src/main/java/org/redkale/util/ResourceListener.java @@ -10,7 +10,8 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** - * @Resource资源被更新时的监听事件。本注解只能标记在方法参数为ResourceEvent[]上。 + * @Resource资源被更新时的监听事件, 本注解只能标记在方法参数为ResourceEvent[]上
+ * 注意: 一个类只能存在一个@ResourceResourceListener的方法, 多余的会被忽略。 * 方法在资源被更新以后调用。 * *
@@ -24,7 +25,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
  *
  *    @ResourceListener
  *    private void changeResource(ResourceEvent[] events) {
- *        for(ResourceEvent event : events) {  
+ *        for(ResourceEvent event : events) {
  *            System.out.println("@Resource = " + event.name() + " 资源变更:  newVal = " + event.newValue() + ", oldVal = " + event.oldValue());
  *        }
  *    }
diff --git a/src/test/java/org/redkale/test/util/ResourceListenerTest.java b/src/test/java/org/redkale/test/util/ResourceListenerTest.java
new file mode 100644
index 000000000..e3df657d5
--- /dev/null
+++ b/src/test/java/org/redkale/test/util/ResourceListenerTest.java
@@ -0,0 +1,129 @@
+/*
+ */
+package org.redkale.test.util;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Resource;
+import org.junit.jupiter.api.*;
+import org.redkale.util.*;
+
+/**
+ *
+ * @author zhangjx
+ */
+public class ResourceListenerTest {
+
+    private boolean main;
+
+    public static void main(String[] args) throws Throwable {
+        ResourceListenerTest test = new ResourceListenerTest();
+        test.main = true;
+        test.run();
+    }
+
+    @Test
+    public void run() throws Exception {
+        AtomicInteger aCounter = new AtomicInteger();
+        Properties env = new Properties();
+        env.put("property.id", "2345");
+        ResourceFactory factory = ResourceFactory.create();
+        factory.register(new Environment(env));
+
+        AService aservice = new AService();
+        BService bservice = new BService();
+        ABService abservice = new ABService();
+
+        factory.inject(aservice);
+        factory.inject(bservice);
+        factory.inject(abservice);
+
+        Properties prop = new Properties();
+        prop.put("property.id", "7890");
+        prop.put("property.name", "my name");
+        factory.register(prop, "", Environment.class);
+
+        if (!main) {
+            Assertions.assertTrue(aservice.counter.get() == 1);
+            Assertions.assertTrue(bservice.counter.get() == 2);
+            Assertions.assertTrue(abservice.counter.get() == 2);
+        }
+                
+        factory.register("property.id", "7777");
+        
+        if (!main) {
+            Assertions.assertTrue(aservice.counter.get() == 2);
+            Assertions.assertTrue(bservice.counter.get() == 2);
+            Assertions.assertTrue(abservice.counter.get() == 3);
+        }
+    }
+
+    class AService {
+
+        public final AtomicInteger counter = new AtomicInteger();
+
+        @Resource(name = "property.id")
+        private String id;
+
+        @Resource(name = "property.desc")
+        private String desc;
+
+        @ResourceListener
+        private void changeResource(ResourceEvent[] events) {
+            for (ResourceEvent event : events) {
+                counter.incrementAndGet();
+                System.out.println(getClass().getSimpleName() + " @Resource = " + event.name() + " 资源变更:  newVal = " + event.newValue() + ", oldVal = " + event.oldValue());
+            }
+        }
+
+        public String test() {
+            return "";
+        }
+
+    }
+
+    class BService {
+
+        public final AtomicInteger counter = new AtomicInteger();
+
+        @Resource
+        private Environment env;
+
+        @ResourceListener
+        private void changeResource(ResourceEvent[] events) {
+            for (ResourceEvent event : events) {
+                counter.incrementAndGet();
+                System.out.println(getClass().getSimpleName() + " @Resource = " + event.name() + " 资源变更:  newVal = " + event.newValue() + ", oldVal = " + event.oldValue());
+            }
+            System.out.println(getClass().getSimpleName() + " env = " + env);
+        }
+
+        public String test() {
+            return "";
+        }
+    }
+
+    class ABService {
+
+        public final AtomicInteger counter = new AtomicInteger();
+
+        @Resource(name = "property.id")
+        private String id;
+
+        @Resource
+        private Environment env;
+
+        @ResourceListener
+        private void changeResource(ResourceEvent[] events) {
+            for (ResourceEvent event : events) {
+                counter.incrementAndGet();
+                System.out.println(getClass().getSimpleName() + " @Resource = " + event.name() + " 资源变更:  newVal = " + event.newValue() + ", oldVal = " + event.oldValue());
+            }
+            System.out.println(getClass().getSimpleName() + " env = " + env);
+        }
+
+        public String test() {
+            return "";
+        }
+    }
+}