一、HandlerMapping

作用是根据当前请求的找到对应的 Handler,并将 Handler(执行程序)与一堆 HandlerInterceptor(拦截器)封装到 HandlerExecutionChain 对象中。在 HandlerMapping 接口的内部只有一个方法,如下:

  • HandlerExecutionChain getHandler(HttpServletRequest request);

HandlerMapping 是由 DispatcherServlet 调用,DispatcherServlet 会从容器中取出所有 HandlerMapping 实例并遍历,让 HandlerMapping 实例根据自己实现类的方式去尝试查找 Handler,而 HandlerMapping 具体有哪些实现类下面就会详细分析。

  1. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  2. // 这些 HandlerMapping 在容器初始化时创建,在 initHandlerMappings 时放入集合中
  3. for (HandlerMapping hm : this.handlerMappings) {
  4. HandlerExecutionChain handler = hm.getHandler(request);
  5. if (handler != null) {
  6. return handler;
  7. }
  8. }
  9. return null;
  10. }

另外上面说到的 Handler 有可能是一个 HandlerMethod(封装了 Controller 中的方法)对象,也有可能是一个 Controller 对象、 HttpRequestHandler 对象或 Servlet 对象,而这个 Handler 具体是什么对象,也是与所使用的 HandlerMapping 实现类有关。如下图所示,可以看到 HandlerMapping 实现类有两个分支,分别继承自 AbstractHandlerMethodMapping(得到 HandlerMethod)和 AbstractUrlHandlerMapping(得到 HttpRequestHandler、Controller 或 Servlet),它们又统一继承于 AbstractHandlerMapping。

先来看一下 AbstractHandlerMapping,它实现了 HandlerMapping 接口中的 getHandler() 方法,源码如下所示

  1. @Override
  2. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  3. // 根据请求获取执行程序,具体的获取方式由子类决定,getHandlerInternal() 是抽象方法
  4. Object handler = getHandlerInternal(request);
  5. if (handler == null) {
  6. handler = getDefaultHandler();
  7. }
  8. if (handler == null) {
  9. return null;
  10. }
  11. // Bean name or resolved handler?
  12. if (handler instanceof String) {
  13. String handlerName = (String) handler;
  14. handler = getApplicationContext().getBean(handlerName);
  15. }
  16. // 将 Handler 与一堆拦截器包装到 HandlerExecutionChain 对象中
  17. return getHandlerExecutionChain(handler, request);
  18. }

可以看到在这个方法中又调用了 getHandlerInternal() 方法获取到了 Handler 对象,而 Handler 对象具体内容是由它的子类去定义的。下面就来一看下 AbstractHandlerMapping 的两个分支子类

1 AbstractUrlHandlerMapping

AbstractUrlHandlerMapping 这个分支获取的 Handler 的类型实际就是一个 Controller 类,所以一个 Controller 只能对应一个请求(或者像 Struts2 那样定位到方法,使同一个业务的方法放在同一个类里),源码如下所示

  1. protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
  2. // 根据当前请求获取“查找路径”
  3. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  4. // 根据路径获取 Handler(即Controller),先尝试直接匹配,再尝试模式匹配
  5. Object handler = lookupHandler(lookupPath, request);
  6. if (handler == null) {
  7. // We need to care for the default handler directly, since we need to
  8. // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
  9. Object rawHandler = null;
  10. if ("/".equals(lookupPath)) {
  11. rawHandler = getRootHandler();
  12. }
  13. if (rawHandler == null) {
  14. rawHandler = getDefaultHandler();
  15. }
  16. if (rawHandler != null) {
  17. // Bean name or resolved handler?
  18. if (rawHandler instanceof String) {
  19. String handlerName = (String) rawHandler;
  20. rawHandler = getApplicationContext().getBean(handlerName);
  21. }
  22. validateHandler(rawHandler, request);
  23. handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
  24. }
  25. }
  26. return handler;
  27. }

1.1 AbstractUrlHandlerMapping 实现类及使用

1) ControllerClassNameHandlerMapping:根据类名访问 Controller。

  1. <!-- 注册 HandlerMapping -->
  2. <bean class="org.springframework.web.servlet.handler.ControllerClassNameHandlerMapping" />
  3. <!-- 注册 Handler -->
  4. <bean class="com.controller.TestController" />

