您好,登錄后才能下訂單哦!
本篇內容介紹了“如何解決啟用Spring-Cloud-OpenFeign配置可刷新項目無法啟動的問題”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
最近在項目中想實現 OpenFeign 的配置可以動態刷新(主要是 Feign 的 Options 配置),例如:
feign: client: config: default: # 鏈接超時 connectTimeout: 500 # 讀取超時 readTimeout: 8000
我們可能會觀察到調用某個 FeignClient 的超時時間不合理,需要臨時修改下,我們不想因為這種事情重啟進程或者刷新整個 ApplicationContext,所以將這部分配置放入 spring-cloud-config 中并使用動態刷新的機制進行刷新。官方提供了這個配置方法,參考:官方文檔 - Spring @RefreshScope Support
即在項目中增加配置:
feign.client.refresh-enabled: true
但是在我們的項目中,增加了這個配置后,啟動失敗,報找不到相關 Bean 的錯誤:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'feign.Request.Options-testService1Client' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:863)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1160)
at org.springframework.cloud.openfeign.FeignContext.getInstance(FeignContext.java:57)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getOptionsByName(FeignClientFactoryBean.java:363)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.configureUsingConfiguration(FeignClientFactoryBean.java:195)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.configureFeign(FeignClientFactoryBean.java:158)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.feign(FeignClientFactoryBean.java:132)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:382)
at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:371)
at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:235)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.obtainFromSupplier(AbstractAutowireCapableBeanFactory.java:1231)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1173)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
... 74 more
通過這個 Bean 名稱,其實可以看出來這個 Bean 是我們開始提到要動態刷新的 Feign.Options,里面有連接超時、讀取超時等配置。名字后面的部分是我們創建的 FeignClient 上面 @FeignClient
注解里面的 contextId。
在創建 FeignClient 的時候,需要加載這個 Feign.Options Bean,每個 FeignClient 都有自己的 ApplicationContext,這個 Feign.Options Bean 就是屬于每個 FeignClient 單獨的 ApplicationContext 的。這個是通過 Spring Cloud 的 NamedContextFactory 實現的。對于 NamedContextFactory 的深入分析,可以參考我的這篇文章:
對于 OpenFeign 的配置開啟動態刷新,其實就是對于 FeignClient 就是要刷新每個 FeignClient 的 Feign.Options 這個 Bean。那么如何實現呢?我們先來看 spring-cloud 的動態刷新 Bean 的實現方式。首先我們要搞清楚,什么是 Scope。
從字面意思上面理解,Scope 即 Bean 的作用域。從實現上面理解,Scope 即我們在獲取 Bean 的時候,這個 Bean 是如何獲取的。
Spring 框架中自帶兩個耳熟能詳的 Scope,即 singleton 和 prototype。singleton 即每次從 BeanFactory 獲取一個 Bean 的時候(getBean
),對于同一個 Bean 每次返回的都是同一個對象,即單例模式。prototype 即每次從 BeanFactory 獲取一個 Bean 的時候,對于同一個 Bean 每次都新創建一個對象返回,即工廠模式。
同時,我們還可以根據自己需要去擴展 Scope,定義獲取 Bean 的方式。舉一個簡單的例子,我們自定義一個 TestScope。自定義的 Scope 需要先定義一個實現 org.springframework.beans.factory.config.Scope
接口的類,來定義在這個 Scope 下的 Bean 的獲取相關的操作。
public interface Scope { //獲取這個 bean,在 BeanFactory.getBean 的時候會被調用 Object get(String name, ObjectFactory<?> objectFactory); //在調用 BeanFactory.destroyScopedBean 的時候,會調用這個方法 @Nullable Object remove(String name); //注冊 destroy 的 callback //這個是可選實現,提供給外部注冊銷毀 bean 的回調。可以在 remove 的時候,執行這里傳入的 callback。 void registerDestructionCallback(String name, Runnable callback); //如果一個 bean 不在 BeanFactory 中,而是根據上下文創建的,例如每個 http 請求創建一個獨立的 bean,這樣從 BeanFactory 中就拿不到了,就會從這里拿 //這個也是可選實現 Object resolveContextualObject(String key); //可選實現,類似于 session id 用戶區分不同上下文的 String getConversationId(); }
我們來實現一個簡單的 Scope:
public static class TestScope implements Scope { @Override public Object get(String name, ObjectFactory<?> objectFactory) { return objectFactory.getObject(); } @Override public Object remove(String name) { return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } }
這個 Scope 只是實現了 get 方法。直接通過傳入的 objectFactory 創建一個新的 bean。這種 Scope 下每次調用 BeanFactory.getFactory 都會返回一個新的 Bean,自動裝載到不同 Bean 的這種 Scope 下的 Bean 也是不同的實例。編寫測試:
@Configuration public static class Config { @Bean //自定義 Scope 的名字是 testScope @org.springframework.context.annotation.Scope(value = "testScope") public A a() { return new A(); } //自動裝載進來 @Autowired private A a; } public static class A { public void test() { System.out.println(this); } }
public static void main(String[] args) { //創建一個 ApplicationContext AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); //注冊我們自定義的 Scope annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope()); //注冊我們需要的配置 Bean annotationConfigApplicationContext.register(Config.class); //調用 refresh 初始化 ApplicationContext annotationConfigApplicationContext.refresh(); //獲取 Config 這個 Bean Config config = annotationConfigApplicationContext.getBean(Config.class); //調用自動裝載的 Bean config.a.test(); //從 BeanFactory 調用 getBean 獲取 A annotationConfigApplicationContext.getBean(A.class).test(); annotationConfigApplicationContext.getBean(A.class).test(); }
執行代碼,叢輸出上可以看出,這三個 A 都是不同的對象:
com.hopegaming.spring.cloud.parent.ScopeTest$A@5241cf67 com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124 com.hopegaming.spring.cloud.parent.ScopeTest$A@77192705
我們再來修改我們的 Bean,讓它成為一個 Disposable Bean:
public static class A implements DisposableBean { public void test() { System.out.println(this); } @Override public void destroy() throws Exception { System.out.println(this + " is destroyed"); } }
再修改下我們的自定義 Scope:
public static class TestScope implements Scope { private Runnable callback; @Override public Object get(String name, ObjectFactory<?> objectFactory) { return objectFactory.getObject(); } @Override public Object remove(String name) { System.out.println(name + " is removed"); this.callback.run(); System.out.println("callback finished"); return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { System.out.println("registerDestructionCallback is called"); this.callback = callback; } @Override public Object resolveContextualObject(String key) { System.out.println("resolveContextualObject is called"); return null; } @Override public String getConversationId() { System.out.println("getConversationId is called"); return null; } }
在測試代碼中,增加調用 destroyScopedBean 銷毀 bean:
annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a");
運行代碼,可以看到對應的輸出:
registerDestructionCallback is called
a is removed
com.hopegaming.spring.cloud.parent.ScopeTest$A@716a7124 is destroyed
callback finished
對于 DisposableBean 或者其他有相關生命周期類型的 Bean,BeanFactory 會通過 registerDestructionCallback 將生命周期需要的操作回調傳進來。使用 BeanFactory.destroyScopedBean
銷毀 Bean 的時候,會調用 Scope 的 remove 方法,我們可以在操作完成時,調用 callback 回調完成 Bean 生命周期。
接下來我們嘗試實現一種單例的 Scope,方式非常簡單,主要基于 ConcurrentHashMap:
public static class TestScope implements Scope { private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Runnable> callback = new ConcurrentHashMap<>(); @Override public Object get(String name, ObjectFactory<?> objectFactory) { System.out.println("get is called"); return map.compute(name, (k, v) -> { if (v == null) { v = objectFactory.getObject(); } return v; }); } @Override public Object remove(String name) { this.map.remove(name); System.out.println(name + " is removed"); this.callback.get(name).run(); System.out.println("callback finished"); return null; } @Override public void registerDestructionCallback(String name, Runnable callback) { System.out.println("registerDestructionCallback is called"); this.callback.put(name, callback); } @Override public Object resolveContextualObject(String key) { return null; } @Override public String getConversationId() { return null; } }
我們使用兩個 ConcurrentHashMap 緩存這個 Scope 下的 Bean,以及對應的 Destroy Callback。在這種實現下,就類似于單例模式的實現了。再使用下面的測試程序測試下:
public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope()); annotationConfigApplicationContext.register(Config.class); annotationConfigApplicationContext.refresh(); Config config = annotationConfigApplicationContext.getBean(Config.class); config.a.test(); annotationConfigApplicationContext.getBean(A.class).test(); //Config 類中注冊 Bean 的方法名稱為 a,所以 Bean 名稱也為 a annotationConfigApplicationContext.getBeanFactory().destroyScopedBean("a"); config.a.test(); annotationConfigApplicationContext.getBean(A.class).test(); }
我們在銷毀 Bean 之前,使用自動裝載和 BeanFactory.getBean 分別去請求獲取 A 這個 Bean 并調用 test 方法。然后銷毀這個 Bean。在這之后,再去使用自動裝載的和 BeanFactory.getBean 分別去請求獲取 A 這個 Bean 并調用 test 方法。可以從輸出中看出, BeanFactory.getBean 請求的是新的 Bean 了,但是自動裝載的里面還是已銷毀的那個 bean。那么如何實現讓自動裝載的也是新的 Bean,也就是重新注入呢?
這就涉及到了 Scope 注解上面的另一個配置,即指定代理模式:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { @AliasFor("scopeName") String value() default ""; @AliasFor("value") String scopeName() default ""; ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; }
其中第三個配置,ScopedProxyMode 是配置獲取這個 Bean 的時候,獲取的是原始 Bean 對象還是代理的 Bean 對象(這也同時影響了自動裝載):
public enum ScopedProxyMode { //走默認配置,沒有其他外圍配置則是 NO DEFAULT, //使用原始對象作為 Bean NO, //使用 JDK 的動態代理 INTERFACES, //使用 CGLIB 動態代理 TARGET_CLASS }
我們來測試下指定 Scope Bean 的實際對象為代理的效果,我們修改下上面的測試代碼,使用 CGLIB 動態代理。修改代碼:
@Configuration public static class Config { @Bean @org.springframework.context.annotation.Scope(value = "testScope" //指定代理模式為基于 CGLIB , proxyMode = ScopedProxyMode.TARGET_CLASS ) public A a() { return new A(); } @Autowired private A a; }
編寫測試主方法:
public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.getBeanFactory().registerScope("testScope", new TestScope()); annotationConfigApplicationContext.register(Config.class); annotationConfigApplicationContext.refresh(); Config config = annotationConfigApplicationContext.getBean(Config.class); config.a.test(); annotationConfigApplicationContext.getBean(A.class).test(); //查看 Bean 實例的類型 System.out.println(config.a.getClass()); System.out.println(annotationConfigApplicationContext.getBean(A.class).getClass()); //這時候我們需要注意,代理 Bean 的名稱有所變化,需要通過 ScopedProxyUtils 獲取 annotationConfigApplicationContext.getBeanFactory().destroyScopedBean(ScopedProxyUtils.getTargetBeanName("a")); config.a.test(); annotationConfigApplicationContext.getBean(A.class).test(); }
執行程序,輸出為:
get is called
registerDestructionCallback is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a
get is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a
class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee
class com.hopegaming.spring.cloud.parent.ScopeTest$A$$EnhancerBySpringCGLIB$$2fa625ee
scopedTarget.a is removed
com.hopegaming.spring.cloud.parent.ScopeTest$A@3dd69f5a is destroyed
callback finished
get is called
registerDestructionCallback is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a
get is called
com.hopegaming.spring.cloud.parent.ScopeTest$A@3aa3193a
從輸出中可以看出:
每次對于自動裝載的 Bean 的調用,都會調用自定義 Scope 的 get 方法重新獲取 Bean
每次通過 BeanFactory 獲取 Bean,也會調用自定義 Scope 的 get 方法重新獲取 Bean
獲取的 Bean 實例,是一個 CGLIB 代理對象
在 Bean 被銷毀后,無論是通過 BeanFactory 獲取 Bean 還是自動裝載的 Bean,都是新的 Bean
那么 Scope 是如何實現這些的呢?我們接下來簡單分析下源碼
如果一個 Bean 沒有聲明任何 Scope,那么他的 Scope 就會被賦值成 singleton,也就是默認的 Bean 都是單例的。這個對應 BeanFactory 注冊 Bean 之前需要生成 Bean 定義,在 Bean 定義的時候會賦上這個默認值,對應源碼:
AbstractBeanFactory
protected RootBeanDefinition getMergedBeanDefinition( String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException { //省略我們不關心的源碼 if (!StringUtils.hasLength(mbd.getScope())) { mbd.setScope(SCOPE_SINGLETON); } //省略我們不關心的源碼 }
在聲明一個 Bean 具有特殊 Scope 之前,我們需要定義這個自定義 Scope 并把它注冊到 BeanFactory 中。這個 Scope 名稱必須全局唯一,因為之后區分不同 Scope 就是通過這個名字進行區分的。注冊 Scope 對應源碼:
AbstractBeanFactory
@Override public void registerScope(String scopeName, Scope scope) { Assert.notNull(scopeName, "Scope identifier must not be null"); Assert.notNull(scope, "Scope must not be null"); //不能為 singleton 和 prototype 這兩個預設的 scope if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) { throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'"); } //放入 scopes 這個 map 中,key 為名稱,value 為自定義 Scope Scope previous = this.scopes.put(scopeName, scope); //可以看出,后面放入的會替換前面的,這個我們要盡量避免出現。 if (previous != null && previous != scope) { if (logger.isDebugEnabled()) { logger.debug("Replacing scope '" + scopeName + "' from [" + previous + "] to [" + scope + "]"); } } else { if (logger.isTraceEnabled()) { logger.trace("Registering scope '" + scopeName + "' with implementation [" + scope + "]"); } } }
當聲明一個 Bean 具有特殊的 Scope 之后,獲取這個 Bean 的時候,就會有特殊的邏輯,參考通過 BeanFactory 獲取 Bean 的核心源碼代碼:
AbstractBeanFactory
@SuppressWarnings("unchecked") protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { //省略我們不關心的源碼 // 創建 Bean 實例 if (mbd.isSingleton()) { //創建或者返回單例實例 } else if (mbd.isPrototype()) { //每次創建一個新實例 } else { //走到這里代表這個 Bean 屬于自定義 Scope String scopeName = mbd.getScope(); //必須有 Scope 名稱 if (!StringUtils.hasLength(scopeName)) { throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'"); } //通過 Scope 名稱獲取對應的 Scope,自定義 Scope 需要手動注冊進來 Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); } try { //調用自定義 Scope 的 get 方法獲取 Bean Object scopedInstance = scope.get(beanName, () -> { //同時將創建 Bean 需要的生命周期的回調傳入,用于創建 Bean beforePrototypeCreation(beanName); try { return createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } }); beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); } catch (IllegalStateException ex) { throw new ScopeNotActiveException(beanName, scopeName, ex); } } //省略我們不關心的源碼 }
同時,如果我們定義 Scope Bean 的代理方式為 CGLIB,那么在獲取 Bean 定義的時候,就會根據原始 Bean 定義創建 Scope 代理的 Bean 定義,對應源碼:
ScopedProxyUtils
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { //原始目標 Bean 名稱 String originalBeanName = definition.getBeanName(); //獲取原始目標 Bean 定義 BeanDefinition targetDefinition = definition.getBeanDefinition(); //獲取代理 Bean 名稱 String targetBeanName = getTargetBeanName(originalBeanName); //創建類型為 ScopedProxyFactoryBean 的 Bean RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); //根據原始目標 Bean 定義的屬性,配置代理 Bean 定義的相關屬性,省略這部分源碼 //根據原始目標 Bean 的自動裝載屬性,復制到代理 Bean 定義 proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); } //設置原始 Bean 定義為不自動裝載并且不為 Primary //這樣通過 BeanFactory 獲取 Bean 以及自動裝載的都是代理 Bean 而不是原始目標 Bean targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); //使用新名稱注冊 Bean registry.registerBeanDefinition(targetBeanName, targetDefinition); return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); } private static final String TARGET_NAME_PREFIX = "scopedTarget."; //這個就是獲取代理 Bean 名稱的工具方法,我們上面 Destroy Bean 的時候也有用到 public static String getTargetBeanName(String originalBeanName) { return TARGET_NAME_PREFIX + originalBeanName; }
這個代理 Bean 有啥作用呢?其實主要用處就是每次調用 Bean 的任何方法的時候,都會通過 BeanFactory 獲取這個 Bean 進行調用。參考源碼:
代理類 ScopedProxyFactoryBean
public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean { private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource(); //這個就是通過 SimpleBeanTargetSource 生成的實際代理,對于 Bean 的方法調用都會通過這個 proxy 進行調用 private Object proxy; }
SimpleBeanTargetSource
就是實際的代理源,他的實現非常簡單,核心方法就是使用 Bean 名稱通過 BeanFactory 獲取這個 Bean:
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { @Override public Object getTarget() throws Exception { return getBeanFactory().getBean(getTargetBeanName()); } }
通過 BeanFactory 獲取這個 Bean,通過上面源碼分析可以知道,對于自定義 Scope 的 Bean 就會調用自定義 Scope 的 get 方法。
然后是 Bean 的銷毀,在 BeanFactory 創建這個 Bean 對象的時候,就會調用自定義 Scope 的 registerDestructionCallback 將 Bean 銷毀的回調傳入:
AbstractBeanFactory
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) { AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null); if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) { if (mbd.isSingleton()) { //對于 singleton registerDisposableBean(beanName, new DisposableBeanAdapter( bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc)); } else { //對于自定義 Scope Scope scope = this.scopes.get(mbd.getScope()); if (scope == null) { throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'"); } //調用 registerDestructionCallback scope.registerDestructionCallback(beanName, new DisposableBeanAdapter( bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc)); } } }
在我們想銷毀 Scope Bean 的時候,需要調用的是 BeanFactory 的 destroyScopedBean 方法,這個方法會調用自定義 Scope 的 remove:
AbstractBeanFactory
@Override public void destroyScopedBean(String beanName) { RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); //僅針對自定義 Scope Bean 使用 if (mbd.isSingleton() || mbd.isPrototype()) { throw new IllegalArgumentException( "Bean name '" + beanName + "' does not correspond to an object in a mutable scope"); } String scopeName = mbd.getScope(); Scope scope = this.scopes.get(scopeName); if (scope == null) { throw new IllegalStateException("No Scope SPI registered for scope name '" + scopeName + "'"); } //調用自定義 Scope 的 remove 方法 Object bean = scope.remove(beanName); if (bean != null) { destroyBean(beanName, bean, mbd); } }
“如何解決啟用Spring-Cloud-OpenFeign配置可刷新項目無法啟動的問題”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。