Spring MVC组件之HandlerAdapter

HandlerAdapter概述

HandlerAdapter组件是一个处理器Handler的适配器。HandlerAdapter组件的主要作用是适配特定的Handler来处理相应的请求。

在SpringMvc的源码中, HandlerAdapter是一个接口。该接口主要定义了三个方法。

1.boolean supports(Object handler)

判断HandlerAdapter组件是否支持这个handler实例。

2.ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception

HandlerAdapter组件使用handler实例来处理具体的请求。

3.long getLastModified(HttpServletRequest request, Object handler)

获取资源的最后修改值,该方法已经被遗弃。

HandlerAdapter类图

从以上类图中可以看出, HandlerAdapter接口系列的继承结构是比较简单的。HandlerFunctionAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,SimpleServletHandlerAdapter这四个类直接继承了HandlerAdapter接口。只有RequestMappingHandlerAdapter类稍微复杂一些。RequestMappingHandlerAdapter类是间接继承了HandlerAdapter这个接口。RequestMappingHandlerAdapter类首先是继承了AbstractHandlerMethodAdapter这个抽象类,而AbstractHandlerMethodAdapter这个抽象类再继承了HandlerAdapter接口。

RequestMappingHandlerAdapter

AbstractHandlerMethodAdapter

AbstractHandlerMethodAdapter是一个抽象类,它继承了HandlerAdapter接口,分别实现了HandlerAdapter接口的supports,handle,getLastModified这三个方法。

getLastModified方法已经弃用,就不多做描述。

supports方法

    @Override

    public final boolean supports(Object handler) {

        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));

}

supports方法主要是判断该适配器是否支持这个handler。代码中的逻辑主要是判断handler是否是HandlerMethod类的实例,再综合了supportsInternal方法的返回值。

supportsInternal方法在AbstractHandlerMethodAdapter类中只是一个虚方法,主要是提供给子类RequestMappingHandlerAdapter来做具体的实现。

    @Override

    @Nullable

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)

           throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);

}

在handle方法中,又直接调用了handleInternal方法。handleInternal方法在AbstractHandlerMethodAdapter类中也只是一个虚方法的声明,专门是提供给子类来实现,handleInternal这个虚方法,最终是在RequestMappingHandlerAdapter类中给出了具体的逻辑实现。

AbstractHandlerMethodAdapter实现了Ordered接口。该接口主要是用于排序。

AbstractHandlerMethodAdapter还继承了WebContentGenerator类,该类是一个web内容生成器的超类,它提供了如浏览器缓存控制、是否必须有session开启、支持的请求方法类型(GET、POST等)等一些属性和方法。

WebContentGenerator中有个checkRequest方法,主要是对请求进行检测。子类RequestMappingHandlerAdapter中会调用这个方法。

RequestMappingHandlerAdapter

Spring MVC容器在初始化HandlerAdapter类型的组件时,默认初始化的就是RequestMappingHandlerAdapter这个组件。

RequestMappingHandlerAdapter除了继承至AbstractHandlerMethodAdapter这个父类,它还实现了InitializingBean和BeanFactoryAware接口。

我们知道在Spring中如果一个类实现了InitializingBean接口,Spring容器就会在实例化该Bean时,会调用这个Bean的afterPropertiesSet方法。

 @Override

    public void afterPropertiesSet() {

        // Do this first, it may add ResponseBody advice beans

        initControllerAdviceCache();

        if (this.argumentResolvers == null) {

           List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();

           this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

        }

        if (this.initBinderArgumentResolvers == null) {

           List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();

           this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

        }

        if (this.returnValueHandlers == null) {

           List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();

           this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);

        }

}

在RequestMappingHandlerAdapter类中的afterPropertiesSet方法中,主要做了以下四件事情。

1.initControllerAdviceCache方法主要是初始化RequestMappingHandlerAdapter类中,initBinderAdviceCache,modelAttributeAdviceCache,          requestResponseBodyAdvice这三个属性。

