热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

WMRouter路由源码解析

WMRouter路由源码解析-theme:fancy本次阅读WMRouter源码,主要为了弄清楚一下两个问题:路由的注册框架如何将我们配置的路由扫描注册并集中管理

theme: fancy

本次阅读 WMRouter 源码,主要为了弄清楚一下两个问题:

  • 路由的注册

    框架如何将我们配置的路由扫描注册并集中管理

  • 路由过程

    通过 Router.startUri 如何实现一次路由

0、WMRouter接入
  • 基础库
  • 使用注解的库
  • gradle插件
  • 混淆规则
  • 1.2.1版本注意替换包名
  • Application 初始化
// 创建RootHandler
DefaultRootUriHandler rootHandler = new DefaultRootUriHandler(context);
// 初始化
Router.init(rootHandler);

路由过程

1、路由处理器 & 路由注册

1.1、UriHandler

对某一类的Uri进行处理。提供 shouldHandle 抽象方法供子类重写来判断自己能否处理当前 UriRequest;提供
handleInternal 抽象方法供子类实现具体的处理逻辑。UriHandle 自身提供了 handle 方法,规定了所有 Handler 的基本逻辑:首先调用 shouldHandle 方法判断当前 Handler 是否需要处理;接着判断当前 Handler 是否配置了拦截器,如果有拦截器先交给拦截器处理,否则直接调用 handleInternal 进行处理。

public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    if (shouldHandle(request)) {
        Debugger.i("%s: handle request %s", this, request);
        if (mInterceptor != null && !request.isSkipInterceptors()) {
            mInterceptor.intercept(request, new UriCallback() {
                @Override
                public void onNext() {
                    handleInternal(request, callback);
                }
                @Override
                public void onComplete(int result) {
                    callback.onComplete(result);
                }
            });
        } else {
            handleInternal(request, callback);
        }
    } else {
        Debugger.i("%s: ignore request %s", this, request);
        callback.onNext();
    }
}

1.2、DefaultRootUriHandler

在 Appliction 中注册的默认的根路由处理器,从上面提供的图中也可以看出每次路由的时候最先被 DefaultRootUriHandler处理。

1.3、PageAnnotationHandler

继承 PathHandler(继承 UriHandler)

  • shouldHandle

    • 只有uri格式为 wm_router://page/xxxx 时才进行处理
  • handle 方法,复写的 handle 方法,调用 ensureInit 进行初始化,后续逻辑仍然是 UriHandler中的handle的逻辑

    • ensureInit 通过 LazyInitHelper 实现
    • ServiceLoaderInit (Gradle插件生成)扫描所有的 ServiceInit_XXX 并调用 init 方法将 PageAnnotationInit_XXX & UriAnnotationInit_XXX 通过 ServiceLoader 注册
    • ensureInit 则是获取到上面注册的 XXXAnnotation_XXX 实现类,并调用 init 方法实现对应页面 Handler 的注册
  • handlerInteral

public static final String SCHEME = "wm_router";
public static final String HOST = "page";
public static final String SCHEME_HOST = RouterUtils.schemeHost(SCHEME, HOST);
@Override
protected boolean shouldHandle(@NonNull UriRequest request) {
    return SCHEME_HOST.matches(request.schemeHost());
}

@Override
public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {
    mInitHelper.ensureInit();
    super.handle(request, callback);
}

private final LazyInitHelper mInitHelper = new LazyInitHelper("PageAnnotationHandler") {
    @Override
    protected void doInit() {
        initAnnotationConfig();
    }
};

// 获取 IPageAnnotationInit 的所有实现类 (Apt生成的)
// 生成的 PageAnnotationInit_XXX 类型
protected void initAnnotationConfig() {
    RouterComponents.loadAnnotation(this, IPageAnnotationInit.class);
}

public static  void loadAnnotation(T handler, Class> initClass) {
    sAnnotationLoader.load(handler, initClass);
}
// sAnnotationLoader 对应的类
public class DefaultAnnotationLoader implements AnnotationLoader {

    public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();

    @Override
    public  void load(T handler,
            Class> initClass) {
        // ServiceLoader:获取对应接口的所有实现类
        List> services = Router.getAllServices(initClass);
        // 调用对应的 Init 方法
        for (AnnotationInit service : services) {
            service.init(handler);
        }
    }
}


public class PageAnnotationInit_bfd2eeb94778305f7d7586fccf7b2630 implements IPageAnnotationInit {
  public void init(PageAnnotationHandler handler) {
    handler.register("/fragment/kroom/feed", new FragmentTransactionHandler("com.netease.cloudmusic.singroom.feed.ui.fragments.SingRoomFeedFragment"));
  }
}