2) ControllerBeanNameHandlerMapping:根据 Bean 名访问 Controller,与 BeanNameUrlHandlerMapping 类似,但是bean名称不用遵循URL公约。

  1. <!-- 注册 HandlerMapping -->
  2. <bean class="org.springframework.web.servlet.handler.ControllerBeanNameHandlerMapping" />
  3. <!-- 注册 Handler -->
  4. <bean id="test" class="com.controller.TestController" />

3) BeanNameUrlHandlerMapping:利用 BeanName 来作为 URL 使用。

  1. <!-- 注册 HandlerMapping -->
  2. <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
  3. <!-- 注册 Handler -->
  4. <bean id="/test.do" class="com.controller.TestController" />

4) SimpleUrlHandlerMapping:可以将 URL 与处理器的定义分离,还可以对 URL 进行统一的映射管理。

  1. <!-- 注册 HandlerMapping -->
  2. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  3. <property name="mappings">
  4. <props>
  5. <prop key="/test.do">testController</prop>
  6. <prop key="/hello.do">testController</prop>
  7. </props>
  8. </property>
  9. </bean>
  10. <!-- 注册 Handler -->
  11. <bean id="testController" class="com.controller.TestController" />

1.2 Controller 类

使用 AbstractUrlHandlerMapping 的实现类时,需要让控制层的类实现 Controller 接口(一般继承 AbstractController 即可),另外还有一些已经实现了的 Controller 类,如下图所示。但是不论是自己实现 Controller 接口还是使用系统已经实现的类,都只能处理一个请求(除了 MultiActionController 可以通过参数的方式让一个类可以处理多个请求)。

另外下面所有的 Controller 均采用 SimpleUrlHandlerMapping 方式的。

1) UrlFilenameViewController:用于跳转界面,控制器根据请求的URL直接解析出视图名,省去了自己实现 Ccntroller 跳转页面。

  1. <bean id="indexController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />

2) ParameterizableViewController:同样用于界面跳转,控制器根据配置的参数来跳转界面,使用方式如下

  1. <bean id="indexController" class="org.springframework.web.servlet.mvc.ParameterizableViewController">
  2. <property name="viewName" value="/index.jsp" />
  3. </bean>

3) ServletForwardingController:将请求转发到 Servlet,使用方式如下

  1. <bean id="indexController" class="org.springframework.web.servlet.mvc.ServletForwardingController">
  2. <property name="servletName" value="indexServlet" />
  3. </bean>

另外还要在 web.xml 中配置要转发到的 Servlet

  1. <servlet>
  2. <servlet-name>indexServlet</servlet-name>
  3. <servlet-class>com.servlet.ServletForwarding</servlet-class>
  4. </servlet>

4) ServletWrappingController:将某个 Servlet 包装为 Controller,所有到 ServletWrappingController 的请求实际上是由它内部所包装的这个 Servlet 实例来处理的,这样可以将这个 Servlet 隐藏起来

5) MultiActionController:一个 Controller 可以写多个方法,分别对应不同的请求,使同一业务的方法可以放在一起了。在使用时让自己的 Controller 类继承 MultiActionController 类,使用方式如下

  1. public class IndexController extends MultiActionController {
  2. public ModelAndView add(HttpServletRequest request,HttpServletResponse response) {
  3. ModelAndView mv = new ModelAndView();
  4. mv.addObject("message","add");
  5. mv.setViewName("add");
  6. return mv;
  7. }
  8. public ModelAndView delete(HttpServletRequest request,HttpServletResponse response) {
  9. ModelAndView mv = new ModelAndView();
  10. mv.addObject("message","delete");
  11. mv.setViewName("delete");
  12. return mv;
  13. }
  14. }

配置自己的 Controller 时要配置一个方法名解析器(默认是 InternalPathMethodNameResolver )

  1. <bean id="indexController" class="com.controller.IndexController">
  2. <property name="methodNameResolver">
  3. <!-- InternalPathMethodNameResolver 根据请求路径解析执行方法名
  4. ParameterMethodNameResolver 根据参数解析执行方法名
  5. PropertiesMethodNameResolver 根据 key/value 列表解析执行方法名 -->
  6. <bean class="org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver">
  7. <!-- 指定参数名为action -->
  8. <property name="paramName" value="action" />
  9. </bean>
  10. </property>
  11. </bean>