在initControllerAdviceCache方法中会首先在Spring容器中,获取所带有@ControllerAdvice注解的bean。

然后依次遍历每个bean,在每个bean查找有@InitBinder注解的方法,再将这些方法初始化initBinderAdviceCache这个属性。

再次查找每个bean中有@ModelAttribute注解的方法,再将这些方法初始化到modelAttributeAdviceCache这个属性。

将实现了RequestBodyAdvice接口和ResponseBodyAdvice接口的bean,先收集起来。最后放入requestResponseBodyAdvice这个属性列表的最前面。

2.初始化RequestMappingHandlerAdapter类的argumentResolvers属性。

在RequestMappingHandlerAdapter类的argumentResolvers属性为空的情况下,会调用getDefaultArgumentResolvers()方法,来构造默认解析器的列表。

解析器的列表是按照注释解析,类型解析,自定义解析,所有类型解析的四种类型的顺序来进行构造的。

3.初始化RequestMappingHandlerAdapter类的initBinderArgumentResolvers属性。

在RequestMappingHandlerAdapter类的initBinderArgumentResolvers属性为空的情况下,会调用getDefaultInitBinderArgumentResolvers ()方法,来构造默认解析器的列表。

解析器的列表同样是按照”注释解析”, ”类型解析”, ”自定义解析”, ”所有类型解析”这四种类型的顺序来进行构造。

4.初始化RequestMappingHandlerAdapter类的returnValueHandlers属性,在RequestMappingHandlerAdapter类的returnValueHandlers属性为空的情况下,会调用getDefaultReturnValueHandlers ()方法,来构造默认解析器的列表。

解析器的列表是按照”单个意图”, ”注释解析”, ”多个意图”, ”类型解析”, ”自定义解析”, ”所有类型解析”等这几种类型的顺序来进行构造的。

RequestMappingHandlerAdapter继承了AbstractHandlerMethodAdapter类,它主要重写了父类AbstractHandlerMethodAdapter提供的三个模板方法。

1.supportsInternal方法

在supportsInternal方法中,直接返回了true。所以真正起作用的还是父类中的supports方法。

2.getLastModifiedInternal方法

该方法直接返回了-1。

3.handleInternal方法

这个方法是实际处理请求的方法。整个方法大致分了三个步骤。

1)         准备好处理请求的所有参数

2)         使用处理器处理请求

3)         将不同的类型的返回值统一成ModelAndView类型返回

 protected ModelAndView handleInternal(HttpServletRequest request,

           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;

        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.

        if (this.synchronizeOnSession) {

           HttpSession session = request.getSession(false);

           if (session != null) {

               Object mutex = WebUtils.getSessionMutex(session);

               synchronized (mutex) {

                   mav = invokeHandlerMethod(request, response, handlerMethod);

               }

           }

           else {

               // No HttpSession available -> no mutex necessary

               mav = invokeHandlerMethod(request, response, handlerMethod);

           }

        }

        else {

           // No synchronization on session demanded at all...

           mav = invokeHandlerMethod(request, response, handlerMethod);

        }

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {

           if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {

               applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);

           }

           else {

               prepareResponse(response);

           }

        }

        return mav;

    }

从以上代码中可以看出,handleInternal的方法中主要是调用了checkRequest,invokeHandlerMethod等这几个方法。

  • checkRequest方法

checkRequest方法主要是对请求进行检测。RequestMappingHandlerAdapter类的

checkRequest方法其实调用的是父类WebContentGenerator的checkRequest方法。

在checkRequest方法中,主要是做了两个检测。

    1. 检测请求的方法是否被支持,在AbstractHandlerMethodAdapter类中由于restrictDefaultSupportedMethods的值设置为false,所以对请求方法的检测不会执行。
    2. 检测请求的session是否存在
  • invokeHandlerMethod方法

1.首先会用request和response构建一个ServletWebRequest对象。

2.构建一个WebDataBinderFactory对象

3.构建一个ModelFactory对象