PathHandler

根据path分发URI给子节点,支持注册的子节点包括ActivityClassName, ActivityClass, UriHandler (官方注释)

  • scheme + host:对应一个 PathHandler
  • PathHandler 内部的 mMap 记录了对应的 path 和 Handler (init的时候调用生成的字节码注册进来的)

UriAnnotationHandler

StartUriHandler

1.4、UriAnnotationHandler

实现和 PageAnnotationHandler 类似,支持不同的 scheme,内部构造了一个 PathHandler 来完成注册。和 PageAnnotationHandler 不同的是,PageAnnotationHandler 本身就是一个 PathHandler 类型。

public static UriHandler parse(Object target, int priority, Map params,
                               Class... interceptors) {
    UriHandler handler = toHandler(target, params);
    if (handler != null) {
        if (UriSourceTools.FROM_INTERNAL == priority) {
            handler.addInterceptor(SourceInternalInterceptor.class);
        } else if (UriSourceTools.FROM_EXTERNAL == priority) {
            handler.addInterceptor(SourceExternalInterceptor.class);
        } else if (UriSourceTools.FROM_WEBVIEW == priority) {
            handler.addInterceptor(SourceWebViewInterceptor.class);
        }
        handler.addInterceptors(interceptors);
    }
    return handler;
}