当我们访问 http://localhost:8080/***/indexAction.do?action=add 时,进入 add() 方法;

当我们访问 http://localhost:8080/***/indexAction.do?action=delete 时,进入 delete() 方法。

2 AbstractHandlerMethodMapping

AbstractHandlerMethodMapping 这个分支获取的 Handler 的类型是 HandlerMethod,即这个 Handler 是一个方法,它保存了方法的信息(如Method),这样一个 Controller 就可以处理多个请求了,源码如下所示

  1. @Override
  2. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  3. // 根据当前请求获取“查找路径”
  4. String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
  5. // 获取当前请求最佳匹配的处理方法(即Controller类的方法中)
  6. HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
  7. return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  8. }

上述代码中 lookupHandlerMethod() 方法主要工作是在 Map<T, HandlerMethod> handlerMethods 中找到 HandlerMethod,这里的 T 是 HandlerMappingInfo,它封装了 @RequestMapping 注解中的信息。那 HandlerMethod 是怎么创建的(即怎么把 Controller 的方法变成了它),继续看一下源码找到 initHandlerMethods() 方法,这个方法是在这个类创建后调用的,如下所示是它的源码

  1. protected void initHandlerMethods() {
  2. // 从容器中获取所有 Bean 的名称,detectHandlerMethodsInAncestorContexts 默认false,不从父容器中查找
  3. //即默认只查找 SpringMVC 的 IOC 容器,不查找它的父容器 Spring 的 IOC 容器
  4. String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
  5. BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
  6. getApplicationContext().getBeanNamesForType(Object.class));
  7. for (String beanName : beanNames) {
  8. // 这里的 isHandler()方法由子类实现,判断是否拥有 @Controller 注解或 @RequestMapping 注解
  9. if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) && isHandler(getApplicationContext().getType(beanName))){
  10. // 利用反射得到 Bean 中的 Method 并包装成 HandlerMethod,然后放入 Map 中
  11. detectHandlerMethods(beanName);
  12. }
  13. }
  14. handlerMethodsInitialized(getHandlerMethods());
  15. }

看完上述代码后,可以知道是在 detectHandlerMethods() 方法中将 Bean 的方法转换为 HandlerMethod 对象,具体实现如下

  1. protected void detectHandlerMethods(final Object handler) {
  2. // 获取这个 Bean 的 Class 对象
  3. Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
  4. // 避免重复调用 getMappingForMethod(),getMappingForMethod() 将重新构建 RequestMappingInfo 实例
  5. final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
  6. // 获取被代理前的原始类型
  7. final Class<?> userType = ClassUtils.getUserClass(handlerType);
  8. // 获取 Method
  9. Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
  10. @Override
  11. public boolean matches(Method method) {
  12. // 根据 Method 和它的 @RequestMapping 注解,创建 RequestMappingInfo 对象。
  13. // 这里的 T 就是 RequestMappingInfo,它封装了 @RequestMapping 信息
  14. T mapping = getMappingForMethod(method, userType);
  15. if (mapping != null) {
  16. mappings.put(method, mapping);
  17. return true;
  18. } else {
  19. return false;
  20. }
  21. }
  22. });
  23. for (Method method : methods) {
  24. // 注册 Method 和它的映射,RequestMappingInfo 储存着映射信息
  25. registerHandlerMethod(handler, method, mappings.get(method));
  26. }
  27. }

最后在 registerHandlerMethod() 方法中,将 RequestMappingInfo 作为 key,把 Method 包装成 HandlerMethod 作为 value 添加到了 Map<T, HandlerMethod> handlerMethods 中。

  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  2. HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
  3. HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
  4. if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
  5. throw new IllegalStateException("");
  6. }
  7. this.handlerMethods.put(mapping, newHandlerMethod);
  8. Set<String> patterns = getMappingPathPatterns(mapping);
  9. for (String pattern : patterns) {
  10. if (!getPathMatcher().isPattern(pattern)) {
  11. this.urlMap.add(pattern, mapping);
  12. }
  13. }
  14. }

1.1 AbstractHandlerMapping 实现类及使用

AbstractHandlerMapping 只有一个实现类 RequestMappingHandlerMapping

二、HandlerAdapter