4.创建一个ServletInvocableHandlerMethod的实例,实际请求的处理就是通过它来执行的。ServletInvocableHandlerMethod实例创建后,会把argumentResolvers,returnValueHandlers,parameterNameDiscoverer这些属性值赋值给这个实例。

ServletInvocableHandlerMethod实例处理请求使用的是invokeAndHandle方法。在invokeAndHandle方法中,会先调用父类的invokeForRequest方法。再对Response的状态进行设置,最后使用HandlerMethodReturnValueHandler来处理返回值。

在invokeForRequest方法中,会先使用getMethodArgumentValues这个方法来获取方法调用的所有参数。再调用doInvoke(args)方法。doInvoke(args)方法是实际执行请求处理的方法,它是HandlerMethod系列的最核心的方法。在doInvoke(args)方法中,通过getBridgedMethod()获取Method的桥方法。再利用java的反射技术,调用BridgedMethod的invoke(getBean(), args)方法,将具体的handler执行。

WebDataBinderFactory

在RequestMappingHandlerAdapter类中,通过getDataBinderFactory方法,创建了一个WebDataBinderFactory类型的实例。

 private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {

        Class<?> handlerType = handlerMethod.getBeanType();

        Set<Method> methods = this.initBinderCache.get(handlerType);

        if (methods == null) {

           methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);

           this.initBinderCache.put(handlerType, methods);

        }

        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();

        // Global methods first

        this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {

           if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {

               Object bean = controllerAdviceBean.resolveBean();

               for (Method method : methodSet) {

                   initBinderMethods.add(createInitBinderMethod(bean, method));

               }

           }

        });

        for (Method method : methods) {

           Object bean = handlerMethod.getBean();

           initBinderMethods.add(createInitBinderMethod(bean, method));

        }

        return createDataBinderFactory(initBinderMethods);

    }

在getDataBinderFactory方法中,会先通过handlerMethod所属bean类型,查找带有@InitBinder注解的方法。

这里使用了initBinderCache缓存,一般是先在缓存中查找,在找不到的情况下,再通过MethodIntrospector.selectMethods方法去查找。最后把查找到的methods放入到缓存中。

    在initBinderAdviceCache缓存中,缓存的是全局的@InitBinder注解的方法。所谓全局的方法就是@ControllerAdvice注解类里面的@InitBinder注解的方法。

代码中会先将全局的method构建成InvocableHandlerMethod对象放入initBinderMethods的集合中。再将handlerMethod所属bean类型的method放入集合。最后通过initBinderMethods集合,来创建一个ServletRequestDataBinderFactory对象。

WebDataBinderFactory是一个工厂类,专门用来创建DataBinder类型的对象。DataBinder类型的对象用于参数绑定,主要功能就是实现参数跟String之间的类型转换,ArgumentResolver在进行参数解析的过程中会用到WebDataBinder,另外ModelFactory在更新Model时也会用到它。

ModelFactory

在RequestMappingHandlerAdapter类中,通过getModelFactory方法,创建了一个ModelFactory类型的实例。

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {

        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);

        Class<?> handlerType = handlerMethod.getBeanType();

        Set<Method> methods = this.modelAttributeCache.get(handlerType);

        if (methods == null) {

           methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);

           this.modelAttributeCache.put(handlerType, methods);

        }

        List<InvocableHandlerMethod> attrMethods = new ArrayList<>();

        // Global methods first

       this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {

           if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {

               Object bean = controllerAdviceBean.resolveBean();

               for (Method method : methodSet) {

                   attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));

               }

           }

        });

        for (Method method : methods) {

           Object bean = handlerMethod.getBean();

           attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));

        }

        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);

    }

ModelFactory类型实例的创建和WebDataBinderFactory的创建过程十分类似。都是先在handlerMethod所属bean类型查找@ModelAttribute注解的局部方法。再从modelAttributeAdviceCache缓存中,查找全局的@ModelAttribute注解的方法。