private static UriHandler toHandler(Object target, Map params) {
    if (target instanceof UriHandler) {
        return (UriHandler) target;
    } else if (target instanceof String) {
        // 一般代码中生成的注册方法都是对应这个case
        return new ActivityClassNameHandler((String) target, params);
    } else if (target instanceof Class) {
        Class clazz = (Class) target;
        if (isValidActivityClass(clazz)) {
            //noinspection unchecked
            return new ActivityHandler((Class) target, params);
        } else if (isValidUriHandlerClass(clazz)) {
            try {
                if (isSingleInstance(clazz)) {
                    return (UriHandler) SingletonPool.get(clazz, null);
                } else {
                    return (UriHandler) clazz.newInstance();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    return null;
}

1.5、 Fragment 和 Activity 对应的 Handler

  • FragmentTransactionHandler

由PageAnnotationHandler在加载由注解处理器生成的 PageAnnotationInit_xxxxxx 时注册的

  • ActivityClassNameHandler

由UriAnnotationHandler在加载由注解处理器生成的UriAnnotationInit_xxxxxx 时注册的

1.6、Gradle 插件生成初始化注册代码

WMRouterTransform

public void transform(TransformInvocation invocation) {
    WMRouterLogger.info(TRANSFORM + "start...");
    long ms = System.currentTimeMillis();
    //非增量编译,先清空输出目录
    if (!invocation.isIncremental()) {
        try {
            invocation.getOutputProvider().deleteAll();
        } catch (IOException e) {
            WMRouterLogger.fatal(e);
        }
    }
    Set initClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
    Set deleteClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
    BaseTransform baseTransform = new BaseTransform(invocation, new TransformCallBack() {
        @Nullable
        @Override
        public byte[] process(@NotNull String className, @Nullable byte[] classBytes) {
            String checkClassName = ClassUtils.path2Classname(className);
            if (checkClassName.startsWith(Const.GEN_PKG_SERVICE)) {
                 WMRouterLogger.info(TRANSFORM + "className = %s, checkClassName = %s", className, checkClassName);
                 initClasses.add(className);
            }
            return null;
        }
    }, false);
    baseTransform.setDeleteCallBack(new DeleteCallBack() {
        @Override
        public void delete(String className, byte[] bytes) {
            String checkClassName = ClassUtils.path2Classname(className);
            if (checkClassName.startsWith(Const.GEN_PKG_SERVICE)) {
                deleteClasses.add(className);
            }
        }
    });
    baseTransform.openSimpleScan();
    baseTransform.startTransform();
    File dest = invocation.getOutputProvider().getContentLocation(
            "WMRouter", TransformManager.CONTENT_CLASS,
            ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY);
    generateServiceInitClass(dest.getAbsolutePath(), initClasses, deleteClasses);
    WMRouterLogger.info(TRANSFORM + "cost %s ms", System.currentTimeMillis() - ms);
}



private void generateServiceInitClass(String directory, Set classes, Set deleteClass) {
    if (classes.isEmpty()) {
        WMRouterLogger.info(GENERATE_INIT + "skipped, no service found");
        return;
    }
    File dest = new File(directory, INIT_SERVICE_PATH + SdkConstants.DOT_CLASS);
    if (!dest.exists()) {
        try {
            WMRouterLogger.info(GENERATE_INIT + "start...");
            long ms = System.currentTimeMillis();

            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
            ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, writer) {
            };
            // 1、类名
            String className = Const.SERVICE_LOADER_INIT.replace('.', '/');
            cv.visit(50, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null);

            // 2、init 方法声明 (公开 & 静态)
            MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                    Const.INIT_METHOD, "()V", null, null);
            // 3、init 方法体
            // 遍历传递的 classes 参数,调用其 init 方法
            mv.visitCode();
            for (String clazz : classes) {
                String input = clazz.replace(".class", "");
                input = input.replace(".", "/");
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, input,
                        "init",
                        "()V",
                        false);
            }
            mv.visitMaxs(0, 0);
            mv.visitInsn(Opcodes.RETURN);
            mv.visitEnd();
            cv.visitEnd();
            // 4、将class字节码写到对应位置
            dest.getParentFile().mkdirs();
            new FileOutputStream(dest).write(writer.toByteArray());

            WMRouterLogger.info(GENERATE_INIT + "cost %s ms", System.currentTimeMillis() - ms);

        } catch (IOException e) {
            WMRouterLogger.fatal(e);
        }
    } else {
        try {
            modifyClass(dest, classes, deleteClass);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • BaseTransform 将所有以 com.sankuai.waimai.router.generated.service 开头的类添加到一个 Set 集合中,之后将其作为参数传递给 generateServiceInitClass 方法;
  • generateServiceInitClass 方法会生成 ServiceLoaderInit 类,并调用 APT 生成的一些列 init 方法进行路由的注册。
3、路由过程

1、Router.startUri ==> RootUriHandler.startUri (实际是DefaultRootUriHandler,在Router.init时传递

DefaultRootUriHandler创建Request, 调用handle) ==> UriHandle.handle

DefaultRootUriHandler 继承 RootUriHandler 继承 ChainedHandler 继承 UriHandler

handle 方法首先判断是否应该处理 shouldHandle (ChainedHandler中,只要 mHandlers列表 不为空就应该处理,mHandlers 在初始化的时候被赋值)

public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) {
    super(context);
    mPageAnnotatiOnHandler= createPageAnnotationHandler();
    mUriAnnotatiOnHandler= createUriAnnotationHandler(defaultScheme, defaultHost);

    // 按优先级排序,数字越大越先执行

    // 处理RouterPage注解定义的内部页面跳转,如果注解没定义,直接结束分发
    addChildHandler(mPageAnnotationHandler, 300);
    // 处理RouterUri注解定义的URI跳转,如果注解没定义,继续分发到后面的Handler
    addChildHandler(mUriAnnotationHandler, 200);
    // 添加其他用户自定义Handler...
    // 都没有处理,则尝试使用默认的StartUriHandler直接启动Uri
    addChildHandler(new StartUriHandler(), -100);
}

public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    if (shouldHandle(request)) {
        Debugger.i("%s: handle request %s", this, request);
        if (mInterceptor != null && !request.isSkipInterceptors()) {
            mInterceptor.intercept(request, new UriCallback() {
                @Override
                public void onNext() {
                    // 1、先调用拦截器的方法,拦截器调用完成之后再调用 handleInternal
                    handleInternal(request, callback);
                }

                @Override
                public void onComplete(int result) {
                    callback.onComplete(result);
                }
            });
        } else {
            // 没有拦截器直接 handleInternal
            handleInternal(request, callback);
        }
    } else {
        Debugger.i("%s: ignore request %s", this, request);
        callback.onNext();
    }
}

因为后面的具体 Handler 基本上都是继承自 UriHandler,因此这一段 handler 的代码相当于时一个模板方法模式,对一个UriRequest的处理基本上都分解成如上的三步:

第一步:调用 shouldHandle 判断是否应该处理;

第二步:判断当前 Handler 是否存在拦截器,如果有拦截器先将 UriRequest 交给拦截器处理;

第三步:将请求交给 handlerInternal 进行处理。

2、到目前为止我们的 Handler 实际的类型还是 DefaultRootUriHandler,其 handlerInternal 在 ChainedHandler 中实现。

@Override
protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
    next(mHandlers.iterator(), request, callback);
}

private void next(@NonNull final Iterator iterator, @NonNull final UriRequest request,
                  @NonNull final UriCallback callback) {
    if (iterator.hasNext()) {
        UriHandler t = iterator.next();
        t.handle(request, new UriCallback() {
            @Override
            public void onNext() {
                next(iterator, request, callback);
            }

            @Override
            public void onComplete(int resultCode) {
                callback.onComplete(resultCode);
            }
        });
    } else {
        callback.onNext();
    }
}

基本就是按照 UriHandler 的顺序依次调用其 handler 方法, UriHandler 在 DefaultRootUriHandler 构造时初始化,并被存储在一个优先级队列中。按照优先级依次是:PageAnnotationHandler、UriAnnotationHandler、StartUriHandler。

3、PageAnnotationHandler

public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {
    mInitHelper.ensureInit();
    super.handle(request, callback);
}

protected void handleInternal(final UriRequest request, final UriCallback callback) {
    // 1、查找能处理的 Handler
    UriHandler h = getChild(request);
    if (h != null) {
        // 2、调用查找到的Handler的handle方法
        h.handle(request, new UriCallback() {
            @Override
            public void onNext() {
                handleByDefault(request, callback);
            }

            @Override
            public void onComplete(int resultCode) {
                callback.onComplete(resultCode);
            }
        });
    } else {
        // 3、调用默认 Handler 的 handle 方法
        handleByDefault(request, callback);
    }
}
// 初始化时注册的路由处理器就是放在 mMap 中
private UriHandler getChild(@NonNull UriRequest request) {
    String path = request.getUri().getPath();
    UriHandler handler;
    if (TextUtils.isEmpty(path)) {
        return mMap.get("/");
    }
    if (!TextUtils.isEmpty(mPathPrefix)) {
        path = path.substring(mPathPrefix.length());
    }
    path = Util.appendSlash(path);
    handler = mMap.get(path);
    if (handler == null) { //modify by zyh 当常规的路由表匹配不上时,从restfulMap中查找匹配的路由信息
        handler = getRestfulChild(request);
    }
    if (handler == null) {
        handler = mMap.get("/");
    }
    return handler;
}
  • 先调用 mInitHelper.ensureInit() 判断是否已经初始化,没有初始化则执行初始化操作,初始化其实就是上面的扫描注解处理器生成的样板代码,注册最终路由处理器的过程。

  • 之后在调用基类UriHandler的handle方法进行处理,按照上面的思路,最终会调用到 handleInternal 中,PageAnnotationHandler继承自 PathHandler,PathHandler 中实现了该方法。

  • 在 mMap 中查找到能够处理的 UriHandler 调用其 handler 方法,否则调用默认 handler 的 handle 方法(PageAnnotationHandler)没有默认 Handler

4、FragmentTransactionHandler

FragmentTransactionHandler 就是由 PageAnnotationHandler 注册的一种路由处理器类型,用来路由到一个 Fragment页面。同理我们主要看其 handleInternal 方法。

protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
    if (TextUtils.isEmpty(mClassName)) {
        Debugger.fatal("FragmentTransactionHandler.handleInternal()应返回的带有ClassName");
        callback.onComplete(UriResult.CODE_BAD_REQUEST);
        return;
    }

    StartFragmentAction action = request.getField(StartFragmentAction.class, StartFragmentAction.START_FRAGMENT_ACTION);
    if (action == null) {
        Debugger.fatal("FragmentTransactionHandler.handleInternal()应返回的带有StartFragmentAction");
        callback.onComplete(UriResult.CODE_BAD_REQUEST);
        return;
    }

    if (!request.hasField(FRAGMENT_CLASS_NAME)) {
        //判断一下,便于被替换
        request.putField(FRAGMENT_CLASS_NAME, mClassName);
    }

    // Extra
    Bundle extra = request.getField(Bundle.class, FIELD_INTENT_EXTRA);
    boolean success = action.startFragment(request, extra);
    // 完成
    callback.onComplete(success ? UriResult.CODE_SUCCESS : UriResult.CODE_BAD_REQUEST);
}

