diff --git a/.gitignore b/.gitignore
index 334264f6..1526aefc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,8 @@
*.pyc
__pycache__
*.class
-target
\ No newline at end of file
+target
+test.log
+*.log
+test.log
+test.json
diff --git a/Java三方库/caffeine.md b/Java三方库/caffeine.md
new file mode 100644
index 00000000..c3153a75
--- /dev/null
+++ b/Java三方库/caffeine.md
@@ -0,0 +1,533 @@
+Caffeine本地缓存详解
+
+
+## 1 概述
+
+### 简介
+Caffeine是一种高性能的缓存库,是基于Java 8的最佳(最优)缓存框架。
+
+基于Google的Guava Cache,Caffeine提供一个性能卓越的本地缓存(local cache) 实现, 也是SpringBoot内置的本地缓存实现。(Caffeine性能是Guava Cache的6倍)
+
+Caffeine提供了灵活的结构来创建缓存,并且有以下特性:
+
+* 自动加载条目到缓存中,可选异步方式
+* 可以基于大小剔除
+* 可以设置过期时间,时间可以从上次访问或上次写入开始计算
+* 异步刷新
+* keys自动包装在弱引用中
+* values自动包装在弱引用或软引用中
+* 条目剔除通知
+* 缓存访问统计
+
+### 依赖
+引入Maven依赖
+
+```java
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 2.9.2
+
+```
+
+## 2 数据加载
+
+Caffeine提供以下四种类型的加载策略:
+
+### Manual手动
+Cache接口可以显式地控制检索、更新和删除Entry
+
+
+```java
+public static void demo(){
+ Cache cache = Caffeine.newBuilder()
+ .expireAfterWrite(20, TimeUnit.SECONDS)
+ .maximumSize(5000)
+ .build();
+
+ // 1.Insert or update an entry
+ cache.put("hello","world");
+
+ // 2. Lookup an entry, or null if not found
+ String val1 = cache.getIfPresent("hello");
+
+ // 3. Lookup and compute an entry if absent, or null if not computable
+ cache.get("msg", k -> createExpensiveGraph(k));
+
+ // 4. Remove an entry
+ cache.invalidate("hello");
+ }
+
+ private static String createExpensiveGraph(String key){
+ System.out.println("begin to query db..."+Thread.currentThread().getName());
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ System.out.println("success to query db...");
+ return UUID.randomUUID().toString();
+ }
+```
+
+### Loading自动
+
+```java
+private static void demo() {
+
+ LoadingCache cache = Caffeine.newBuilder()
+ .expireAfterWrite(5, TimeUnit.SECONDS)
+ .maximumSize(500)
+ .build(new CacheLoader() {
+
+ @Override
+ public String load(String key) throws Exception {
+ return createExpensiveGraph(key);
+ }
+
+ @Override
+ public Map loadAll(Iterable extends String> keys) {
+ System.out.println("build keys");
+ Map map = new HashMap<>();
+ for(String k : keys){
+ map.put(k,k+"-val");
+ }
+ return map;
+ }
+ });
+
+ String val1 = cache.get("hello");
+ Map values = cache.getAll(Lists.newArrayList("key1", "key2"));
+
+ }
+
+ private static String createExpensiveGraph(String key){
+ System.out.println("begin to query db..."+Thread.currentThread().getName());
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ System.out.println("success to query db...");
+ return UUID.randomUUID().toString();
+ }
+```
+
+LoadingCache通过关联一个CacheLoader来构建Cache, 当缓存未命中会调用CacheLoader的load方法生成V
+
+还可以通过LoadingCache的getAll方法批量查询, 当CacheLoader未实现loadAll方法时, 会批量调用load方法聚合会返回.
+
+当CacheLoader实现loadAll方法时, 则直接调用loadAll返回.
+
+```java
+public interface CacheLoader{
+
+ V load(@NonNull K var1) throws Exception;
+
+ Map loadAll(@NonNull Iterable extends K> keys);
+}
+```
+
+### Asynchronous Manual异步手动
+AsyncCache是另一种Cache,它基于Executor计算Entry,并返回一个CompletableFuture
+
+和Cache的区别是, AsyncCache计算Entry的线程是ForkJoinPool线程池. 手动Cache缓存是调用线程进行计算
+
+
+```java
+private static void demo() throws ExecutionException, InterruptedException {
+ AsyncCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .buildAsync();
+
+ // Lookup and asynchronously compute an entry if absent
+ CompletableFuture future = cache.get("hello", k -> createExpensiveGraph(k));
+ System.out.println(future.get());
+ }
+
+ private static String createExpensiveGraph(String key){
+ System.out.println("begin to query db..."+Thread.currentThread().getName());
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ System.out.println("success to query db...");
+ return UUID.randomUUID().toString();
+ }
+```
+
+### Asynchronously Loading异步自动
+AsyncLoadingCache 是关联了 AsyncCacheLoader 的 AsyncCache
+
+
+
+```java
+public static void demo() throws ExecutionException, InterruptedException {
+ AsyncLoadingCache cache = Caffeine.newBuilder()
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .maximumSize(500)
+ .buildAsync(k -> createExpensiveGraph(k));
+ CompletableFuture future = cache.get("hello");
+ System.out.println(future.get());
+ }
+
+ private static String createExpensiveGraph(String key){
+ System.out.println("begin to query db..."+Thread.currentThread().getName());
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ System.out.println("success to query db...");
+ return UUID.randomUUID().toString();
+ }
+```
+
+## 3 数据驱逐
+
+Caffeine提供以下几种剔除方式:基于大小、基于权重、基于时间、基于引用
+
+### 1. 基于容量
+又包含两种, 基于size和基于weight权重
+
+* 基于size
+ * 如果缓存的条目数量不应该超过某个值,那么可以使用Caffeine.maximumSize(long)。如果超过这个值,则会剔除很久没有被访问过或者不经常使用的那个条目。
+ * 上述测试并不是i=500时, 而是稍微延迟于i的增加, 说明驱逐是另外一个线程异步进行的
+
+```
+LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .recordStats()
+ .build( k -> UUID.randomUUID().toString());
+
+ for (int i = 0; i < 600; i++) {
+ cache.get(String.valueOf(i));
+ if(i> 500){
+ CacheStats stats = cache.stats();
+ System.out.println("evictionCount:"+stats.evictionCount());
+ System.out.println("stats:"+stats.toString());
+ }
+ }
+```
+
+* 基于权重
+ * 如果,不同的条目有不同的权重值的话(不同的实例占用空间大小不一样),那么你可以用Caffeine.weigher(Weigher)来指定一个权重函数,并且使用Caffeine.maximumWeight(long)来设定最大的权重值。
+ * 上述测试并不是i=200时, 而是稍微延迟于i的增加, 说明驱逐是另外一个线程异步进行的
+ * 简单的来说,要么限制缓存条目的数量,要么限制缓存条目的权重值,二者取其一。
+```java
+LoadingCache cache = Caffeine.newBuilder()
+ .maximumWeight(300)
+ .recordStats()
+ .weigher((Weigher) (key, value) -> {
+ if(key % 2 == 0){
+ return 2;
+ }
+ return 1;
+ })
+ .build( k -> UUID.randomUUID().toString());
+
+ for (int i = 0; i < 300; i++) {
+ cache.get(i);
+ if(i> 200){
+ System.out.println(cache.stats().toString());
+ }
+ }
+```
+
+
+
+### 2. 基于时间
+基于时间又分为四种: expireAfterAccess、expireAfterWrite、refreshAfterWrite、expireAfter
+
+#### expireAfterAccess
+超时未访问则失效: 访问包括读和写
+```java
+ private static LoadingCache cache = Caffeine.newBuilder()
+ .expireAfterAccess(1, TimeUnit.SECONDS)
+ .build(key -> UUID.randomUUID().toString());
+```
+特征:
+
+* 访问包括读和写入
+* 数据失效后不会主动重新加载, 必须依赖下一次访问. (言外之意: 失效和回源是两个动作)
+* key超时失效或不存在,若多个线程并发访问, 只有1个线程回源数据,其他线程阻塞等待数据返回
+* 对同一数据一直访问, 且间隔小于失效时间, 则不会去load数据, 一直读到的是脏数据
+
+#### expireAfterWrite
+写后超时失效
+```java
+ private static LoadingCache cache = Caffeine.newBuilder()
+ .expireAfterWrite(1, TimeUnit.SECONDS)
+ .build(key -> UUID.randomUUID().toString());
+```
+特征:
+
+* 数据失效后不会主动重新加载, 必须依赖下一次访问. (言外之意: 失效和回源是两个动作)
+* key超时失效或不存在,若多个线程并发访问, 只有1个线程回源数据,其他线程阻塞等待数据返回
+* expire后来访问一定能保证拿到最新的数据
+
+#### refreshAfterWrite
+```java
+ private static LoadingCache cache = Caffeine.newBuilder()
+ .refreshAfterWrite(1, TimeUnit.SECONDS)
+ .build(key -> UUID.randomUUID().toString());
+```
+和expireAfterWrite类似基于写后超时驱逐, 区别是重新load的操作不一样.
+
+特征:
+
+* 数据失效后不会主动重新加载, 必须依赖下一次访问. (言外之意: 失效和回源是两个动作)
+* 当cache命中未命中时, 若多个线程并发访问时, 只有1个线程回源数据,其他线程阻塞等待数据返回
+* 当cache命中失效数据时, 若多个线程并发访问时, 第一个访问的线程提交一个load数据的任务到公共线程池,然后和所有其他访问线程一样直接返回旧值
+* 实际通过LoadingCache.refresh(K)进行异步刷新, 如果想覆盖默认的刷新行为, 可以实现CacheLoader.reload(K, V)方法
+
+
+#### expireAfter
+比较少用
+```java
+public static void demo(){
+
+ MyTicker ticker = new MyTicker();
+
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .ticker(ticker)
+ //此时的效果为expireAfterWrite(5,TimeUnit.SECONDS)
+ .expireAfter(new Expiry() {
+ //1.如果写入key时是第一次创建,则调用该方法返回key剩余的超时时间, 单位纳秒ns
+ //currentTime为当前put时Ticket的时间,单位ns
+ @Override
+ public long expireAfterCreate(String key,String value, long currentTime) {
+ System.out.println("write first currentTime:"+currentTime/1_000_000_000L);
+ return 5_000_000_000L;//5s
+ }
+ //2.如果写入key时已经存在即更新key时,则调用该方法返回key剩余的超时时间, 单位纳秒ns
+ //currentTime为当前put时Ticket的时间,单位ns,durationTime为旧值(上次设置)剩余的存活时间,单位是ns
+ @Override
+ public long expireAfterUpdate(String key,String value, long currentTime,long durationTime) {
+ System.out.println("update currentTime:"+currentTime/1_000_000_000L+",leftTime:"+durationTime/1_000_000_000L);
+ return 5_000_000_000L;//5s
+ }
+ //3.如果key被访问时,则调用该方法返回key剩余的超时时间, 单位纳秒ns
+ //currentTime为read时Ticket的时间,单位ns,durationTime为旧值(上次设置)剩余的存活时间,单位是ns
+ @Override
+ public long expireAfterRead(String key,String value, long currentTime,long durationTime) {
+ System.out.println("read currentTime:"+currentTime/1_000_000_000L+",leftTime:"+durationTime/1_000_000_000L);
+ return durationTime;
+ }
+ })
+ .build(k -> UUID.randomUUID().toString());
+
+ cache.get("key1");//触发expireAfterCreate
+ ticker.advance(1, TimeUnit.SECONDS);//模拟时间消逝
+ cache.get("key1");//触发expireAfterRead,剩余生存时间4s
+ ticker.advance(2, TimeUnit.SECONDS);//模拟时间消逝
+ cache.put("key1","value1");//触发expireAfterUpdate,重置生存时间为5s
+ ticker.advance(3, TimeUnit.SECONDS);//模拟时间消逝
+ cache.get("key1");//触发expireAfterCreate,剩余生存时间为2s
+
+ }
+
+public class MyTicker implements Ticker {
+ private final AtomicLong nanos = new AtomicLong();
+ //模拟时间消逝
+ public void advance(long time, TimeUnit unit) {
+ this.nanos.getAndAdd(unit.toNanos(time));
+ }
+ @Override
+ public long read() {
+ return this.nanos.get();
+ }
+}
+```
+上述实现了Expiry接口, 分别重写了expireAfterCreate、expireAfterUpdate、expireAfterRead方法, 当第一次写入时、更新时、读访问时会分别调用这三个方法有机会重新设置剩余的失效时间, 上述案例模拟了expireAfterWrite(5,TimeUnit.SECONDS)的效果.
+
+
+注意点:
+
+* 以上基于时间驱逐, 数据超时失效和回源是两个动作, 必须依赖下一次访问. 为了避免服务启动时大量缓存穿透, 可以通过提前项目启动时手动预热
+* 一般expireAfterWrite和refreshAfterWrite结合使用, expire的时间t1大于refresh的时间t2, 在t2~t1内数据更新允许脏数据, t1之后必须要重新同步加载新数据
+
+### 3. 基于弱/软引用
+```java
+/**
+ * 允许GC时回收keys或values
+ */
+ public static void demo(){
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .weakKeys()
+ .weakValues()
+ .build(k -> UUID.randomUUID().toString());
+
+ }
+```
+* Caffeine.weakKeys() 使用弱引用存储key。如果没有强引用这个key,则GC时允许回收该条目
+
+* Caffeine.weakValues() 使用弱引用存储value。如果没有强引用这个value,则GC时允许回收该条目
+
+* Caffeine.softValues() 使用软引用存储value, 如果没有强引用这个value,则GC内存不足时允许回收该条目
+```java
+public static void demo(){
+ /**
+ * 使用软引用存储value,GC内存不够时会回收
+ */
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .softValues()//注意没有softKeys方法
+ .build(k -> UUID.randomUUID().toString());
+ }
+```
+### 总结
+Java4种引用的级别由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用
+
+| 引用类型 | 被垃圾回收时间 | 用途 | 生存时间 |
+|------|---------|---------|------------|
+| 强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
+| 软引用 | 在内存不足时 | 对象缓存 | 内存不足时终止 |
+| 弱引用 | 在垃圾回收时 | 对象缓存 | gc运行后终止 |
+| 虚引用 | Unknown | Unknown | Unknown |
+
+
+## 4 驱逐监听
+
+* eviction 指受策略影响而被删除
+* invalidation 值被调用者手动删除
+* removal 值因eviction或invalidation而发生的一种行为
+
+### 手动触发删除
+```java
+// individual key
+cache.invalidate(key)
+// bulk keys
+cache.invalidateAll(keys)
+// all keys
+cache.invalidateAll()
+```
+
+### 被驱逐的原因
+```java
+EXPLICIT:如果原因是这个,那么意味着数据被我们手动的remove掉了
+REPLACED:就是替换了,也就是put数据的时候旧的数据被覆盖导致的移除
+COLLECTED:这个有歧义点,其实就是收集,也就是垃圾回收导致的,一般是用弱引用或者软引用会导致这个情况
+EXPIRED:数据过期,无需解释的原因。
+SIZE:个数超过限制导致的移除
+```
+
+### 监听器
+```java
+public static void demo(){
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(5)
+ .recordStats()
+ .expireAfterWrite(2, TimeUnit.SECONDS)
+ .removalListener((String key, String value, RemovalCause cause) -> {
+ System.out.printf("Key %s was removed (%s)%n", key, cause);
+ })
+ .build(key -> UUID.randomUUID().toString());
+ for (int i = 0; i < 15; i++) {
+ cache.get(i+"");
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ //因为evict是异步线程去执行,为了看到效果稍微停顿一下
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ }
+```
+
+```log
+Key 0 was removed (SIZE)
+Key 1 was removed (SIZE)
+Key 6 was removed (SIZE)
+Key 7 was removed (SIZE)
+Key 8 was removed (SIZE)
+Key 9 was removed (SIZE)
+Key 10 was removed (SIZE)
+Key 2 was removed (EXPIRED)
+Key 3 was removed (EXPIRED)
+Key 4 was removed (EXPIRED)
+```
+
+## 5 统计
+```java
+public static void demo(){
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(10)
+ .expireAfterWrite(10, TimeUnit.SECONDS)
+ .recordStats()
+ .build(key -> {
+ if(key % 6 == 0 ){
+ return null;
+ }
+ return UUID.randomUUID().toString();
+ });
+
+ for (int i = 0; i < 20; i++) {
+ cache.get(i);
+ printStats(cache.stats());
+ }
+ for (int i = 0; i < 10; i++) {
+ cache.get(i);
+ printStats(cache.stats());
+ }
+ }
+
+ private static void printStats(CacheStats stats){
+ System.out.println("---------------------");
+ System.out.println("stats.hitCount():"+stats.hitCount());//命中次数
+ System.out.println("stats.hitRate():"+stats.hitRate());//缓存命中率
+ System.out.println("stats.missCount():"+stats.missCount());//未命中次数
+ System.out.println("stats.missRate():"+stats.missRate());//未命中率
+ System.out.println("stats.loadSuccessCount():"+stats.loadSuccessCount());//加载成功的次数
+ System.out.println("stats.loadFailureCount():"+stats.loadFailureCount());//加载失败的次数,返回null
+ System.out.println("stats.loadFailureRate():"+stats.loadFailureRate());//加载失败的百分比
+ System.out.println("stats.totalLoadTime():"+stats.totalLoadTime());//总加载时间,单位ns
+ System.out.println("stats.evictionCount():"+stats.evictionCount());//驱逐次数
+ System.out.println("stats.evictionWeight():"+stats.evictionWeight());//驱逐的weight值总和
+ System.out.println("stats.requestCount():"+stats.requestCount());//请求次数
+ System.out.println("stats.averageLoadPenalty():"+stats.averageLoadPenalty());//单次load平均耗时
+ }
+```
+
+
+## 6 其他
+
+### Ticker
+时钟, 方便测试模拟时间流逝
+```java
+public static void demo(){
+
+ MyTicker ticker = new MyTicker();
+
+ LoadingCache cache = Caffeine.newBuilder()
+ .maximumSize(500)
+ .ticker(ticker)
+ .expireAfterWrite(1, TimeUnit.SECONDS)
+ .build(k -> UUID.randomUUID().toString());
+
+ cache.get("key1");//触发expireAfterCreate
+ ticker.advance(1, TimeUnit.SECONDS);//模拟时间消逝
+ cache.get("key1");//触发expireAfterRead,剩余生存时间4s
+ ticker.advance(2, TimeUnit.SECONDS);//模拟时间消逝
+ cache.put("key1","value1");//触发expireAfterUpdate,重置生存时间为5s
+ }
+
+public class MyTicker implements Ticker {
+ private final AtomicLong nanos = new AtomicLong();
+ //模拟时间消逝
+ public void advance(long time, TimeUnit unit) {
+ this.nanos.getAndAdd(unit.toNanos(time));
+ }
+ @Override
+ public long read() {
+ return this.nanos.get();
+ }
+}
+```
\ No newline at end of file
diff --git a/Java三方库/mockito.md b/Java三方库/mockito.md
index 7af8cddd..a70b5786 100644
--- a/Java三方库/mockito.md
+++ b/Java三方库/mockito.md
@@ -27,7 +27,8 @@ public class ApplicationTest {
}
//2
public class ApplicationTest {
- @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
//code
}
@@ -711,7 +712,7 @@ mockito 会将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰
* 设值函数注入(set函数)
* 属性注入
-
+```java
package demo;
import java.util.Random;
@@ -745,6 +746,7 @@ public class ExampleService {
}
}
+```
编写测试类:
```java
import org.junit.Assert;
diff --git a/Java基础教程/Java实用技巧/02 Java的三种变量.md b/Java基础教程/Java实用技巧/02 Java的三种变量.md
new file mode 100644
index 00000000..8f90d961
--- /dev/null
+++ b/Java基础教程/Java实用技巧/02 Java的三种变量.md
@@ -0,0 +1,100 @@
+
+
+
+## 概述
+
+### 三种变量
+
+
+
+* VM options是JVM变量
+* Programp arguments是程序变量
+* Environment variables是环境变量
+
+## JVM变量
+
+### 是什么
+* 所谓的JVM变量和平时见到的不太一样,比如-Xmx1024m这样的,这里说的JVM变量更确切的说是java命令中的一个选项,我们前边在idea中进行配置的时候,使用的也是“-D”这个选项,在服务启动的时候会设置一个key-value的属性。并且从上面的注释也可以看到对“-D”的解释是“设置系统属性”
+
+### 怎么用
+
+* 需要注意一点这里的配置除了JVM已经定义好的,其他的自定义配置请使用前面说到的“-D”的形式,多个配置使用空格隔开
+
+
+* JVM启动参数
+
+
+* 获取JVM变量System.getProperty()
+* 也可以设置JVM变量System.setProperty("myproperty", "foo");
+```java
+@SpringBootApplication
+public class BootServer {
+ public static void main(String[] args) {
+ //获取vm参数
+ String value=System.getProperty("customer.value");
+ String key=System.getProperty("customer.key");
+ System.out.println("value:"+value+",key:"+key);
+ SpringApplication.run(BootServer.class);
+ }
+}
+```
+
+## 环境变量
+
+### 是什么
+* 所谓系统环境变量,更多的理解为某个服务的全局的环境变量,供这个服务中的所有组件来使用,
+
+
+### 怎样做
+
+* 环境变量的配置,在idea中是下面这样的配置
+
+
+* 获取环境变量。System.getenv()
+```java
+@SpringBootApplication
+public class BootServer {
+ public static void main(String[] args) {
+ //获取vm参数
+ String value=System.getProperty("customer.value");
+ String key=System.getProperty("customer.key");
+ System.out.println("value:"+value+",key:"+key);
+ //获取环境变量
+ String templateValue=System.getenv("template");
+ System.out.println("环境变量:"+templateValue);
+ SpringApplication.run(BootServer.class);
+ }
+}
+
+```
+## 程序变量
+### 是什么
+* 所谓程序变量指的是main方法中的参数
+
+
+### 怎样做
+
+* 在idea中“Program arguments”中配置程序变量,多个值使用“空格”分隔即可
+
+
+* 启动参数
+
+
+* 使用args[0]
+```java
+@SpringBootApplication
+public class BootServer {
+ public static void main(String[] args) {
+ //获取vm参数
+ String value=System.getProperty("customer.value");
+ String key=System.getProperty("customer.key");
+ System.out.println("value:"+value+",key:"+key);
+ //获取环境变量
+ String templateValue=System.getenv("template");
+ System.out.println("环境变量:"+templateValue);
+ //获取程序变量
+ System.out.println(args[0]+","+args[1]+","+args[2]);
+ SpringApplication.run(BootServer.class);
+ }
+}
+```
diff --git a/Java基础教程/Java实用技巧/03 javax postconstrct.md b/Java基础教程/Java实用技巧/03 javax postconstrct.md
new file mode 100644
index 00000000..1135d608
--- /dev/null
+++ b/Java基础教程/Java实用技巧/03 javax postconstrct.md
@@ -0,0 +1,4 @@
+1、@PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
+2、通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
+Constructor(构造方法) -> @Autowired(依赖注入)/@Value -> @PostConstruct(注释的方法)
+3、应用:在静态方法中调用依赖注入的Bean中的方法
\ No newline at end of file
diff --git a/Java基础教程/Java实用技巧/image/2022-12-23-10-31-14.png b/Java基础教程/Java实用技巧/image/2022-12-23-10-31-14.png
new file mode 100644
index 00000000..6e3f7cad
Binary files /dev/null and b/Java基础教程/Java实用技巧/image/2022-12-23-10-31-14.png differ
diff --git a/Java基础教程/Java实用技巧/image/2022-12-23-10-33-21.png b/Java基础教程/Java实用技巧/image/2022-12-23-10-33-21.png
new file mode 100644
index 00000000..f28a3a7d
Binary files /dev/null and b/Java基础教程/Java实用技巧/image/2022-12-23-10-33-21.png differ
diff --git a/Java基础教程/Java实用技巧/image/2022-12-23-10-35-25.png b/Java基础教程/Java实用技巧/image/2022-12-23-10-35-25.png
new file mode 100644
index 00000000..0f3e2403
Binary files /dev/null and b/Java基础教程/Java实用技巧/image/2022-12-23-10-35-25.png differ
diff --git a/Java基础教程/Java实用技巧/image/2022-12-23-10-38-22.png b/Java基础教程/Java实用技巧/image/2022-12-23-10-38-22.png
new file mode 100644
index 00000000..5e3e273a
Binary files /dev/null and b/Java基础教程/Java实用技巧/image/2022-12-23-10-38-22.png differ
diff --git a/Java基础教程/Java语言基础/09 Java反射.md b/Java基础教程/Java语言基础/09 Java反射.md
index 5c3e8f61..1f5c98d9 100644
--- a/Java基础教程/Java语言基础/09 Java反射.md
+++ b/Java基础教程/Java语言基础/09 Java反射.md
@@ -39,6 +39,29 @@ Class对象的作用
* 对象.getClass()
* 任何类型.class
+```java
+package com.zking.jee07.reflect;
+
+public class Test {
+
+ public static void main(String[] args) throws Exception {
+
+ //获取大写Class的三种方式
+ //1.Class.forName() 得到Student的模板
+ Class stuClazz =(Class)Class.forName("com.zking.jee07.reflect.Student");
+
+ //2.Student.class
+ Class stuClazz01 =Student.class;
+
+ //3.对象.getClass()
+ Student stu =new Student();
+ Class stuClazz02 =(Class)stu.getClass();
+
+ }
+
+}
+```
+
### 反射机制的使用
diff --git a/Java基础教程/Java语言基础/10 Java泛型.md b/Java基础教程/Java语言基础/10 Java泛型.md
index 4c5907da..7350dd94 100644
--- a/Java基础教程/Java语言基础/10 Java泛型.md
+++ b/Java基础教程/Java语言基础/10 Java泛型.md
@@ -14,6 +14,8 @@
- [4 泛型通配符](#4-泛型通配符)
- [5 泛型中的 KTVE](#5-泛型中的-ktve)
- [6 泛型的实现原理](#6-泛型的实现原理)
+ - [7 泛型实例化](#7-泛型实例化)
+ - [反射](#反射)
# 泛型机制
@@ -370,3 +372,74 @@ public class Apple{}
实际上编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外的是,如果构建泛型实例时使用了泛型语法,那么编译器将标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。
实际上编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast 指令用于强制类型转换,这一个过程就叫做『泛型翻译』。
+
+
+## 7 泛型实例化
+
+
+### 反射
+```java
+getClass().getGenericSuperclass()).getActualTypeArguments()[0].newInstance();
+
+
+
+ public void init(){
+
+ try {
+ Type[] typeArguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
+ for(Type type : typeArguments){
+ System.out.println("type:"+type);//打印映射的实际类
+ }
+ Class tClass = (Class) typeArguments[0];
+ Class dClass = (Class) typeArguments[1];
+ Class cClass = (Class) typeArguments[2];
+ this.t = tClass.newInstance();
+ this.d = dClass.newInstance();
+ this.e = cClass.newInstance();
+ } catch ( Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+```
+
+
+```java
+getConstructor().newInstance()
+
+public void init(){
+
+ try {
+ Type[] typeArguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
+ for(Type type : typeArguments){
+ System.out.println("type:"+type);
+ }
+ T e = (T) ((Class)typeArguments[0]).getConstructor().newInstance();
+ D e = (D) ((Class)typeArguments[1]).getConstructor().newInstance();
+ E e = (E) ((Class)typeArguments[2]).getConstructor().newInstance();
+ } catch ( Exception e) {
+ e.printStackTrace();
+ }
+ }
+```
+
+
+```java
+Class.forName(className).newInstance()
+
+ public void init(){
+
+ try {
+ Type[] typeArguments = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
+ for(Type type : typeArguments){
+ System.out.println("type:"+type);//打印映射的实际类
+ }
+ T t = (T) Class.forName(typeArguments[0].getTypeName()).newInstance();
+ D d = (D) Class.forName(typeArguments[1].getTypeName()).newInstance();
+ E e = (E) Class.forName(typeArguments[2].getTypeName()).newInstance();
+ } catch ( Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+```
\ No newline at end of file
diff --git a/Spring/Spring5/02 AOP.md b/Spring/Spring5/02 AOP.md
index 28db7cdd..a2b3275d 100644
--- a/Spring/Spring5/02 AOP.md
+++ b/Spring/Spring5/02 AOP.md
@@ -107,8 +107,8 @@ class UserDaoProxy implements InvocationHandler{
* 前置通知Before。
* 后置通知AfterReturn。正常返回才有
* 环绕通知Around。通过切入点修改前后
-* 异常通知afterThrowing
-* 最终通知after。finally无论出现异常都执行。
+* 异常通知AfterThrowing
+* 最终通知After。finally无论出现异常都执行。
### 切面
切面是一个工作。
diff --git a/Spring/Spring5/02 AOP详解.md b/Spring/Spring5/02 AOP详解.md
new file mode 100644
index 00000000..025c2eca
--- /dev/null
+++ b/Spring/Spring5/02 AOP详解.md
@@ -0,0 +1,649 @@
+## 1 概述
+### 概念
+
+AOP(Aspect Oriented Programming)面向切面思想,是Spring的三大核心思想之一(AOP-面向切面、IOC-控制反转、DI-依赖注入)。
+### 为什么AOP
+* 有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法,就是下面这样:
+
+
+代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。 有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中
+
+### AOP体系结构
+
+AOP要做的三件事:
+1. 在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行;(切入点)
+2. 什么时候切入,是业务代码执行前还是执行后;(通知)
+3. 切入后做什么事,比如做权限校验、日志记录等。
+
+
+
+
+* Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
+
+* Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
+
+* Aspect:切面,即Pointcut和Advice。
+
+* Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
+
+* Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
+
+
+### 特点
+
+面向切面编程,核心原理是使用动态代理模式在方法执行前后或出现异常时加入相关逻辑。
+
+
+* AOP是基于动态代理模式。
+* AOP是方法级别的。
+* AOP可以分离业务代码和关注点代码(重复代码),在执行业务代码时,动态的注入关注点代码。切面就是关注点代码形成的类。
+
+### 作用
+
+AOP能干什么
+* Spring声明式事务管理配置
+
+* Controller层的参数校验
+
+* 使用Spring AOP实现MySQL数据库读写分离案例分析
+
+* 在执行方法前,判断是否具有权限。
+
+* 对部分函数的调用进行日志记录。监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员。
+
+* 信息过滤,页面转发等等功能。
+
+## 2 原理
+
+### 代理模式
+为其他对象提供一种代理以控制对这个对象的访问。比如A对象要做一件事情,在没有代理前,自己来做,在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。
+
+
+### 静态代理模式
+静态代理就是在程序运行前就已经存在代理类的字节码文件,代理类和原始类的关系在运行前就已经确定。
+```java
+package test.staticProxy;
+// 接口
+public interface IUserDao {
+ void save();
+ void find();
+}
+//目标对象
+class UserDao implements IUserDao{
+ @Override
+ public void save() {
+ System.out.println("模拟:保存用户!");
+ }
+ @Override
+ public void find() {
+ System.out.println("模拟:查询用户");
+ }
+}
+/**
+ 静态代理
+ 特点:
+ 1. 目标对象必须要实现接口
+ 2. 代理对象,要实现与目标对象一样的接口
+ */
+class UserDaoProxy implements IUserDao{
+ // 代理对象,需要维护一个目标对象
+ private IUserDao target = new UserDao();
+ @Override
+ public void save() {
+ System.out.println("代理操作: 开启事务...");
+ target.save(); // 执行目标对象的方法
+ System.out.println("代理操作:提交事务...");
+ }
+ @Override
+ public void find() {
+ target.find();
+ }
+}
+```
+
+
+静态代理虽然保证了业务类只需关注逻辑本身,代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理。还有就是如果增加一个方法,除了实现类需要实现这个方法外,所有的代理类也要实现此方法。增加了代码的维护成本。那如何解决呢?使用动态代理。
+### 动态代理模式
+动态代理类的源码是在程序运行期间通过JVM反射等机制动态生成,代理类和委托类的关系是运行时才确定的。实例如下:
+```java
+ import java.lang.reflect.InvocationHandler;
+ import java.lang.reflect.Method;
+ import java.lang.reflect.Proxy;
+ // 接口
+ public interface IUserDao {
+ void save();
+ void find();
+ }
+ //目标对象
+ class UserDao implements IUserDao{
+ @Override
+ public void save() {
+ System.out.println("模拟: 保存用户!");
+ }
+ @Override
+ public void find() {
+ System.out.println("查询");
+ }
+ }
+ /**
+ * 动态代理:
+ * 代理工厂,给多个目标对象生成代理对象!
+ *
+ */
+ class ProxyFactory {
+ // 接收一个目标对象
+ private Object target;
+ public ProxyFactory(Object target) {
+ this.target = target;
+ }
+ // 返回对目标对象(target)代理后的对象(proxy)
+ public Object getProxyInstance() {
+ Object proxy = Proxy.newProxyInstance(
+ target.getClass().getClassLoader(), // 目标对象使用的类加载器
+ target.getClass().getInterfaces(), // 目标对象实现的所有接口
+ new InvocationHandler() { // 执行代理对象方法时候触发
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+
+ // 获取当前执行的方法的方法名
+ String methodName = method.getName();
+ // 方法返回值
+ Object result = null;
+ if ("find".equals(methodName)) {
+ // 直接调用目标对象方法
+ result = method.invoke(target, args);
+ } else {
+ System.out.println("开启事务...");
+ // 执行目标对象方法
+ result = method.invoke(target, args);
+ System.out.println("提交事务...");
+ }
+ return result;
+ }
+ }
+ );
+ return proxy;
+ }
+ }
+```
+
+
+
+
+在运行测试类中创建测试类对象代码中
+```java
+IUserDao proxy = (IUserDao)new ProxyFactory(target).getProxyInstance();
+```
+其实是JDK动态生成了一个类去实现接口,隐藏了这个过程:
+```java
+class $jdkProxy implements IUserDao{}
+```
+使用jdk生成的动态代理的前提是目标类必须有实现的接口。但这里又引入一个问题,如果某个类没有实现接口,就不能使用JDK动态代理,所以Cglib代理就是解决这个问题的。
+
+Cglib是以动态生成的子类继承目标的方式实现,在运行期动态的在内存中构建一个子类,如下:
+```java
+public class UserDao{}
+//Cglib是以动态生成的子类继承目标的方式实现,程序执行时,隐藏了下面的过程
+public class $Cglib_Proxy_class extends UserDao{}
+```
+Cglib使用的前提是目标类不能为final修饰。因为final修饰的类不能被继承。
+
+## 3 实例
+
+### 实例1 xml
+
+* 连接点
+
+```java
+import org.aspectj.lang.ProceedingJoinPoint;
+public interface IUserDao {
+ void save();
+}
+//用于测试Cglib动态代理
+class OrderDao {
+ public void save() {
+ //int i =1/0;用于测试异常通知
+ System.out.println("保存订单...");
+ }
+}
+//用于测试jdk动态代理
+class UserDao implements IUserDao {
+ public void save() {
+ //int i =1/0;用于测试异常通知
+ System.out.println("保存用户...");
+ }
+```
+
+* 切面
+
+```java
+//切面类
+class TransactionAop {
+ public void beginTransaction() {
+ System.out.println("[前置通知] 开启事务..");
+ }
+ public void commit() {
+ System.out.println("[后置通知] 提交事务..");
+ }
+ public void afterReturing(){
+ System.out.println("[返回后通知]");
+ }
+ public void afterThrowing(){
+ System.out.println("[异常通知]");
+ }
+ public void arroud(ProceedingJoinPoint pjp) throws Throwable{
+ System.out.println("[环绕前:]");
+ pjp.proceed(); // 执行目标方法
+ System.out.println("[环绕后:]");
+ }
+}
+```
+
+* xml配置
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+expression="execution(public * com.briup.aop.service.*.*(..))"
+这个引号""里面就是用表达式的方式来定义切入点,只要是符合我们这个表达式要求的,方法就是我们的连接点,
+连接点的集合就是我们要定义的切入点。表达式中从左到右的*号:第一个* 表示方法的返回类型不限。第二个* 表示包中的任意一个类
+第三个* 表示类中的任意一个方法。同时方法的参数也没有限制.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+
+### 实例2 注解
+
+* 引入AOP依赖
+
+```xml
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+```
+
+* 切面:创建一个AOP切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现advice:
+
+```java
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class LogAdvice {
+ // 定义一个切点:所有被GetMapping注解修饰的方法会织入advice
+ @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
+ private void logAdvicePointcut() {}
+
+ // Before表示logAdvice将在目标方法执行前执行
+ @Before("logAdvicePointcut()")
+ public void logAdvice(){
+ // 这里只是一个示例,你可以写任何处理逻辑
+ System.out.println("get请求的advice触发了");
+ }
+}
+```
+
+
+### 实例3 注解切点
+
+* 自定义注解
+```java
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PermissionAnnotation{
+}
+```
+
+* 切面:创建第一个AOP切面类,只要在类上加个@Aspect注解即可。@Component 注解将该类交给 Spring 来管理,在这个类里实现权限校验逻辑。
+
+```java
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+@Order(1)
+public class PermissionFirstAdvice {
+
+ // 定义一个切面,括号内写入第1步中自定义注解的路径
+ @Pointcut("@annotation(com.mu.demo.annotation.PermissionAnnotation)")
+ private void permissionCheck() {
+ }
+
+ @Around("permissionCheck()")
+ public Object permissionCheckFirst(ProceedingJoinPoint joinPoint) throws Throwable {
+ System.out.println("===================第一个切面===================:" + System.currentTimeMillis());
+
+ //获取请求参数,详见接口类
+ Object[] objects = joinPoint.getArgs();
+ Long id = ((JSONObject) objects[0]).getLong("id");
+ String name = ((JSONObject) objects[0]).getString("name");
+ System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
+ System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);
+
+ // id小于0则抛出非法id的异常
+ if (id < 0) {
+ return JSON.parseObject("{\"message\":\"illegal id\",\"code\":403}");
+ }
+ return joinPoint.proceed();
+ }
+}
+```
+
+
+## 4 AOP相关注解
+
+### @Pointcut
+@Pointcut 注解,用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。
+
+* @Pointcut 注解指定一个切点,定义需要拦截的东西,这里介绍两个常用的表达式:一个是使用 execution(),另一个是使用 annotation()
+
+```
+@Aspect
+@Component
+public class LogAspectHandler {
+
+ /**
+ * 定义一个切面,拦截 com.mutest.controller 包和子包下的所有方法
+ */
+ @Pointcut("execution(* com.mutest.controller..*.*(..))")
+ public void pointCut() {}
+}
+```
+### execution表达式:
+以 `execution(* com.mutest.controller..*.*(..))`表达式为例:
+
+* 第一个 * 号的位置:表示返回值类型,* 表示所有类型。
+
+* 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
+
+* 第二个 * 号的位置:表示类名,* 表示所有类。
+
+* `*(..)`:这个星号表示方法名,* 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
+
+
+### annotation() 表达式:
+
+annotation() 方式是针对某个注解来定义切点,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:
+```java
+@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
+public void annotationPointcut() {}
+```
+
+### @Around
+
+@Around注解用于修饰Around增强处理,Around增强处理非常强大,表现在:
+
+* @Around可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法。这个特性的实现在于,调用ProceedingJoinPoint参数的proceed()方法才会执行目标方法。
+
+* @Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
+
+Around增强处理有以下特点:
+
+* 当定义一个Around增强处理方法时,该方法的第一个形参必须是 ProceedingJoinPoint 类型(至少一个形参)。在增强处理方法体内,调用ProceedingJoinPoint的proceed方法才会执行目标方法:这就是@Around增强处理可以完全控制目标方法执行时机、如何执行的关键;如果程序没有调用ProceedingJoinPoint的proceed方法,则目标方法不会执行。
+
+* 调用ProceedingJoinPoint的proceed方法时,还可以传入一个Object[ ]对象,该数组中的值将被传入目标方法作为实参——这就是Around增强处理方法可以改变目标方法参数值的关键。这就是如果传入的Object[ ]数组长度与目标方法所需要的参数个数不相等,或者Object[ ]数组元素与目标方法所需参数的类型不匹配,程序就会出现异常。
+
+* @Around功能虽然强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturning就能解决的问题,就没有必要使用Around了。如果需要目标方法执行之前和之后共享某种状态数据,则应该考虑使用Around。尤其是需要使用增强处理阻止目标的执行,或需要改变目标方法的返回值时,则只能使用Around增强处理了。
+
+下面,在前面例子上做一些改造,来观察@Around的特点。
+
+* 自定义注解类不变。首先,定义接口类:
+```java
+package com.example.demo;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping(value = "/permission")
+public class TestController {
+ @RequestMapping(value = "/check", method = RequestMethod.POST)
+ @PermissionsAnnotation()
+ public JSONObject getGroupList(@RequestBody JSONObject request) {
+ return JSON.parseObject("{\"message\":\"SUCCESS\",\"code\":200,\"data\":" + request + "}");
+ }
+}
+```
+* 唯一切面类(前面案例有两个切面类,这里只需保留一个即可):
+```java
+package com.example.demo;
+
+import com.alibaba.fastjson.JSONObject;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+
+@Aspect
+@Component
+@Order(1)
+public class PermissionAdvice {
+
+ @Pointcut("@annotation(com.example.demo.PermissionsAnnotation)")
+ private void permissionCheck() {
+ }
+
+
+ @Around("permissionCheck()")
+ public Object permissionCheck(ProceedingJoinPoint joinPoint) throws Throwable {
+ System.out.println("===================开始增强处理===================");
+
+ //获取请求参数,详见接口类
+ Object[] objects = joinPoint.getArgs();
+ Long id = ((JSONObject) objects[0]).getLong("id");
+ String name = ((JSONObject) objects[0]).getString("name");
+ System.out.println("id1->>>>>>>>>>>>>>>>>>>>>>" + id);
+ System.out.println("name1->>>>>>>>>>>>>>>>>>>>>>" + name);
+
+ // 修改入参
+ JSONObject object = new JSONObject();
+ object.put("id", 8);
+ object.put("name", "lisi");
+ objects[0] = object;
+
+ // 将修改后的参数传入
+ return joinPoint.proceed(objects);
+ }
+}
+```
+
+### @Before
+
+@Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址等等,这个在做个人站点的时候都能用得到,都是常用的方法。例如下面代码:
+```java
+@Aspect
+@Component
+@Slf4j
+public class LogAspectHandler {
+ /**
+ * 在上面定义的切面方法之前执行该方法
+ * @param joinPoint jointPoint
+ */
+ @Before("pointCut()")
+ public void doBefore(JoinPoint joinPoint) {
+ log.info("====doBefore方法进入了====");
+
+ // 获取签名
+ Signature signature = joinPoint.getSignature();
+ // 获取切入的包名
+ String declaringTypeName = signature.getDeclaringTypeName();
+ // 获取即将执行的方法名
+ String funcName = signature.getName();
+ log.info("即将执行方法为: {},属于{}包", funcName, declaringTypeName);
+
+ // 也可以用来记录一些信息,比如获取请求的 URL 和 IP
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+ // 获取请求 URL
+ String url = request.getRequestURL().toString();
+ // 获取请求 IP
+ String ip = request.getRemoteAddr();
+ log.info("用户请求的url为:{},ip地址为:{}", url, ip);
+ }
+}
+```
+JointPoint 对象很有用,可以用它来获取一个签名,利用签名可以获取请求的包名、方法名,包括参数(通过 joinPoint.getArgs()获取)等。
+
+
+### @After
+
+@After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。
+```java
+@Aspect
+@Component
+@Slf4j
+public class LogAspectHandler {
+ /**
+ * 定义一个切面,拦截 com.mutest.controller 包下的所有方法
+ */
+ @Pointcut("execution(* com.mutest.controller..*.*(..))")
+ public void pointCut() {}
+
+ /**
+ * 在上面定义的切面方法之后执行该方法
+ * @param joinPoint jointPoint
+ */
+ @After("pointCut()")
+ public void doAfter(JoinPoint joinPoint) {
+
+ log.info("==== doAfter 方法进入了====");
+ Signature signature = joinPoint.getSignature();
+ String method = signature.getName();
+ log.info("方法{}已经执行完", method);
+ }
+}
+```
+到这里,我们来写个 Controller 测试一下执行结果,新建一个 AopController 如下:
+```java
+@RestController
+@RequestMapping("/aop")
+public class AopController {
+
+ @GetMapping("/{name}")
+ public String testAop(@PathVariable String name) {
+ return "Hello " + name;
+ }
+}
+```
+启动项目,在浏览器中输入:localhost:8080/aop/csdn,观察一下控制台的输出信息:
+```
+====doBefore 方法进入了====
+即将执行方法为: testAop,属于com.itcodai.mutest.AopController包
+用户请求的 url 为:http://localhost:8080/aop/name,ip地址为:0:0:0:0:0:0:0:1
+==== doAfter 方法进入了====
+方法 testAop 已经执行完
+```
+从打印出来的 Log 中可以看出程序执行的逻辑与顺序,可以很直观的掌握 @Before 和 @After 两个注解的实际作用。
+
+
+### @AfterReturning
+
+@AfterReturning 注解和 @After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理,例如:
+```java
+@Aspect
+@Component
+@Slf4j
+public class LogAspectHandler {
+ /**
+ * 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强
+ * @param joinPoint joinPoint
+ * @param result result
+ */
+ @AfterReturning(pointcut = "pointCut()", returning = "result")
+ public void doAfterReturning(JoinPoint joinPoint, Object result) {
+
+ Signature signature = joinPoint.getSignature();
+ String classMethod = signature.getName();
+ log.info("方法{}执行完毕,返回参数为:{}", classMethod, result);
+ // 实际项目中可以根据业务做具体的返回值增强
+ log.info("对返回参数进行业务上的增强:{}", result + "增强版");
+ }
+}
+```
+需要注意的是,在@AfterReturning 注解 中,属性 returning 的值必须要和参数保持一致,否则会检测不到。该方法中的第二个入参就是被切方法的返回值,在 doAfterReturning 方法中可以对返回值进行增强,可以根据业务需要做相应的封装。我们重启一下服务,再测试一下:
+
+方法 testAop 执行完毕,返回参数为:Hello CSDN
+
+对返回参数进行业务上的增强:Hello CSDN 增强版
+
+### @AfterThrowing
+
+当被切方法执行过程中抛出异常时,会进入@AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。
+```
+@Aspect
+@Component
+@Slf4j
+public class LogAspectHandler {
+ /**
+ * 在上面定义的切面方法执行抛异常时,执行该方法
+ * @param joinPoint jointPoint
+ * @param ex ex
+ */
+ @AfterThrowing(pointcut = "pointCut()", throwing = "ex")
+ public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
+ Signature signature = joinPoint.getSignature();
+ String method = signature.getName();
+ // 处理异常的逻辑
+ log.info("执行方法{}出错,异常为:{}", method, ex);
+ }
+}
+```
\ No newline at end of file
diff --git a/Spring/Spring5/image/2022-12-22-11-40-02.png b/Spring/Spring5/image/2022-12-22-11-40-02.png
new file mode 100644
index 00000000..9d79533a
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-11-40-02.png differ
diff --git a/Spring/Spring5/image/2022-12-22-11-40-09.png b/Spring/Spring5/image/2022-12-22-11-40-09.png
new file mode 100644
index 00000000..5f2657fe
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-11-40-09.png differ
diff --git a/Spring/Spring5/image/2022-12-22-11-40-29.png b/Spring/Spring5/image/2022-12-22-11-40-29.png
new file mode 100644
index 00000000..e4fd6cf7
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-11-40-29.png differ
diff --git a/Spring/Spring5/image/2022-12-22-12-04-42.png b/Spring/Spring5/image/2022-12-22-12-04-42.png
new file mode 100644
index 00000000..f9428c92
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-12-04-42.png differ
diff --git a/Spring/Spring5/image/2022-12-22-12-05-30.png b/Spring/Spring5/image/2022-12-22-12-05-30.png
new file mode 100644
index 00000000..13e2d67e
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-12-05-30.png differ
diff --git a/Spring/Spring5/image/2022-12-22-12-05-37.png b/Spring/Spring5/image/2022-12-22-12-05-37.png
new file mode 100644
index 00000000..13e2d67e
Binary files /dev/null and b/Spring/Spring5/image/2022-12-22-12-05-37.png differ
diff --git a/Spring/Springboot/04 Properties配置文件.md b/Spring/Springboot/04 属性配置文件.md
similarity index 88%
rename from Spring/Springboot/04 Properties配置文件.md
rename to Spring/Springboot/04 属性配置文件.md
index f5154eb8..d6ac7948 100644
--- a/Spring/Springboot/04 Properties配置文件.md
+++ b/Spring/Springboot/04 属性配置文件.md
@@ -39,23 +39,6 @@ spring.my-example.url[1]=http://spring.io
pring.my-example.url[0]=http://example.com
spring.my-example.url[1]=http://spring.io
```
-
-* yaml中使用列表
-
-```
-spring:
- my-example:
- url:
- - http://example.com
- - http://spring.io
-
-```
-* yaml文件中使用,分割列表
-```
-spring:
- my-example:
- url: http://example.com, http://spring.io
-```
### Map类型
Map类型在properties和yaml中的标准配置方式如下:
@@ -66,13 +49,7 @@ Map类型在properties和yaml中的标准配置方式如下:
spring.my-example.foo=bar
spring.my-example.hello=world
```
-* yaml格式:
-```
-spring:
- my-example:
- foo: bar
- hello: world
-```
+
### 使用随机数配置
@@ -242,37 +219,32 @@ person:
## 3 其他配置方式
-### 系统环境变量
+### JVM变量/系统属性
java程序启动参数 -D是用来做什么:
-Set a system property value. If value is a string that contains spaces, you must enclose the string in double quotes。在运行改程序时加上JVM参数-Ddubbo.token=“666” 或者 -Ddubbo.token=666,那么运行之后你可以看到控制台输出了666!一点值得注意的是,需要设置的是JVM参数而不是program参数
+使用空格作为分割符,如果一个key或者value包含空格,则需要双引号。在运行改程序时加上JVM参数-Ddubbo.token=“666” 那么运行之后你可以看到控制台输出了666!一点值得注意的是,需要设置的是JVM参数而不是program参数
```
java -Dfoo="some string" SomeClass
+-D"spring.my-example.url[0]=http://example.com"
+-D"spring.my-example.url[1]=http://spring.io"
+-Dspring.my-example.url=http://example.com,http://spring.io
```
-* 列表形式:由于环境变量中无法使用[和]符号,所以使用_来替代。任何由下划线包围的数字都会被认为是[]的数组形式。
+### 环境变量
+由于环境变量中无法使用[和]符号,所以使用_来替代。任何由下划线包围的数字都会被认为是[]的数组形式。
```
MY_FOO_1_ = my.foo[1]
MY_FOO_1_BAR = my.foo[1].bar
MY_FOO_1_2_ = my.foo[1][2]
```
-### 通过命令行配置
-在启动java应用是,添加配置参数。系统属性的绑定也与文件属性的绑定类似,通过[]来标示,同样的,他也支持逗号分割的方式
+### 程序变量
+在启动java应用是,添加配置参数。系统属性的绑定也与文件属性的绑定类似,通过[]来标识列表,同样的,他也支持逗号分割的方式
```
java -jar xxx.jar --server.port=8888
-
--D"spring.my-example.url[0]=http://example.com"
--D"spring.my-example.url[1]=http://spring.io"
-
-
--Dspring.my-example.url=http://example.com,http://spring.io
-
```
-
-
## 4 多环境配置
### 多环境配置文件
@@ -295,7 +267,7 @@ application.yml/properties总是会被加载,不管是否配置spring.profile.
// -Dspring.profiles.active=mydev123一定要放-jar之前能触发java属性方式
java -Dspring.profiles.active=mydev123 -jar SpringBootEnv-1.0.jar
-// 通过java代码设置系统属性的方式。
+// 通过java代码设置JVM系统属性的方式。
System.setProperty("spring.profiles.active","mydev");
```
3. 命令行的方式。在启动jar时指定加载配置(命令行方式)
@@ -366,16 +338,16 @@ public class getProperties {
```
-### @ConfigurationProperties+Componet
+### @ConfigurationProperties
1. 只有在容器中的组件,才会有Springboot的强大功能。
2. 从配置文件中自动加载,进行属性配置,然后使用Componet注册
-@EnableConfigurationProperties + @ConfigurationProperties
+
假设在propertes配置中有这样一个配置:
```
com.didispace.foo=bar
```
-我们为它创建对应的配置类:
+**方法一:创建对应的配置类:**
```java
@Component
@ConfigurationProperties(prefix = "com.didispace")
@@ -385,8 +357,8 @@ public class FooProperties {
}
```
-接下来,通过最新的Binder就可以这样来拿配置信息了:
-```
+**方法二:通过Binder绑定:**
+```java
@SpringBootApplication
public class Application {
@@ -402,12 +374,10 @@ public class Application {
}
```
-
-### @ConfigurationProperties+ @EnableConfigurationProperties
-
+**方法三:@EnableConfigurationProperties+ @ConfigurationProperties**
1. 在配置类上开启属性配置功能。开启car的属性配置功能
2. 该中方法对配置类进行修改然后装配。不需要修改类本身的代码。
-```
+```java
@EnableConfigurationProperties(Car.class)
class MyConfig{
@@ -581,4 +551,17 @@ public class MailConfig
//省略getter与setter方法...
}
-```
\ No newline at end of file
+```
+
+### ConfigurationProperty和Value对比
+
+| 描述 | @ConfigurationProperties | @Value |
+|---|---|---|
+| 底层框架 | Spring Boot | Spring |
+| 使用位置 | 标注在 JavaBean 的类名上 | 标注在 JavaBean 的属性上 |
+| 功能不同 | 用于批量绑定配置文件中的配置 | 只能一个一个的指定需要绑定的配置 |
+| 需要setXX()方法 | 需要 | 有没有均可 |
+| 复杂类型属性注入 | 支持所有类型数据的封装,例如 Map、List、Set、以及对象等; | 不支持复杂类型,只支持基本数据类型的封装,例如字符串、布尔值、整数等类型。 |
+| 松散绑定 | 支持 例如实体类 Person 中有一个属性为 firstName,那么配置文件中的属性名支持以下写法: person.firstName person.first-name person.first_name PERSON_FIRST_NAME | 不支持 |
+| SpEl支持 即${xxx}的EL表达式 | 不支持 | 支持,例 @Value("${user.id}") private String id; @Value("${user.name}") private String name; |
+| 应用场景 | 若专门编写了一个 JavaBean 来和配置文件进行映射,则建议使用 @ConfigurationProperties 注解。 | 若只是获取配置文件中的某项值,则推荐使用 @Value 注解; |
diff --git a/test.json b/test.json
index 5bf05f4e..082bdea2 100644
--- a/test.json
+++ b/test.json
@@ -1,90 +1,191 @@
-{
- "status": "UP",
- "details": {
- "SOFABootReadinessHealthCheckInfo": {
- "status": "UP",
- "details": {
- "HealthChecker": {
- "alipaySofaRuntimeHealthChecker": {
- "status": "UP"
- },
- "sofaComponentHealthChecker": {
- "status": "UP",
- "details": {
- "reference:com.alipay.sofa.cloud.osp.instance.facade.MeterFacade:#-259276000": "[bolt,passed] [jvm,passed]",
- "extension-point:opscloudExtension$opsSyncChangeService": "passed",
- "service:com.alipay.opscloudcore.common.service.client.http.HttpHeartbeatCheck": "[jvm,passed]",
- "reference:com.alipay.sofa.cloud.osp.instance.facade.InstanceFacade:#-528282148": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.ExecutionNotifyClient": "[jvm,passed]",
- "extension-point:opscloudExtension$opsChangeService": "passed",
- "service:com.alipay.sofa.servicegovern.common.service.facade.RouterRuleFacade": "[tr,passed]",
- "reference:com.alipay.sofa.cloud.osp.user.facade.UserFacade:#1276066022": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.common.service.facade.DrmServiceFacade": "[bolt,passed]",
- "reference:com.alipay.antcloud.drmdata.facade.DrmDataQueryFacade:#1340292234": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.opscloudcore.framework.sdk.clientframework.SyncChangeServiceShortcut": "[jvm,passed]",
- "reference:com.alipay.antscheduler.facade.IAntJobFacade:#-54943409": "[jvm,passed] [bolt,passed]",
- "service:com.alipay.opscloudcore.common.service.client.OpsChngTraceClient": "[jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.OpscloudApiSwitch": "[jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.core.service.opscloud.template.OpscloudFacadeTemplate": "[jvm,passed]",
- "service:com.alipay.huanyu.facade.change.ChangePlanCallback:dsrconsoleMAIN_SITE": "[tr,passed]",
- "service:com.alipay.opscloudcore.framework.core.facade.OpscloudClientSyncServiceFacade:dsrconsole": "[tr,passed] [jvm,passed]",
- "reference:com.alipay.antcloud.drmdata.facade.DrmDataClusterFacade:#-234876751": "[bolt,passed] [jvm,passed]",
- "extension-point:opscloudExtension$opsChangeTransaction": "passed",
- "service:com.alipay.opscloudcore.common.service.client.ChangeServiceCheckClient": "[jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.http.HttpOpsChngTraceFacade": "[jvm,passed]",
- "reference:com.alipay.huanyu.facade.change.ChangePlanFacade:#2078914018": "[tr,passed] [jvm,passed]",
- "reference:com.alipay.sofa.discovery.sync.enterprise.facade.api.ServiceRestFacade:#223367432": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.http.HttpChangeServiceSyncExecuteFacade": "[jvm,passed]",
- "reference:com.alipay.sofa.cloud.osp.instance.facade.OSPMetadataFacade:#1502309493": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.http.HttpChangeServiceCheckFacade": "[jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.ChangeServiceAsyncCheckClient": "[jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.ChangeServiceSyncExecuteClient": "[jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.core.service.opscloud.OpscloudStrategy": "[jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.common.service.facade.ReportServiceFacade": "[tr,passed]",
- "extension-point:opscloudClientExtension$facades": "passed",
- "reference:com.alipay.antscheduler.facade.ISwitchZoneFacade:#-1973549567": "[bolt,passed] [jvm,passed]",
- "reference:com.alipay.antscheduler.facade.IJobTriggerInstanceFacade:#973874193": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.opscloudcore.common.service.client.http.HttpChangeServiceAsyncCheckFacade": "[jvm,passed]",
- "reference:com.alipay.sofa.cloud.auth.AuthenticationService:#-1387992122": "[bolt,passed] [jvm,passed]",
- "reference:com.alipay.antscheduler.facade.IJobExecuteInstanceFacade:#1941186356": "[bolt,passed] [jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.common.service.facade.DrmResourceMetaFacade": "[bolt,passed]",
- "service:com.alipay.opscloudcore.common.service.client.extension.OpscloudClientExtensionImpl": "[jvm,passed]",
- "service:com.alipay.antcloud.dsrconsole.common.service.facade.AuthFacade": "[bolt,passed]",
- "reference:com.alipay.sofa.discovery.sync.enterprise.facade.api.ClusterRestFacade:#685631288": "[bolt,passed] [jvm,passed]",
- "reference:com.alipay.sofa.cloud.osp.console.openapi.OspOpenAPIConfigFacade:#208429739": "[bolt,passed] [jvm,passed]"
- }
- }
- },
- "ReadinessCheckCallback": {
- "applicationAfterReadinessCheckCallback": {
- "status": "UP"
- },
- "lazyActivateBeanProcessor": {
- "status": "UP"
- },
- "rpcAfterHealthCheckCallback": {
- "status": "UP"
- }
- }
- }
- },
- "diskSpace": {
- "status": "UP",
- "details": {
- "total": 528311816192,
- "free": 457311166464,
- "threshold": 10485760,
- "exists": true
- }
- },
- "pingHealthContributor": {
- "status": "UP"
- },
- "sessionConnectionHealthChecker": {
- "status": "UP",
- "details": {
- "sessionConnection": "session connection is health"
- }
- }
- }
-}
\ No newline at end of file
+| sofa.ms.sg.apprelations.query | 获取应用关联数据 |
+| sofa.ms.sg.apps.list | 获取应用列表 |
+| sofa.ms.sg.serviceclients.sync | 微服务-同步客户端下线事件 |
+| sofa.ms.sg.serviceheartbeats.sync | 微服务-同步心跳数据 |
+| sofa.ms.sg.servicekeyword.query | 获取服务候选词 |
+| sofa.ms.sg.serviceparam.batchquery | 微服务-查询全部服务参数信息 |
+| sofa.ms.sg.serviceparams.disable | 禁用一个服务下某个服务提供者 |
+| sofa.ms.sg.serviceparams.enable | 启动一个服务下某个服务提供者 |
+| sofa.ms.sg.serviceparams.reinit | 恢复服务提供者默认设置 |
+| sofa.ms.sg.serviceparamweight.set | 设置服务提供者权重 |
+| sofa.ms.sg.servicepubkeys.query | 根据ip搜索服务提供方 |
+| sofa.ms.sg.servicepublishers.offline | 提供给 paas 平台,发布时先调用下线接口,再发布 |
+| sofa.ms.sg.servicepublishers.query | 分页查询发布者信息 |
+| sofa.ms.sg.servicepublishers.sync | 微服务-同步服务发布者信息 |
+| sofa.ms.sg.servicepubrelations.query | 获取服务发布者关系 |
+| sofa.ms.sg.servicepubs.list | 微服务-通过DataId和IP获取pub服务详情 |
+| sofa.ms.sg.servicepubs.query | 获取发布者服务详情 |
+| sofa.ms.sg.services.query | 微服务-根据关键词搜索 |
+| sofa.ms.sg.servicesubkeys.query | 根据ip搜索消费者 |
+| sofa.ms.sg.servicesubrelations.query | 获取服务消费者数据 |
+| sofa.ms.sg.servicesubs.list | 微服务-通过DataId和IP获取sub服务详情 |
+| sofa.ms.sg.servicesubs.query | 获取订阅者服务详情 |
+| sofa.ms.sg.servicesubscribers.query | 分页查询消费者 |
+| sofa.ms.sg.servicesubscribers.sync | 微服务-同步服务发布者信息 |
+| sofa.ms.sg.servicesummarys.query | 微服务-获取服务汇总信息 |
+| sofa.ms.sg.serviceapps.list | 获取发布服务的应用列表 |
+
+
+
+### DRM动态配置
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.drm.drmapps.all | 查询实例App name |
+| sofa.ms.drm.drmattributes.add | 添加drm属性 |
+| sofa.ms.drm.drmattributes.delete | 删除drm资源属性 |
+| sofa.ms.drm.drmattributes.get | 获取drm资源属性 |
+| sofa.ms.drm.drmattributes.update | 更新drm资源属性 |
+| sofa.ms.drm.drmcells.all | 获取实例的单元格 |
+| sofa.ms.drm.drmclients.query | 搜索客户端信息 |
+| sofa.ms.drm.drmdata.add | 推送drm配置 |
+| sofa.ms.drm.drmdatacells.list | 获取Drm数据单元信息 |
+| sofa.ms.drm.drmgraydata.add | 灰度推送数据 |
+| sofa.ms.drm.drmhosts.query | 搜索客户端信息 |
+| sofa.ms.drm.drmpushlogs.list | 分页搜索推送日志 |
+| sofa.ms.drm.drmpushlogs.query | 按条件分页查询推送日志 |
+| sofa.ms.drm.drmresources.add | 添加drm资源 |
+| sofa.ms.drm.drmresources.delete | Delete resource |
+| sofa.ms.drm.drmresources.export | 当前workspace下所有资源元信息 |
+| sofa.ms.drm.drmresources.import | 导入drm资源元信息 |
+| sofa.ms.drm.drmresources.query | 分页查询Drm资源 |
+| sofa.ms.drm.drmresources.update | 更新drm资源 |
+| sofa.ms.sg.resource.export | 资源以文件形式导出 |
+| sofa.ms.sg.resource.import | 资源以文件形式导入导出 |
+
+
+
+### 服务治理——服务限流
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.guardian.guardianappnames.all | 微服务-查询appName列表 |
+| sofa.ms.guardian.guardianapps.add | 添加 监控App |
+| sofa.ms.guardian.guardianapps.all | 微服务-获取app列表 |
+| sofa.ms.guardian.guardianapps.delete | 删除监控 app |
+| sofa.ms.guardian.guardianapps.get | ms -根据appName获取app |
+| sofa.ms.guardian.guardianapps.query | 微服务-分页查询app |
+| sofa.ms.guardian.guardianapps.update | ws-更新监控app |
+| sofa.ms.guardian.guardianconfig.get | 微服务-配置获取 |
+| sofa.ms.guardian.guardiangrayrules.add | 灰度推送 |
+| sofa.ms.guardian.guardiangrayrulestatus.update | 更改规则灰度状态 |
+| sofa.ms.guardian.guardianrulepushhistorys.list | 查询推送历史 |
+| sofa.ms.guardian.guardianrulepushhistorys.query | 分页查询推送历史 |
+| sofa.ms.guardian.guardianrules.add | 添加规则 |
+| sofa.ms.guardian.guardianrules.delete | 删除规则 |
+| sofa.ms.guardian.guardianrules.export | 导出guardian路由规则 |
+| sofa.ms.guardian.guardianrules.get | 根据id获取规则 |
+| sofa.ms.guardian.guardianrules.import | 导入guardian规则 |
+| sofa.ms.guardian.guardianrules.query | ms - 规则配置分页 |
+| sofa.ms.guardian.guardianrules.update | 更新规则 |
+| sofa.ms.guardian.guardianrulestatus.update | 更新规则状态 |
+
+
+### 服务治理——服务路由
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.routerruleallapps.list | 查询已存在服务路由规则的应用接口 |
+| sofa.ms.sg.routerruleallservices.list | 查询已存在服务路由规则的服务接口 |
+| sofa.ms.sg.routerruleappnames.list | 微服务-通过DataId获取AppNames |
+| sofa.ms.sg.routerruledispatchlogs.query | 微服务-获取调度日志 |
+| sofa.ms.sg.routerrulegroup.delete | 删除路由规则组 |
+| sofa.ms.sg.routerrulegroup.export | 路由规则导出 |
+| sofa.ms.sg.routerrulegroup.import | 路由规则导出 |
+| sofa.ms.sg.routerrulegroupapps.list | 查询服务路由一级列表 |
+| sofa.ms.sg.routerrulegroups.add | 添加服务路由规则 |
+| sofa.ms.sg.routerrulegroups.list | 查询服务路由二级例表 |
+| sofa.ms.sg.routerrulegroups.update | 更新服务路由规则 |
+| sofa.ms.sg.routerrulegroupstatus.update | 更新路由规则组状态 |
+| sofa.ms.sg.routerrules.enable | 微服务-启用路由器规则组 |
+| sofa.ms.sg.routerrules.get | 查询路由规则 |
+| sofa.ms.sg.routerrulesnapshots.get | 微服务-获取快照最后一个 |
+| sofa.ms.sg.routerrulesnapshots.rollback | 微服务-回滚快照最后一个 |
+| sofa.ms.sg.routerrulestatus.query | 获取路由规则状态 |
+| sofa.ms.sg.routerruleversions.list | 获取路由规则组版本列表 |
+
+
+
+### 服务治理——服务熔断
+
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.circuitbreakerrules.add | 添加熔断规则 |
+| sofa.ms.sg.circuitbreakerrules.all | 查询所有的服务熔断的appName及对应的规则数 |
+| sofa.ms.sg.circuitbreakerrules.delete | 删除熔断规则 |
+| sofa.ms.sg.circuitbreakerrules.enable | 生效熔断规则 |
+| sofa.ms.sg.circuitbreakerrules.export | 熔断规则导出 |
+| sofa.ms.sg.circuitbreakerrules.get | 通过appName查询熔断规则 |
+| sofa.ms.sg.circuitbreakerrules.import | 熔断规则导入 |
+| sofa.ms.sg.circuitbreakerrules.list | 获取租户下服务熔断规则 |
+| sofa.ms.sg.circuitbreakerrules.query | 根据实例id和服务id获取所有荣富安规则 |
+| sofa.ms.sg.circuitbreakerrules.set | 设置熔断规则运行模式 |
+| sofa.ms.sg.circuitbreakerrules.update | 更新熔断规则 |
+| sofa.ms.sg.downgradegroups.disable | 全局关闭降级规则 |
+
+### 服务治理——服务降级
+
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.downgradegroups.enable | 全局开启降级规则 |
+| sofa.ms.sg.downgradegroups.query | 查询降级规则组列表 |
+| sofa.ms.sg.downgrades.add | 新增降级规则 |
+| sofa.ms.sg.downgrades.delete | 删除降级规则 |
+| sofa.ms.sg.downgrades.disable | 关闭一条降级规则 |
+| sofa.ms.sg.downgrades.enable | 开启一条降级规则 |
+| sofa.ms.sg.downgrades.export | 导出降级规则 |
+| sofa.ms.sg.downgrades.import | 导入降级规则 |
+| sofa.ms.sg.downgrades.update | 更新降级规则 |
+
+### 服务治理——故障注入
+
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.faultinjectallapps.list | 查询已添加故障注入规则的应用 |
+| sofa.ms.sg.faultinjectallservices.list | 查询已添加故障注入规则的服务 |
+| sofa.ms.sg.faultinjectapps.list | 查询故障注入一级列表 |
+| sofa.ms.sg.faultinjectrule.add | 新增故障注入规则 |
+| sofa.ms.sg.faultinjectrule.delete | 删除故障注入规则 |
+| sofa.ms.sg.faultinjectrule.export | 导出故障注入 |
+| sofa.ms.sg.faultinjectrule.import | 导入故障注入 |
+| sofa.ms.sg.faultinjectrule.update | 更新故障注入规则 |
+| sofa.ms.sg.faultinjectrules.list | 分页查询故障注入二级列表 |
+| sofa.ms.sg.faultinjectrulestatus.update | 更新故障注入规则状态 |
+| sofa.ms.sg.faulttoleranceapps.list | 故障隔离App列表及其规则数量 |
+| sofa.ms.sg.faulttolerancerule.add | 故障隔离规则新增 |
+| sofa.ms.sg.faulttolerancerule.delete | 故障隔离规则删除 |
+| sofa.ms.sg.faulttolerancerule.enable | 故障隔离规则启用 |
+| sofa.ms.sg.faulttolerancerule.export | 导出故障隔离规则 |
+| sofa.ms.sg.faulttolerancerule.import | 导入故障隔离规则 |
+| sofa.ms.sg.faulttolerancerule.update | 故障隔离规则更新 |
+| sofa.ms.sg.faulttolerancerules.list | 故障隔离规则列表 |
+
+
+
+### 服务治理——服务鉴权
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.authruleactiveappnames.list | 查找出目前存活的应用名称列表 |
+| sofa.ms.sg.authruleactivedataids.list | 查找该应用下存活的服务列表 |
+| sofa.ms.sg.authruleappnames.list | 查找出所有包含鉴权规则组的应用名(一般用于服务鉴权列表搜索框查询) |
+| sofa.ms.sg.authruledataids.list | 查找出所有包含鉴权规则组的服务ID(一般用于鉴权规则列表搜索框查询) |
+| sofa.ms.sg.authrulegroupapps.list | 应用级服务鉴权列表搜索 |
+| sofa.ms.sg.authrulegroups.disable | 禁止一份服务鉴权黑、白名单 |
+| sofa.ms.sg.authrulegroups.enable | 启用服务鉴权黑、白名单。黑、白名单不能同时生效 |
+| sofa.ms.sg.authrulegroups.export | 导出鉴权规则数据 |
+| sofa.ms.sg.authrulegroups.import | 导入鉴权规则数据 |
+| sofa.ms.sg.authrulegroups.list | 根据服务接口查询服务鉴权规则 |
+| sofa.ms.sg.authrulegroups.retry | 服务鉴权规则下发失败,重新下发规则 |
+| sofa.ms.sg.authrulegroups.save | 创建服务鉴权规则 |
+| sofa.ms.sg.authrulegroupsbatch.add | 添加鉴权规则 (添加服务级鉴权规则可批量添加) |
+| sofa.ms.sg.authrulegroupsbatch.disable | 批量关闭鉴权规则 |
+| sofa.ms.sg.authrulegroupsbatch.enable | 批量开启鉴权规则 |
+| sofa.ms.sg.authrulegroupservices.list | 服务级服务鉴权列表搜索 |
+| sofa.ms.sg.authrules.add | 添加一条白、黑名单规则 |
+| sofa.ms.sg.authrules.delete | 删除鉴权规则 |
+| sofa.ms.sg.authrules.disable | 禁用黑、白名单中的一条规则 |
+| sofa.ms.sg.authrules.enable | 启用服务鉴权规则 |
+| sofa.ms.sg.authrules.reinit | 清空黑白名单下所有鉴权规则 |
+| sofa.ms.sg.authrules.update | 更新一条服务鉴权规则 |
+
+### 审计目录
+| 接口名称 | 描述 |
+| ---- | ---- |
+| sofa.ms.sg.auditlogs.list | 分页查询审计日志 |
\ No newline at end of file
diff --git a/test.log b/test.log
index 8ef1c419..3fc3021f 100644
--- a/test.log
+++ b/test.log
@@ -1,3 +1,67 @@
-Alipay.jsbkclcore:name=com.antgroup.creditloan.clcore.common.util.drm.CommonGrayControlDrm.feeSubsidyQuerySwicthDrm.controlRule,version=3.0@DRM
-
-Alipay.jsbkclcore:name=com.antgroup.creditloan.clcore.common.util.drm.CommonGrayControlDrm.feeSubsidyQuerySwicthDrm.controlRule,version=3.0@DRM
\ No newline at end of file
+MySQL [osp]> select * from t_instance_group where tenant_id = "CZKZJVCN";
++------+--------------+---------------+-----------------------+-----------+----------+---------------------+---------------------+
+| id | instance_id | instance_name | workspace_id | tenant_id | operator | gmt_create | gmt_modified |
++------+--------------+---------------+-----------------------+-----------+----------+---------------------+---------------------+
+| 4936 | BGAUH89M7MFH | default | acgatewayshpre | CZKZJVCN | NULL | 2022-06-14 20:52:09 | 2022-06-14 20:52:09 |
+| 4935 | V6LFHIUEDCLA | default | acgatewayshprod | CZKZJVCN | NULL | 2022-06-14 20:21:17 | 2022-06-14 20:21:17 |
+| 4675 | ALAZYISSVVQQ | default | aicontainerpre | CZKZJVCN | NULL | 2022-02-15 16:16:17 | 2022-02-15 16:16:17 |
+| 4705 | UWHIWDYOIAPU | default | aicontainerprod | CZKZJVCN | NULL | 2022-02-25 20:12:14 | 2022-02-25 20:12:14 |
+| 4773 | TR2M3AOT2HA5 | default | AIotPRE | CZKZJVCN | NULL | 2022-03-16 20:49:59 | 2022-03-16 20:49:59 |
+| 4836 | GMIKNNQTZJXD | default | AIotPROD | CZKZJVCN | NULL | 2022-03-30 20:15:36 | 2022-03-30 20:15:36 |
+| 4653 | FZTPEGUAEYOX | default | baascontainerpre | CZKZJVCN | NULL | 2022-01-27 10:14:57 | 2022-01-27 10:14:57 |
+| 4629 | DPPSMO0CDA8V | default | baascontainerprod | CZKZJVCN | NULL | 2022-01-17 16:29:35 | 2022-01-17 16:29:35 |
+| 282 | WDBAHQRYFC4U | default | BaasDev | CZKZJVCN | NULL | 2018-09-19 11:39:03 | 2018-09-19 11:39:03 |
+| 132 | DPPSMO0CDA8V | default | BaaSProduction | CZKZJVCN | NULL | 2018-04-25 18:46:39 | 2018-04-25 18:46:39 |
+| 1157 | HVKV1C2MB9LA | default | BAIProd | CZKZJVCN | NULL | 2019-11-27 16:34:05 | 2019-11-27 16:34:05 |
+| 1135 | YXMF6VJQZEEE | default | BAITest | CZKZJVCN | NULL | 2019-11-15 15:23:48 | 2019-11-15 15:23:48 |
+| 1297 | S4J83OOBBVY5 | default | bamboo | CZKZJVCN | NULL | 2020-02-05 11:32:39 | 2020-02-05 11:32:39 |
+| 4572 | K9OZTUJSIG29 | default | BaseService | CZKZJVCN | NULL | 2021-12-28 12:01:08 | 2021-12-28 12:01:08 |
+| 549 | UZLS24IJMMYU | default | BillProd | CZKZJVCN | NULL | 2019-01-23 17:11:13 | 2019-01-23 17:11:13 |
+| 864 | KZER0YOOPS76 | default | BillTest | CZKZJVCN | NULL | 2019-06-06 17:34:54 | 2019-06-06 17:34:54 |
+| 4172 | Q5HGURE3PPIW | default | carbonchain | CZKZJVCN | NULL | 2021-07-29 10:18:46 | 2021-07-29 10:18:46 |
+| 1001 | UIYU2EXZALCN | default | chainfin | CZKZJVCN | NULL | 2019-09-16 14:12:18 | 2019-09-16 14:12:18 |
+| 4909 | H3SHOYMMCR7U | default | commoncontainerpre | CZKZJVCN | NULL | 2022-05-18 15:30:00 | 2022-05-18 15:30:00 |
+| 4888 | TMOA3W40FD8A | default | commoncontainerprod | CZKZJVCN | NULL | 2022-05-10 20:09:15 | 2022-05-10 20:09:15 |
+| 4477 | BVABJWRJQVNO | default | customertechprod | CZKZJVCN | NULL | 2021-12-06 11:17:38 | 2021-12-06 11:17:38 |
+| 320 | CHSNHYPZOPWT | default | default | CZKZJVCN | NULL | 2018-09-29 11:30:34 | 2018-09-29 11:30:34 |
+| 554 | SVDWJXSAF27G | default | dev | CZKZJVCN | NULL | 2019-01-25 19:49:51 | 2019-01-25 19:49:51 |
+| 2920 | IJZ8UB2Z6ERB | default | EWTPProd | CZKZJVCN | NULL | 2020-09-03 10:02:04 | 2020-09-03 10:02:04 |
+| 501 | 0TFGMHCWK7DU | default | FinanceSystem | CZKZJVCN | NULL | 2019-01-04 14:23:16 | 2019-01-04 14:23:16 |
+| 4207 | 7MHKQPIQVBRT | default | govchain | CZKZJVCN | NULL | 2021-08-10 20:38:01 | 2021-08-10 20:38:01 |
+| 1492 | 9CHDLAFI2SHI | default | gvtprod | CZKZJVCN | NULL | 2020-04-02 18:02:31 | 2020-04-02 18:02:31 |
+| 4739 | MVEEOWVWRRYZ | default | horizontalbusinesspre | CZKZJVCN | NULL | 2022-03-08 16:37:03 | 2022-03-08 16:37:03 |
+| 1141 | W6472KEGAD1G | default | IoTFinanceIntTest | CZKZJVCN | NULL | 2019-11-19 15:46:10 | 2019-11-19 15:46:10 |
+| 988 | WAORB2A5FDUF | default | IoTFinanceProd | CZKZJVCN | NULL | 2019-09-05 11:23:33 | 2019-09-05 11:23:33 |
+| 960 | QVHRUKXBHAI3 | default | iotfinancetest | CZKZJVCN | NULL | 2019-08-12 17:20:00 | 2019-08-12 17:20:00 |
+| 1563 | NVL9APRD97BX | default | liangfanshanghai | CZKZJVCN | NULL | 2020-04-16 10:18:46 | 2020-04-16 10:18:46 |
+| 5014 | 8JL0ZHQWSPFL | default | MainsetCommunication | CZKZJVCN | NULL | 2022-08-17 10:41:47 | 2022-08-17 10:41:47 |
+| 1063 | RZV9KY3PZW8K | default | miniappprodworkspace | CZKZJVCN | NULL | 2019-09-26 22:18:49 | 2019-09-26 22:18:49 |
+| 751 | UNI0HI3CMNUM | default | miniapptestworkspace | CZKZJVCN | NULL | 2019-04-08 16:19:08 | 2019-04-08 16:19:08 |
+| 280 | 2NP8RKLV1LXK | default | miniProgramProd | CZKZJVCN | NULL | 2018-09-19 11:12:11 | 2018-09-19 11:12:11 |
+| 659 | NCVTGS0C3DPG | default | miniProgramTest | CZKZJVCN | NULL | 2019-03-12 20:42:38 | 2019-03-12 20:42:38 |
+| 4764 | KD46PEF7BIVK | default | morsecontainerpre | CZKZJVCN | NULL | 2022-03-14 16:21:20 | 2022-03-14 16:21:20 |
+| 4844 | WKURV3M5KPME | default | morsecontainerprod | CZKZJVCN | NULL | 2022-04-01 16:54:45 | 2022-04-01 16:54:45 |
+| 907 | TOHZIGDSAHMP | default | morseprod | CZKZJVCN | NULL | 2019-06-27 11:40:12 | 2019-06-27 11:40:12 |
+| 909 | XGJ4W5MUZYYT | default | MPC | CZKZJVCN | NULL | 2019-06-27 15:21:56 | 2019-06-27 15:21:56 |
+| 944 | KIQNASVQAHCS | default | mychain10dev | CZKZJVCN | NULL | 2019-07-31 15:10:56 | 2019-07-31 15:10:56 |
+| 1245 | AXA7KOKPTQXS | default | mychaingldev | CZKZJVCN | NULL | 2020-01-14 09:53:49 | 2020-01-14 09:53:49 |
+| 875 | LDKQGMOXC52B | default | mychaintest | CZKZJVCN | NULL | 2019-06-13 19:22:11 | 2019-06-13 19:22:11 |
+| 846 | PDYQG1XD12FN | default | mykmsprod | CZKZJVCN | NULL | 2019-05-31 10:23:36 | 2019-05-31 10:23:36 |
+| 905 | 5ALUQZ5BOWGN | default | myshellprod | CZKZJVCN | NULL | 2019-06-26 15:24:26 | 2019-06-26 15:24:26 |
+| 841 | FJDNJYCYDAEM | default | myshelltest | CZKZJVCN | NULL | 2019-05-29 13:58:47 | 2019-05-29 13:58:47 |
+| 977 | FZTPEGUAEYOX | default | pre | CZKZJVCN | NULL | 2019-08-27 15:42:19 | 2019-08-27 15:42:19 |
+| 3921 | QTLRCLZWQ9QA | default | prodmetrics | CZKZJVCN | NULL | 2021-05-13 20:34:38 | 2021-05-13 20:34:38 |
+| 717 | 4QOFZUYOFA52 | default | QABaasNotary | CZKZJVCN | NULL | 2019-03-28 11:04:37 | 2019-03-28 11:04:37 |
+| 1384 | S3EFEM0YDTOD | default | SitDigitalLogistics | CZKZJVCN | NULL | 2020-03-02 20:00:00 | 2020-03-02 20:00:00 |
+| 692 | IHPNO7LQ3AEO | default | SoftSeaturtleProd | CZKZJVCN | NULL | 2019-03-21 22:40:10 | 2019-03-21 22:40:10 |
+| 879 | FQVQPQ8YEHK4 | default | SoftSeaturtleTest | CZKZJVCN | NULL | 2019-06-14 23:13:54 | 2019-06-14 23:13:54 |
+| 513 | J1WZ89RSAYIB | default | sslab1online | CZKZJVCN | NULL | 2019-01-08 16:39:06 | 2019-01-08 16:39:06 |
+| 4889 | 0ZODETAPWWVB | default | taascontainerpre | CZKZJVCN | NULL | 2022-05-11 11:23:53 | 2022-05-11 11:23:53 |
+| 4943 | WRHL3ERZXO7I | default | taascontainerprod | CZKZJVCN | NULL | 2022-06-20 11:29:27 | 2022-06-20 11:29:27 |
+| 4209 | ARX5RQMCTPTE | default | tianyandev | CZKZJVCN | NULL | 2021-08-11 19:38:58 | 2021-08-11 19:38:58 |
+| 4807 | ZYS49OKF7FO3 | default | TrusplePre | CZKZJVCN | NULL | 2022-03-25 11:44:58 | 2022-03-25 11:44:58 |
+| 4677 | XKNG9BHAL2VZ | default | trustedindustrypre | CZKZJVCN | NULL | 2022-02-16 10:00:14 | 2022-02-16 10:00:14 |
+| 4683 | ZJANF6TCHYXE | default | trustedindustryprod | CZKZJVCN | NULL | 2022-02-17 15:21:25 | 2022-02-17 15:21:25 |
+| 4791 | MBS1OXWDVBHV | default | TWCPre | CZKZJVCN | NULL | 2022-03-23 10:10:13 | 2022-03-23 10:10:13 |
+| 4849 | IHPNO7LQ3AEO | default | TWCProd | CZKZJVCN | NULL | 2022-04-08 16:08:31 | 2022-04-08 16:08:31 |
++------+--------------+---------------+-----------------------+-----------+----------+---------------------+---------------------+
\ No newline at end of file