根据 Handler 来找到支持它的 HandlerAdapter,通过 HandlerAdapter 执行这个 Handler 得到 ModelAndView 对象。HandlerAdapter 接口中的方法如下:

  • boolean supports(Object handler); // 当前 HandlerAdapter 是否支持这个 Handler
  • ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object handler); // 利用 Handler 处理请求
  • long getLastModified(HttpServletRequest request, Object handler);

1 RequestMappingHandlerAdapter

从上面的文章中可以知道,利用 RequestMappingHandlerMapping 获取的 Handler 是 HadnlerMethod 类型,它代表 Controller 里要执行的方法,而 RequestMappingHandlerAdapter 可以执行 HadnlerMethod 对象。

RequestMappingHandlerAdapter 的 handle() 方法是在它的父类 AbstractHandlerMethodAdapter 类中实现的,源码如下所示

  1. @Override
  2. public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. return handleInternal(request, response, (HandlerMethod) handler);
  4. }

handleInternal() 方法是由 RequestMappingHandlerAdapter 自己来实现的,源码如下所示

  1. @Override
  2. protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  3. // 是否通过 @SessionAttributes 注释声明了 session 属性。
  4. if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
  5. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
  6. } else {
  7. checkAndPrepare(request, response, true);
  8. }
  9. // 是否需要在 synchronize 块中执行
  10. if (this.synchronizeOnSession) {
  11. HttpSession session = request.getSession(false);
  12. if (session != null) {
  13. Object mutex = WebUtils.getSessionMutex(session);
  14. synchronized (mutex) {
  15. // 执行 HandlerMethod
  16. return invokeHandleMethod(request, response, handlerMethod);
  17. }
  18. }
  19. }
  20. // 执行 HandlerMethod,得到 ModelAndView
  21. return invokeHandleMethod(request, response, handlerMethod);
  22. }

继续再来看一下如何得到 ModelAndView,invokeHandlerMethod() 方法如下

  1. private ModelAndView invokeHandleMethod(HttpServletRequest request,
  2. HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
  3. //
  4. ServletWebRequest webRequest = new ServletWebRequest(request, response);
  5. // 数据绑定
  6. WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
  7. ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
  8. // 绑定参数,执行方法
  9. ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);
  10. // 创建模型和视图容器
  11. ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  12. // 设置FlasgMap中的值
  13. mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
  14. // 初始化模型
  15. modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
  16. mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
  17.  
  18. AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
  19. asyncWebRequest.setTimeout(this.asyncRequestTimeout);
  20.  
  21. final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  22. asyncManager.setTaskExecutor(this.taskExecutor);
  23. asyncManager.setAsyncWebRequest(asyncWebRequest);
  24. asyncManager.registerCallableInterceptors(this.callableInterceptors);
  25. asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
  26. if (asyncManager.hasConcurrentResult()) {
  27. Object result = asyncManager.getConcurrentResult();
  28. mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
  29. asyncManager.clearConcurrentResult();
  30. requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
  31. }
  32. requestMappingMethod.invokeAndHandle(webRequest, mavContainer);
  33. if (asyncManager.isConcurrentHandlingStarted()) {
  34. return null;
  35. }
  36. return getModelAndView(mavContainer, modelFactory, webRequest);
  37. }

2 HttpRequestHandlerAdapter

HttpRequestHandlerAdapter 可以执行 HttpRequestHandler 类型的 Handler,源码如下

  1. @Override
  2. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. ((HttpRequestHandler) handler).handleRequest(request, response);
  4. return null;
  5. }

3 SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter 可以执行 Controller 类型的 Handler,源码如下

  1. @Override
  2. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. return ((Controller) handler).handleRequest(request, response);
  4. }

4 SimpleServletHandlerAdapter

SimpleServletHandlerAdapter 可以执行 Servlet 类型的 Handler,源码如下

  1. @Override
  2. public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  3. ((Servlet) handler).service(request, response);
  4. return null;
  5. }

三、HandlerExceptionResolver

负责处理异常的类,负责根据异常来设置 ModelAndView,然后交由 render 渲染界面。HandlerExecptionResolver 接口中只有一个方法,如下:

  • ModelAndView resolveException(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex);