public boolean startFragment(@NonNull UriRequest request, @NonNull Bundle bundle) throws ActivityNotFoundException, SecurityException {
    String fragmentClassName = request.getStringField(FragmentTransactionHandler.FRAGMENT_CLASS_NAME);
    try {
        Fragment fragment = Fragment.instantiate(request.getContext(), fragmentClassName, bundle);
        if (fragment == null) {
            return false;
        }

        FragmentTransaction transaction = mFragmentManager.beginTransaction();
        switch (mStartType) {
            case TYPE_ADD:
                transaction.add(mContainerViewId, fragment, mTag);
                break;
            case TYPE_REPLACE:
                transaction.replace(mContainerViewId, fragment, mTag);
                break;
        }
        if (mAllowingStateLoss) {
            transaction.commitAllowingStateLoss();
        } else {
            transaction.commit();
        }
        return true;
    } catch (Exception e) {
        Debugger.e("FragmentTransactionUriRequest",e);
        return false;
    }
}
  • 创建了 StartFragmentAction 实例,调用起 startFragment 方法。
  • StartFragmentAction 是一个接口,我们看其中一种实现。

  • 调用 Fragment.instantiate 实例化一个 Fragment 对象,再通过 FragmentTransaction 调用 add 或者 replace 方法将其显示出来。