将全局和局部的@ModelAttribute注解方法,组合起来形成一个HandlerMothed的列表。全局的@ModelAttribute注解方法会放在这个列表的前面。

ModelFactory与WebDataBinderFactory的创建过程不同的是,多了一个SessionAttributesHandler实例的获取。最后会把HandlerMothed的列表,SessionAttributesHandler的实例和binderFactory作为参数,来构建ModelFactory的实例。

ModelFactory类是专门用来维护Model的。他主要做了两件事件。

  1. ModelFactory类的initModel方法初始化Model

initModel方法主要是在处理器Handler执行前,将相应的数据设置到Model中。

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)

           throws Exception {

        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);

        container.mergeAttributes(sessionAttributes);

        invokeModelAttributeMethods(request, container);

        for (String name : findSessionAttributeArguments(handlerMethod)) {

           if (!container.containsAttribute(name)) {

               Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);

               if (value == null) {

                   throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);

               }

               container.addAttribute(name, value);

           }

        }

    }

从initModel方法的代码中可以看出,initModel方法一共做了三件事件。

  • 从SessionAttributesHandler对象中取出sessionAttributes,合并到ModelAndViewContainer的对象中。
  • 调用invokeModelAttributeMethods方法,该方法主要是依次执行所有@ModelAttribute注解的方法,并将最终的结果放到Model中。
  • 找到即是@ModelAttribute注解又是@SessionAttribute注解的参数名称,再遍历所有的参数,判断这个参数是否已经在Model中,如果没有,则把参数名和参数值存到Model中。
  1. ModelFactory类的updateModel方法,主要是将参数更新到SessionAttributes。updateModel方法主要做了两件事情。
  • 对SessionAttributes中的进行设置。
  • 给Model中需要的参数设置BindingResult,以备视图使用。

ServletInvocableHandlerMethod

类图

从类图中可以看出,ServletInvocableHandlerMethod类继承了InvocableHandlerMethod类,而InvocableHandlerMethod类又继承了HandlerMethod这个父类。

HandlerMethod

HandlerMethod类主要是封装了处理程序方法的信息,并提供了对方法参数、方法返回值、方法注释等信息的访问。

HandlerMethod类中的属性

名称

类型

描述

bean

Object

Web控制器方法所在的Web控制器 bean。可以是字符串,代表 bean 的名称; 也可以是 bean 实例对象本身。

beanType

Class

Web控制器方法所在的Web控制器bean 的类型, 如果该bean被代理,这里记录的是被代理的用户类信息

method

Method

Web控制器方法

bridgedMethod

Method

被桥接的Web控制器方法

parameters

MethodParameter[]

Web控制器方法的参数信息: 所在类所在方法,参数,索引,参数类型

responseStatus

HttpStatus

注解@ResponseStatus的code属性

responseStatusReason

String

注解@ResponseStatus的reason属性

InvocableHandlerMethod

InvocableHandlerMethod类继承了HandlerMethod类,它在父类的基础上增加了三个属性。

  1. dataBinderFactory:WebDataBinderFactory类型,主要是用于@InitBinder注释的参数。
  2. argumentResolvers:HandlerMethodArgumentResolverComposite类型,用于参数解析器。
  3. parameterNameDiscoverer:ParameterNameDiscoverer类型,用于获取参数名。

InvocableHandlerMethod类提供了一个重要的方法invokeForRequest,该方法主要是使用反射方式对Method进行调用。

其实@InitBinder注解和@ModelAttribute注解的方法,都是封装成了InvocableHandlerMethod类型的实例。调用InvocableHandlerMethod类型实例的invokeForRequest方法,就是对这两个注解下的方法进行了调用。

 @Nullable

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,

           Object... providedArgs) throws Exception {

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        if (logger.isTraceEnabled()) {

           logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +

                   "' with arguments " + Arrays.toString(args));

        }

        Object returnValue = doInvoke(args);

        if (logger.isTraceEnabled()) {

           logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +

                   "] returned [" + returnValue + "]");

        }

        return returnValue;

    }

