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. 李呈祥:bilibili在湖仓一体查询加速上的实践与探索

    导读: 本文主要介绍哔哩哔哩在数据湖与数据仓库一体架构下,探索查询加速以及索引增强的一些实践.主要内容包括: 什么是湖仓一体架构 哔哩哔哩目前的湖仓一体架构 湖仓一体架构下,数据的排序组织优化 湖仓一 ...

  2. 重学ES系列之新增的几个循环方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. SAP Drag or drop tree

    1 *&---------------------------------------------------------------------* 2 *& Report RSDEM ...

  4. Windows安装face_recognition

    安装提供的python和cmake,最好都添加一下环境变量 安装dlib,pip install dlib-19.7.0-cp36-cp36m-win_amd64.whl 安装face_recogni ...

  5. 云ATM架构设计

    云ATM架构设计 启动程序(Start.java) public class Start { public static void main(String[] args) { MainView vie ...

  6. 4-7 CS后台项目练习-1

    1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...

  7. Solution -「CF520E」Pluses everywhere

    Step 1. 转化一步题目:考虑有 \(n\) 个小球,每个小球有 \(a_i\) 的价值,\(m\) 个板子,把板子插进小球间的空隙,且不能插在第 \(1\) 个球之前与第 \(n\) 个球之后. ...

  8. ActiveMQ、RabbitMQ、RocketMQ、Kafka四种消息中间件分析介绍

    ActiveMQ.RabbitMQ.RocketMQ.Kafka四种消息中间件分析介绍 我们从四种消息中间件的介绍到基本使用,以及高可用,消息重复性,消息丢失,消息顺序性能方面进行分析介绍! 一.消息 ...

  9. 搞懂前端二进制系列(二):🍈File、FileReader与Base64

    参考资料: JavaScript高级程序设计第四版:File API https://juejin.cn/post/7046313942938812424[前端二进制一次搞清楚] 一.File 类型 ...

  10. SpringBoot的创建和特性

    一.SpringBoot的特点 创建独立的Spring应用程序 直接嵌入Tomcat.Jetty或Undertow(无需部署WAR文件) 提供自以为是的"starter"依赖项,以 ...