5、UriAnnotationHandler

如果前一步的 PageAnnotationHandler 没有处理,下一步就由UriAnnotationHandler来处理。其处理逻辑和 PageAnnotationHandler 相似,也是先初始化,然后再调用 UriHandler 的 handle 方法。和 PageAnnotationHandler 不同的是 UriAnnotationHandler 直接继承 UriHandler,而前者直接继承 PathHandler。

public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {
    mInitHelper.ensureInit();
    super.handle(request, callback);
}

protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
    PathHandler pathHandler = getChild(request);
    if (pathHandler != null) {
        pathHandler.handle(request, callback);
    } else {
        // 没找到的继续分发
        callback.onNext();
    }
}

private final Map mMap = new HashMap<>();
private PathHandler getChild(@NonNull UriRequest request) {
    return mMap.get(request.schemeHost());
}
  • 由上面分析可知最终也是进入 handleInternal 方法中,首先从 mMap 中通过 scheme + host 拿到对应的 PathHandler,再调用其 handle 方法 (也是 UriHandler 的 handle)

  • 最终也是调用到 PathHandler 的 handleInternal 方法

  • 在 UriAnnotationHandler 每个 scheme + host 对应一个 PathHandler,而一个 PathHandler 中又可以注册多个路由。

  • 在 PageAnnotationHandler 只有一个 PathHandler (就是其自身,其scheme和host固定)

  • 后面的处理和 PageAnnotationHandler 中分析的类似了:在 PathHandler 查找到具体的路由处理器,调用对应的 handle 方法

6、ActivityClassNameHandler

该 Handler 就是由 UriAnnotationHandler 解析注册的。继承 AbsActivityHandler,最终交给其 handleInternal方法处理。

protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {
    // 创建Intent
    Intent intent = createIntent(request);
    if (intent == null || intent.getComponent() == null) {
        Debugger.fatal("AbsActivityHandler.createIntent()应返回的带有ClassName的显式跳转Intent");
        callback.onComplete(UriResult.CODE_ERROR);
        return;
    }
    intent.setData(request.getUri());
    UriSourceTools.setIntentSource(intent, request);
    // 启动Activity
    request.putFieldIfAbsent(ActivityLauncher.FIELD_LIMIT_PACKAGE, limitPackage());
    int resultCode = RouterComponents.startActivity(request, intent);
    // 回调方法
    onActivityStartComplete(request, resultCode);
    // 完成
    callback.onComplete(resultCode);
}
4、其他

WMRouter除了页面路由的功能外还提供了ServiceLoader的功能,此外还有@Autowired注解可以自动注入路由传递的参数。


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 解决.net项目中未注册“microsoft.ACE.oledb.12.0”提供程序的方法
    在开发.net项目中,通过microsoft.ACE.oledb读取excel文件信息时,报错“未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序”。本文提供了解决这个问题的方法,包括错误描述和代码示例。通过注册提供程序和修改连接字符串,可以成功读取excel文件信息。 ... [详细]
  • 本文介绍了MVP架构模式及其在国庆技术博客中的应用。MVP架构模式是一种演变自MVC架构的新模式,其中View和Model之间的通信通过Presenter进行。相比MVC架构,MVP架构将交互逻辑放在Presenter内部,而View直接从Model中读取数据而不是通过Controller。本文还探讨了MVP架构在国庆技术博客中的具体应用。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Spring学习(4):Spring管理对象之间的关联关系
    本文是关于Spring学习的第四篇文章,讲述了Spring框架中管理对象之间的关联关系。文章介绍了MessageService类和MessagePrinter类的实现,并解释了它们之间的关联关系。通过学习本文,读者可以了解Spring框架中对象之间的关联关系的概念和实现方式。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
author-avatar
哇哈时候_206
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有