[Java] SpringMVC工作原理之二:HandlerMapping和HandlerAdapter的更多相关文章

  1. [Java] SpringMVC工作原理之一:DispatcherServlet

    一.DispatcherServlet 处理流程 在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作.在看 Di ...

  2. [Java] SpringMVC工作原理之四:MultipartResolver

    MultipartResolver 用于处理文件上传,当收到请求时 DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isM ...

  3. [Java] Servlet工作原理之二:Session与Cookie

    (未完成) 一.Cookie与Session的使用简介 1 Cookie Cookie 用于记录用户在一段时间内的行为,它有两个版本:Version 0 和 Version 1,分别对应两种响应头 S ...

  4. [Java] SpringMVC工作原理之三:ViewResolver

    一.ViewResolver 根据视图的名称将其解析为 View 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Model 填入 ...

  5. 【SpringMVC架构】SpringMVC入门实例,解析工作原理(二)

    上篇博文,我们简单的介绍了什么是SpringMVC.这篇博文.我们搭建一个简单SpringMVC的环境,使用非注解形式实现一个HelloWorld实例,从简单入手,逐步深入. 环境准备 我们须要有主要 ...

  6. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  7. Java虚拟机工作原理详解 (一)

    一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘当中.然后你在命令行中输入 javac YourClassNam ...

  8. Java虚拟机工作原理详解

    原文地址:http://blog.csdn.net/bingduanlbd/article/details/8363734 一.类加载器 首先来看一下java程序的执行过程. 从这个框图很容易大体上了 ...

  9. Java虚拟机工作原理具体解释

    一.类载入器 首先来看一下java程序的运行过程. 从这个框图非常easy大体上了解java程序工作原理.首先,你写好java代码,保存到硬盘其中.然后你在命令行中输入 javac YourClass ...

随机推荐

  1. [转]docker基础详解

    本文转自:https://blog.csdn.net/xsj_blog/article/details/71700032 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog ...

  2. js数据类型有哪些,js属性和方法的归属,

    1.js的数据类型有哪些? 2.全局方法和全局属性? 一 1.js的本质就是处理数据,数据来自后台的数据库.所以变量就起到一个临时存储数据的作用. ECMAScript 制定了js的数据类型. 数据类 ...

  3. Netty 系列七(那些开箱即用的 ChannelHandler).

    一.前言 Netty 为许多通用协议提供了编解码器和处理器,几乎可以开箱即用, 这减少了你在那些相当繁琐的事务上本来会花费的时间与精力.另外,这篇文章中,就不涉及 Netty 对 WebSocket协 ...

  4. 浅析多线程的对象锁和Class锁

    一.前言 本来想在另外一篇文章说的,发现可能篇幅有点大,所以还是另开一篇博文来说好了.知识参考<Java多线程编程核心技术>,评价下这本书吧——大量的代码,简单的说明,真像在看博客.不过这 ...

  5. Java web.xml笔记

    Javaweb项目中, web.xml文件其中的各种设置, 就是简单的标注 <?xml version="1.0" encoding="UTF-8"?&g ...

  6. 汇编语言--微机CPU的指令系统(五)(移位操作指令)

    (5) 移位操作指令 移位操作指令是一组经常使用的指令,它包括算术移位.逻辑移位.双精度移位.循环移位和带进位的循环移位等五大类. 移位指令都有指定移动二进制位数的操作数,该操作数可以是立即数或CL的 ...

  7. 百度前端学院-基础学院-第20到21天之setTimeOut与setInterval

    setTimeout()可以使用clearTimeout()关闭 setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭. 注意:setInterv ...

  8. [ Laravel 5.5 文档 ] 快速入门 —— 目录结构篇

    简介 Laravel 默认的目录结构试图为不管是大型应用还是小型应用提供一个良好的起点.当然,你也可以按照自己的喜好重新组织应用的目录结构,因为 Laravel 对于指定类在何处被加载没有任何限制 — ...

  9. Android-textview图文混排(网络图片)

    工作太忙,不做过多的解释了,核心是用到了 SpannableStringBuilder  Glide  和 Rxjava 直接上代码了,就两个类. public class ImageSpanAsyn ...

  10. CsQuery中文编码乱码问题

    一.问题描述 InnerHTML 中文显示为Модель 二.解决方法 在初始化CQ对象前,先设置执行以下语句: CsQuery.Config.HtmlEncoder = CsQuery.HtmlEn ...