bean生命周期源码(三)
一、Bean的销毁逻辑
1. 简介
前面我们已经分析完了Spring创建Bean的整个过程的源码,在创建bean的核心方法中doCreateBean
这一个核心方法中,在方法的最后面有这么一段代码:
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
这个registerDisposableBeanIfNecessary
方法就是销毁bean的逻辑,下面我们就详细分析一下销毁Bean的过程。
2. Bean销毁逻辑的注册
在使用bean的销毁逻辑时,我们可以像下面代码一样:
@Component
public UserService implements DisposedBean{
@Autowired
private OrderService orderservice;
@Override
public void destroy() throws Exception{
//业务逻辑
}
}
@Component
public UserService{
@Autowired
private OrderService orderservice;
@preDestroy
public void destroy() throws Exception{
//业务逻辑
}
}
上面代码的逻辑就是让我们的UserService实现了DisposedBean
接口,并实现了destroy方法,或者直接在指定方法上加上@preDestroy
注解,那么这个方法会在UserService被销毁时调用。
注意所谓的销毁并不是JVM将我们的bean对象销毁,我们知道JVM在垃圾回收的过程中,会回收销毁掉垃圾对象,但调用destroy方法在对象销毁时被调用这一个逻辑JVM是不能帮我们实现的,JVM在只会在销毁对象是调用默认的finalize方法,那么这个销毁方法会在什么时候被调用?其实是在我们的Spring容器销毁时被调用。
下面我们给了两种关闭bean容器的方法:
- 手动关闭
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
applicationContext.close();
}
直接调用close()方法实现容器的关闭。
- 向JVM注册关闭钩子
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
userService.test();
applicationContext.registerShutdownHook();
}
applicationContext.registerShutdownHook() 是用来注册一个JVM关闭钩子(shutdown hook)。在Java中,当JVM即将关闭时,会执行一些预定义的动作。通过注册关闭钩子,你可以指定在JVM关闭之前执行一些特定的操作。具体地说,registerShutdownHook()会注册一个钩子,以确保在JVM关闭时,Spring容器执行销毁和清理工作。这包括关闭所有的单例bean、释放资源等。这样可以确保在应用程序关闭时,Spring能够做一些必要的清理,以避免资源泄漏或其他问题。
下面是这两种方式的源码:
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose();
// If we registered a JVM shutdown hook, we don't need it anymore now:
// We've already explicitly closed the context.
if (this.shutdownHook != null) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
}
catch (IllegalStateException ex) {
// ignore - VM is already shutting down
}
}
}
}
@Override
public void registerShutdownHook() {
if (this.shutdownHook == null) {
// No shutdown hook registered yet.
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
@Override
public void run() {
synchronized (startupShutdownMonitor) {
doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
可以发现这两种方式底层其实都是调用的doClose()
方法。
继续回到registerDisposableBeanIfNecessary
方法
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
该方法接收3个参数,分别是beanname,初始化后的bean以及beandefintion,我们进去看看它的源码:
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()) {
// Register a DisposableBean implementation that performs all destruction
// work for the given bean: DestructionAwareBeanPostProcessors,
// DisposableBean interface, custom destroy method.
registerDisposableBean(beanName, new DisposableBeanAdapter(
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
else {
// A bean with a custom scope...
Scope scope = this.scopes.get(mbd.getScope());
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
}
scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
}
}
首先if (!mbd.isPrototype() && requiresDestruction(bean, mbd))
会判断你的bean是否是单例的,如果是单例的它会执行requiresDestruction(bean, mbd)
来判断你这个bean到底需不需要执行销毁前的逻辑。
protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) {
return (bean.getClass() != NullBean.class && (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) ||
(hasDestructionAwareBeanPostProcessors() && DisposableBeanAdapter.hasApplicableProcessors(
bean, getBeanPostProcessorCache().destructionAware))));
}
上面方法通过DisposableBeanAdapter.hasDestroyMethod(bean, mbd)
来判断你这个方法有没有所谓的销毁逻辑。下面我们看看它是怎样去判断的。
public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
return true;
}
return inferDestroyMethodIfNecessary(bean, beanDefinition) != null;
}
如果你的Bean实现了DisposableBean和AutoClosedable接口,就返回true,否则执行方法inferDestroyMethodIfNecessary
。
@Nullable
private static String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
//看看你的beanDefinition有没有销毁方法
String destroyMethodName = beanDefinition.resolvedDestroyMethodName;
if (destroyMethodName == null) {
//获得你beanDefinition中配置销毁方法的名称
destroyMethodName = beanDefinition.getDestroyMethodName(); //
//如果你在beanDefinition中的设置的方法(通常是通过实现BeanPostProcess接口中设置的)名为INFER_METHOD,则执行小面的逻辑
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
//就会获取你定义的close方法来作为销毁方法
destroyMethodName = bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex) {
try {
//或者将你定义的ShutDown方法作为销毁方法
destroyMethodName = bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
}
catch (NoSuchMethodException ex2) {
// no candidate destroy method found
}
}
}
}
beanDefinition.resolvedDestroyMethodName = (destroyMethodName != null ? destroyMethodName : "");
}
return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
}
上面代码中的if (destroyMethodName == null) {
这个代码块中的逻辑可能比较难以理解,下面我用案例来分析一下,如下面代码:
@Component
public class UserService {
@Autowired
private OrderService orderService;
public void test(){
System.out.println(orderService);
}
public void close(){
System.out.println("close方法被调用了");
}
}
上面我们给我们的UserService加上了一个close()方法,然后我们添加一个类加上MergedBeanDefinitionPostProcessor
和InstantiationAwareBeanPostProcessor
。
@Component
public class jackBeanPostProcessor implements InstantiationAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
if(beanName.equals("userService")){
beanDefinition.setDestroyMethodName("(inferred)");
}
}
}
上面代码中我们给beanDefinition设置了一个销毁方法,名字为inferred
,也就是前面源码中说的AbstractBeanDefinition.INFER_METHOD
这个常量。然后执行下面代码,就会自定调用我们名为close()
的方法。
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) applicationContext.getBean("userService");
applicationContext.close();
}
}
这也就上面那段源码的作用,下面我们接着回到inferDestroyMethodIfNecessary
方法,到这里该方法就执行完毕了。然后继续回到requiresDestruction
,里面还有一句代码这样的hasDestructionAwareBeanPostProcessors
,该方法用来判断容器中有没有bean实现了DestructionAwareBeanPostProcessors
接口。
public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
default boolean requiresDestruction(Object bean) {
return true;
}
}
这个接口同样继承了BeanPostProcessor接口,然后它只有两个方法,
postProcessBeforeDestruction
这个方法就是执行bean销毁前的逻辑,requiresDestruction
这个方法就是判断你这个bean需不需要执行销毁方法。
继续回到requiresDestruction
方法,如果hasDestructionAwareBeanPostProcessors
返回为true,那么就会执行后面的代码DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessorCache().destructionAware))
。
public static boolean hasApplicableProcessors(Object bean, List<DestructionAwareBeanPostProcessor> postProcessors) {
if (!CollectionUtils.isEmpty(postProcessors)) {
for (DestructionAwareBeanPostProcessor processor : postProcessors) {
if (processor.requiresDestruction(bean)) {
return true;
}
}
}
return false;
}
首先它会便利我们的destructionAware
集合中所有的实现 DestructionAwareBeanPostProcessor
接口的bean,然后调用processor.requiresDestruction
,判断当前bean需不需要执行销毁方法。
processor.requiresDestruction底层具体实现类,会找到方法前有@preConstruct和@preDestroy注解到方法,然后存入相关的容器。
@Override
public boolean requiresDestruction(Object bean) {
return findLifecycleMetadata(bean.getClass()).hasDestroyMethods();
}
private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) {
if (this.lifecycleMetadataCache == null) {
// Happens after deserialization, during destruction...
return buildLifecycleMetadata(clazz);
}
// Quick check on the concurrent map first, with minimal locking.
LifecycleMetadata metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
synchronized (this.lifecycleMetadataCache) {
metadata = this.lifecycleMetadataCache.get(clazz);
if (metadata == null) {
metadata = buildLifecycleMetadata(clazz);
this.lifecycleMetadataCache.put(clazz, metadata);
}
return metadata;
}
}
return metadata;
}
private LifecycleMetadata buildLifecycleMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(this.initAnnotationType, this.destroyAnnotationType))) {
return this.emptyLifecycleMetadata;
}
List<LifecycleElement> initMethods = new ArrayList<>();
List<LifecycleElement> destroyMethods = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<LifecycleElement> currInitMethods = new ArrayList<>();
final List<LifecycleElement> currDestroyMethods = new ArrayList<>();
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
if (this.initAnnotationType != null && method.isAnnotationPresent(this.initAnnotationType)) {
LifecycleElement element = new LifecycleElement(method);
currInitMethods.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
if (this.destroyAnnotationType != null && method.isAnnotationPresent(this.destroyAnnotationType)) {
currDestroyMethods.add(new LifecycleElement(method));
if (logger.isTraceEnabled()) {
logger.trace("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
});
// 父类的在前面
initMethods.addAll(0, currInitMethods);
destroyMethods.addAll(currDestroyMethods);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return (initMethods.isEmpty() && destroyMethods.isEmpty() ? this.emptyLifecycleMetadata :
new LifecycleMetadata(clazz, initMethods, destroyMethods));
}
执行完上面的逻辑,继续回到requiresDestruction
方法,这里我们可以总结一下,Spring底层是如何判断我们是否有销毁的逻辑的:
- 首先判断我们的bean是否实现类
AutoCloseable
和DisposedBean
接口,如果实现类了就调用相应的close方法 - 如果没有实现上面的两个接口,就会判断我们是否修改了BeanDefinition,设置了销毁方法,如果有我们调用自己写的close方法或shutdown方法即可
- 最后一种逻辑就是,我们时候使用了
@PreDestroy
注解
如果我们bean具有上面三种中的任何一种逻辑,就说明我们的bean可以执行相应的销毁逻辑,继续回到registerDisposableBeanIfNecessary
方法,上面我们已经把if (!mbd.isPrototype() && requiresDestruction(bean, mbd))
逻辑执行完了,下面可以进入语句块中了。
protected void registerDisposableBeanIfNecessary(String beanName, Object bean, RootBeanDefinition mbd) {
AccessControlContext acc = (System.getSecurityManager() != null ? getAccessControlContext() : null);
if (!mbd.isPrototype() && requiresDestruction(bean, mbd)) {
//如果我们的bean是单例
if (mbd.isSingleton()) {
registerDisposableBean(beanName, new DisposableBeanAdapter(
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
else {
// A bean with a custom scope...
Scope scope = this.scopes.get(mbd.getScope());
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + mbd.getScope() + "'");
}
scope.registerDestructionCallback(beanName, new DisposableBeanAdapter(
bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));
}
}
}
然后继续执行registerDisposableBean(beanName, new DisposableBeanAdapter(bean, beanName, mbd, getBeanPostProcessorCache().destructionAware, acc));}
,这个方法的参数为beanname和创建了一个DisposableBeanAdapter对象。
DisposableBeanAdapter 是 Spring Framework 中的一个内部类,用于帮助处理 bean 的销毁(disposal)操作。在 Spring 容器关闭时,容器会负责销毁一些 bean 实例以释放资源。这个方法是在 Spring 容器创建 bean 实例的过程中被调用的。
public void registerDisposableBean(String beanName, DisposableBean bean) {
synchronized (this.disposableBeans) {
this.disposableBeans.put(beanName, bean);
}
}
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
registerDisposableBean
只是将我们的相应的销毁逻辑(DisposableBeanAdapter)封装到了一个集合中(有销毁逻辑的bean的集合)。至此我们的销毁方法的注册的全部流程就已经结束了,下面就可以开始分析在容器关闭的时候是怎么执行我们的这些销毁逻辑的。
3. Bean的销毁过程
前面已经分析了bean销毁逻辑的注册过程,而且前面说到容器关闭有两种方法,分别是注册JVM钩子和调用容器的close方法显示关闭,其实他们的底层都是调用了doclose方法。
protected void doClose() {
// Check whether an actual close attempt is necessary...
if (this.active.get() && this.closed.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Closing " + this);
}
if (!NativeDetector.inNativeImage()) {
LiveBeansView.unregisterApplicationContext(this);
}
try {
// Publish shutdown event.
publishEvent(new ContextClosedEvent(this));
}
catch (Throwable ex) {
logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
}
// Stop all Lifecycle beans, to avoid delays during individual destruction.
if (this.lifecycleProcessor != null) {
try {
this.lifecycleProcessor.onClose();
}
catch (Throwable ex) {
logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
}
}
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// Close the state of this context itself.
closeBeanFactory();
// Let subclasses do some final clean-up if they wish...
onClose();
// Reset local application listeners to pre-refresh state.
if (this.earlyApplicationListeners != null) {
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
// Switch to inactive.
this.active.set(false);
}
}
上面代码关于bean销毁所调用的核心方法是destroyBeans
protected void destroyBeans() {
getBeanFactory().destroySingletons();
}
然后destroyBeans
底层调用的是destroySingletons
方法
@Override
public void destroySingletons() {
super.destroySingletons();
// 清空manualSingletonNames集合
updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
clearByTypeCache();
}
继续进入super.destroySingletons
方法
public void destroySingletons() {
if (logger.isTraceEnabled()) {
logger.trace("Destroying singletons in " + this);
}
synchronized (this.singletonObjects) {
this.singletonsCurrentlyInDestruction = true;
}
String[] disposableBeanNames;
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
this.containedBeanMap.clear();
this.dependentBeanMap.clear();
this.dependenciesForBeanMap.clear();
clearSingletonCache();
}
有关执行前面的销毁逻辑的代码就是下面两句
//定义一个String数组
String[] disposableBeanNames;
// private final Map<String, Object> disposableBeans = new LinkedHashMap<>();disposableBeans就是我们前面讲注册销毁逻辑时最后封装的那个集合
synchronized (this.disposableBeans) {
//然后获得结合的keySet(也就是我们的beanname)
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
//遍历beanname,执行销毁逻辑
destroySingleton(disposableBeanNames[i]);
}
上面代码就是拿到了我们前面注册销毁逻辑的那个集合,然后遍历执行destroySingleton
方法
public void destroySingleton(String beanName) {
// Remove a registered singleton of the given name, if any.
// 先从单例池中移除掉
removeSingleton(beanName);
// Destroy the corresponding DisposableBean instance.
DisposableBean disposableBean;
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
destroyBean(beanName, disposableBean);
}
然后上面的方法首先将我们的bean从单例池中移除掉,然后将我们的bean同样从this.disposableBeans移除掉,最后执行destroyBean(beanName, disposableBean)
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// dependentBeanMap表示某bean被哪些bean依赖了
// 所以现在要销毁某个bean时,如果这个Bean还被其他Bean依赖了,那么也得销毁其他Bean
// Trigger destruction of dependent beans first...
Set<String> dependencies;
synchronized (this.dependentBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
dependencies = this.dependentBeanMap.remove(beanName);
}
if (dependencies != null) {
if (logger.isTraceEnabled()) {
logger.trace("Retrieved dependent beans for bean '" + beanName + "': " + dependencies);
}
for (String dependentBeanName : dependencies) {
//当前依赖我们这个bean的其他bean也要执行销毁逻辑(如果有销毁逻辑),这里是递归实现
destroySingleton(dependentBeanName);
}
}
//开始真正执行我们的销毁逻辑
if (bean != null) {
try {
bean.destroy();
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex);
}
}
}
// Trigger destruction of contained beans...
Set<String> containedBeans;
synchronized (this.containedBeanMap) {
// Within full synchronization in order to guarantee a disconnected Set
containedBeans = this.containedBeanMap.remove(beanName);
}
if (containedBeans != null) {
for (String containedBeanName : containedBeans) {
destroySingleton(containedBeanName);
}
}
// Remove destroyed bean from other beans' dependencies.
synchronized (this.dependentBeanMap) {
for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Set<String>> entry = it.next();
Set<String> dependenciesToClean = entry.getValue();
dependenciesToClean.remove(beanName);
if (dependenciesToClean.isEmpty()) {
it.remove();
}
}
}
// Remove destroyed bean's prepared dependency information.
this.dependenciesForBeanMap.remove(beanName);
}
上面真正执行销毁逻辑的代码就是下面这段代码:
//开始真正执行我们的销毁逻辑
if (bean != null) {
try {
bean.destroy();
}
catch (Throwable ex) {
if (logger.isWarnEnabled()) {
logger.warn("Destruction of bean with name '" + beanName + "' threw an exception", ex);
}
}
}
destory方法是DisposableBeanAdapter类的方法:
public DisposableBeanAdapter(
Object bean, List<DestructionAwareBeanPostProcessor> postProcessors, AccessControlContext acc) {
Assert.notNull(bean, "Disposable bean must not be null");
this.bean = bean;
this.beanName = bean.getClass().getName();
this.invokeDisposableBean = (this.bean instanceof DisposableBean);
this.nonPublicAccessAllowed = true;
this.acc = acc;
this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
每个DisposableBeanAdapter对象,封装了对应的bean,以及容器中所有的DestructionAwareBeanPostProcessor(这就是销毁方法的逻辑)
public void destroy() {
//如果this.beanPostProcessors集合不为空,调用processor.postProcessBeforeDestruction方法
if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
processor.postProcessBeforeDestruction(this.bean, this.beanName);
}
}
if (this.invokeDisposableBean) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
}
try {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
((DisposableBean) this.bean).destroy();
return null;
}, this.acc);
}
else {
((DisposableBean) this.bean).destroy();
}
}
catch (Throwable ex) {
String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
if (logger.isDebugEnabled()) {
logger.warn(msg, ex);
}
else {
logger.warn(msg + ": " + ex);
}
}
}
if (this.destroyMethod != null) {
invokeCustomDestroyMethod(this.destroyMethod);
}
else if (this.destroyMethodName != null) {
Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
if (methodToInvoke != null) {
invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
}
}
}
processor.postProcessBeforeDestruction(this.bean, this.beanName);
这句代码底层就会根据我们前面判断某个bean有没有销毁逻辑的三种方法去执行我们bean的销毁逻辑的,至此某个bean底层执行我们制定的销毁逻辑的底层原理我们就分析完了。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!