invokeForRequest方法中主要做了两件事件。1.是通过getMethodArgumentValues方法,准备好了调用方法所需要的参数。2. 在doInvoke方法中,通过桥接方法,使用反射方式对方法进行了调用。

ServletInvocableHandlerMethod

ServletInvocableHandlerMethod类又继承了InvocableHandlerMethod类。它在提供了invokeAndHandle方法。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,

           Object... providedArgs) throws Exception {

       Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        setResponseStatus(webRequest);

        if (returnValue == null) {

           if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {

               disableContentCachingIfNecessary(webRequest);

               mavContainer.setRequestHandled(true);

               return;

           }

        }

        else if (StringUtils.hasText(getResponseStatusReason())) {

           mavContainer.setRequestHandled(true);

           return;

        }

        mavContainer.setRequestHandled(false);

        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {

           this.returnValueHandlers.handleReturnValue(

                   returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

        }

        catch (Exception ex) {

           if (logger.isTraceEnabled()) {

               logger.trace(formatErrorForReturnValue(returnValue), ex);

           }

           throw ex;

        }

    }

在代码中可以看到,ServletInvocableHandlerMethod类的invokeAndHandle方法中,先调用了父类的invokeForRequest方法。在此基础上,1.增加了对ResponseStatus状态的设置。2.对返回值进行了处理。

ModelAndView

ModelAndView类中包含了Model和View两个重要的属性。ModelAndView主要用于后台与前端页面交互。它可以保存数据,并重定向或转发到指定页面,然后使用数据来渲染页面。

Model是一个ModelMap类型,而ModelMap继承了LinkedHashMap的这个子类。Model 对象负责在控制器(Controller)和视图(View)之间传递数据。Model属性中的数据会复制到 Servlet Response的属性中。Model相当于一个JOPO的对象。

View是Spring MVC中的视图。视图的作用是渲染数据,将模型model中的数据展示给用户。视图可以用于重定向或转发到指定页面。

HttpRequestHandlerAdapter

HttpRequestHandlerAdapter是专门的http请求处理器的适配器。这里的http请求处理器Handler,是一个实现了org.springframework.web. HttpRequestHandler接口的实例。

我们自定义的http请求处理器,需要实现HttpRequestHandler接口的handleRequest方法。在这个方法实现自己业务逻辑。

handleRequest方法的中提供了HttpServletRequest和HttpServletResponse的两个参数,来供我们使用。

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter是控制器Controller处理器的适配器。这里的控制器处理器的Handler,是一个实现了org.springframework.web.servlet.mvc.Controller接口的实例。

Spring MVC中提供了org.springframework.web.servlet.mvc.AbstractController的这个抽象类,AbstractController抽象类实现了Controller接口。

在AbstractController中实现Controller接口的handleRequest方法,并提供了handleRequestInternal的这个模板方法留给子类自行扩展。

我们一般会直接继承AbstractController抽象类,并重写handleRequestInternal方法。在handleRequestInternal方法中,我们需要构造一个ModelAndView对象返回即可。

Spring MVC组件之HandlerAdapter的更多相关文章

  1. Spring MVC组件之HandlerMapping

    Spring MVC组件之HandlerMapping HandlerMapping概述 HandlerMapping组件的作用解析一个个Request请求,并找到相应处理这个Request的Hand ...

  2. Spring MVC(2)Spring MVC 组件开发

    一.控制器接收各类请求参数 代码测试环境: 接收各类参数的控制器--ParamsController package com.ssm.chapter15.controller; @Controller ...

  3. Java之Spring mvc详解

    文章大纲 一.Spring mvc介绍二.Spring mvc代码实战三.项目源码下载四.参考文章   一.Spring mvc介绍 1. 什么是springmvc   springmvc是sprin ...

  4. Spring MVC 设计概述

      MVC设计的根本原因在于解耦各个模块 Spring MVC的架构 对于持久层而言,随着软件发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足移植数据库的要求. ...

  5. Spring MVC(一)--SpringMVC的初始化和流程

    SpringMVC是Spring提供给WEB应用的MVC框架,MVC框架一般来说由三部分组成: Model:模型层,一般由java bean完成,主要是进行数据库操作: View:视图层,用于前端展示 ...

  6. Spring MVC 工作原理和流程、注解

    Spring MVC 是实现MVC设计模式的企业级开发框架,是Spring框架的一个子模块,无需整合,开发起来更加便捷. MVC设计模式 MVC是一种设计模式,它将应用程序分为 Controller. ...

  7. Spring MVC(1)Spring MVC的初始化和流程以及SSM的实现

    一.Spring MVC概述 1.Spring MVC 的架构 对于持久层而言,随着软件的发展,迁移数据库的可能性很小,所以在大部分情况下都用不到Hibernate的HQL来满足迁移数据库的要求.与此 ...

  8. 看透Spring MVC:源代码分析与实践 (Web开发技术丛书)

    第一篇 网站基础知识 第1章 网站架构及其演变过程2 1.1 软件的三大类型2 1.2 基础的结构并不简单3 1.3 架构演变的起点5 1.4 海量数据的解决方案5 1.4.1 缓存和页面静态化5 1 ...

  9. spring mvc与mybatis事务整合

    之前公司用的是mybatis,但事务管理这块是用ejb的CMT容器管理的事务.基本原理是ejb请求进来,业务代码会创建一个mybatis的session然后放入当前线程,之后所有的方法操作涉及到数据库 ...

随机推荐

  1. Python参数传递中的 args, kwargs

    概念 真正的Python参数传递语法是*和**,其被称为 被称为打包和解包参数.*args和**kwargs只是大家默认的一种形式.也可以写成*keys和**kkeys等其他形式.二者都是为了在不知道 ...

  2. Tensor的创建和维度的查看

    常见的Tensor创建方法 1,基础Tensor函数:torch.Tensor(2,2)32位浮点型 2,指定类型: torch.DoubleTensor(2,2)64位浮点型 3,使用python的 ...

  3. 安装ImageMagick7.1库以及php的Imagick扩展

    由于ImageMagick7以下不支持heic等图片格式,所以重新安装了ImageMagick7.1版本支持heic格式,并写此文章记录一下. 如果安装过程中遇到一些未知的错误,https://ima ...

  4. Android 12(S) 图像显示系统 - HWC HAL 初始化与调用流程

    必读: Android 12(S) 图像显示系统 - 开篇 接口定义 源码位置:/hardware/interfaces/graphics/composer/ 在源码目录下可以看到4个版本的HIDL ...

  5. 论文解读(GCC)《Efficient Graph Convolution for Joint Node RepresentationLearning and Clustering》

    论文信息 论文标题:Efficient Graph Convolution for Joint Node RepresentationLearning and Clustering论文作者:Chaki ...

  6. netty系列之:在netty中使用native传输协议

    目录 简介 native传输协议的依赖 netty本地传输协议的使用 总结 简介 对于IO来说,除了传统的block IO,使用最多的就是NIO了,通常我们在netty程序中最常用到的就是NIO,比如 ...

  7. vue在Docker上运行

    Dockerfile # 设置基础镜像 FROM nginx:latest # 定义作者 MAINTAINER test # 将dist文件中的内容复制到 /etc/nginx/html/ 这个目录下 ...

  8. S32Kxxx bootloader之UDS bootloader

    了解更多关于bootloader 的C语言实现,请加我Q扣: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 两周前完成了基于UDS ...

  9. NC25025 [USACO 2007 Nov G]Sunscreen

    NC25025 [USACO 2007 Nov G]Sunscreen 题目 题目描述 To avoid unsightly burns while tanning, each of the \(C\ ...

  10. Visdom

    安装visdom pip install visdom 启动visdom服务 python -m visdom.server visdom绘制各种图形: visdom属性介绍: 实例: from vi ...