Spring MVC 源码 - HandlerAdapter 组件(一)之 HandlerAdapter

news2024/9/26 5:17:48

HandlerAdapter 组件

HandlerAdapter 组件,处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器

HandlerAdapter 组件(一)之 HandlerAdapter

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerAdapter 组件,可以回到《一个请求响应的旅行过程》中的 DispatcherServletdoDispatch 方法中看看,如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   ModelAndView mv = null;
   // ... 省略相关代码
   // <3> 获得请求对应的 HandlerExecutionChain 对象(HandlerMethod 和 HandlerInterceptor 拦截器们)
   mappedHandler = getHandler(processedRequest);
   // ... 省略相关代码
   // <4> 获得当前 handler 对应的 HandlerAdapter 对象
   HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
   // ... 省略相关代码
   // <6> 真正的调用 handler 方法,也就是执行对应的方法,并返回视图
   mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
   // ... 省略相关代码
}
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
   if (this.handlerAdapters != null) {
       for (HandlerAdapter adapter : this.handlerAdapters) {
           if (adapter.supports(handler)) {
               return adapter;
           }
       }
   }
   throw new ServletException("No adapter for handler [...");
}

通过遍历 HandlerAdapter 组件们,判断是否支持处理该 handler 处理器,支持则返回该 HandlerAdapter 组件。注意,这里是通过一个一个的 HandlerAdapter 组件去判断是否支持该处理器,如果支持则直接返回这个 HandlerAdapter 组件,不会继续下去,所以获取处理器对应 HandlerAdapter 组件是有一定的先后顺序的,默认是HttpRequestHandlerAdapter -> SimpleControllerHandlerAdapter -> RequestMappingHandlerAdapter

本文涉及到的内容适中,可以先查看我的总结

HandlerAdapter 接口

org.springframework.web.servlet.HandlerAdapter接口,处理器的适配器,去执行处理器,代码如下:

public interface HandlerAdapter {
    /**
     * 是否支持该处理器
     */
    boolean supports(Object handler);

    /**
     * 执行处理器,返回 ModelAndView 结果
     */
    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    /**
     * 返回请求的最新更新时间,如果不支持该操作,则返回 -1 即可
     */
    long getLastModified(HttpServletRequest request, Object handler);
}

HandlerAdapter 接口的体系结构如下:

没有特别多 心里有点点欣慰,其中 RequestMappingHandlerAdapter 就是基于@RequestMapping 等注解的 HandlerMethod 的 HandlerMethodAdapter 实现类,名字都差不多

初始化过程

DispatcherServletinitHandlerAdapters(ApplicationContext context) 方法,会在 onRefresh 方法被调用,初始化 HandlerAdapter 组件,方法如下:

private void initHandlerAdapters(ApplicationContext context) {
    this.handlerAdapters = null;

    if (this.detectAllHandlerAdapters) {
        // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerAdapter> matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerAdapters = new ArrayList<>(matchingBeans.values());
            // We keep HandlerAdapters in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerAdapters);
        }
    }
    else {
        try {
            HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
            this.handlerAdapters = Collections.singletonList(ha);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerAdapter later.
        }
    }

    // Ensure we have at least some HandlerAdapters, by registering
    // default HandlerAdapters if no other adapters are found.
    /**
     * 如果未获得到,则获得默认配置的 HandlerAdapter 类
     * {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter}
     * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter}
     * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter}
     */
    if (this.handlerAdapters == null) {
        this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  1. 如果“开启”探测功能,则扫描已注册的 HandlerAdapter 的 Bean 们,添加到 handlerAdapters 中,默认开启,这里会进行排序,可以通过实现 Order 接口设置排序值

  1. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerAdapter" 对应的 Bean ,将其添加至 handlerAdapters

  1. 如果未获得到,则获得默认配置的 HandlerAdapter 类,调用

getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 HandlerAdapter 的默认实现类,如下:

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

可以看到对应的是 HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter、RequestMappingHandlerAdapter 三个实现类,接下来就一个一个分析

HttpRequestHandlerAdapter

org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,实现 HandlerAdapter 接口,基于 HttpRequestHandler 接口的 HandlerAdapter 实现类,代码如下:

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        // 判断是 HttpRequestHandler 类型
        return (handler instanceof HttpRequestHandler);
    }
    
    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // HttpRequestHandler 类型的调用
        ((HttpRequestHandler) handler).handleRequest(request, response);
        return null;
    }
    
    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        // 处理器实现了 LastModified 接口的情况下
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

//  org.springframework.web.HttpRequestHandler.java
@FunctionalInterface
public interface HttpRequestHandler {
    /**
     * 处理请求
     */
    void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException;
}

逻辑比较简单,如果这个处理器实现了 HttpRequestHandler 接口,则使用 HttpRequestHandlerAdapter 调用该处理器的 handleRequest(HttpServletRequest request, HttpServletResponse response)方法去处理器请求,返回 null

这种处理器如何配置呢?

可以回到《HandlerMapping 组件(四)之 AbstractUrlHandlerMapping》SimpleUrlHandlerMapping 或者 BeanNameUrlHandlerMapping 小节中的使用示例看看


SimpleControllerHandlerAdapter

org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,实现 HandlerAdapter 接口,基于 Controller 接口的 HandlerAdapter 实现类,代码如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        // <1> 判断是 Controller 类型
        return (handler instanceof Controller);
    }

    @Override
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // <2> Controller 类型的调用
        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        // 处理器实现了 LastModified 接口的情况下
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }
}

@FunctionalInterface
public interface Controller {
    /**
     * 处理请求
     */
    @Nullable
    ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

逻辑比较简单,和 HttpRequestHandlerAdapter 差不多,如果这个处理器实现了 Controoler 接口,则使用 HttpRequestHandlerAdapter 调用该处理器的 handleRequest(HttpServletRequest request, HttpServletResponse response)方法去处理器请求,直接返回处理器执行后返回 ModelAndView

这种处理器如何配置和 HttpRequestHandlerAdapter 相同,见上文描述

SimpleServletHandlerAdapter 实现类就不讲述了,因为默认的 HandlerAdapter 实现类中没有它
逻辑实现和 SimpleControllerHandlerAdapter 差不多,区别在于它判断是否为 javax.servlet.Servlet 对象,是的话则调用其 service 方法,返回该方法执行后返回的 ModelAndView 对象

AbstractHandlerMethodAdapter

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter,实现 HandlerAdapter、Ordered 接口,继承 WebContentGenerator 抽象类,基于 HandlerMethod 的 HandlerMethodAdapter 抽象类

构造方法

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
    /** 最低优先级 */
    private int order = Ordered.LOWEST_PRECEDENCE;

    public AbstractHandlerMethodAdapter() {
        // no restriction of HTTP methods by default
        // 调用 WebContentGenerator 类的构造方法
        // 参数 restrictDefaultSupportedMethods 参数为 false ,表示不需要严格校验 HttpMethod
        super(false);
    }
}

supports方法

实现 supports(Object handler) 方法,判断是否支持该处理器,代码如下:

@Override
public final boolean supports(Object handler) {
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
  • 处理器必须是 HandlerMethod 类型,也就是在《HandlerMapping 组件(三)之 AbstractHandlerMethodMapping》讲到的通过 @RequestMapping 等注解方法所生成对应 HandlerMethod 对象

  • 还需要调用抽象方法 supportsInternal(HandlerMethod handlerMethod)判断是否支持, 交由子类去实现,详情见下文

handle方法

实现 handle(HttpServletRequest request, HttpServletResponse response, Object handler) 方法,用于处理请求,执行该处理器,代码如下:

@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
}

@Nullable
protected abstract ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
  • 如果该 HandlerAdapter 支持这个处理器,那么则会调用该方法去处理请求,执行这个处理器

  • 直接调用 handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 抽象方法,交由子类去实现,详情见下文

getLastModified方法

实现 getLastModified(HttpServletRequest request, Object handler) 方法,获得最后更新时间,代码如下

@Override
public final long getLastModified(HttpServletRequest request, Object handler) {
    return getLastModifiedInternal(request, (HandlerMethod) handler);
}

protected abstract long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod);
  • 直接调用getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod)抽象方法,交由子类去实现,详情见下文

RequestMappingHandlerAdapter

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,实现 BeanFactoryAware、InitializingBean 接口,继承 AbstractHandlerMethodAdapter 抽象类,基于 @RequestMapping 注解的 HandlerMethod 处理器的 HandlerMethodAdapter 实现类

构造方法

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    /**
     * MethodFilter that matches {@link InitBinder @InitBinder} methods.
     */
    public static final MethodFilter INIT_BINDER_METHODS = method ->
            AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
    /**
     * MethodFilter that matches {@link ModelAttribute @ModelAttribute} methods.
     */
    public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
            (!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
                    AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));

    @Nullable
    private List<HandlerMethodArgumentResolver> customArgumentResolvers;

    @Nullable
    private HandlerMethodArgumentResolverComposite argumentResolvers;

    @Nullable
    private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;

    @Nullable
    private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;

    @Nullable
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    @Nullable
    private List<ModelAndViewResolver> modelAndViewResolvers;

    private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();

    private List<HttpMessageConverter<?>> messageConverters;

    private List<Object> requestResponseBodyAdvice = new ArrayList<>();

    @Nullable
    private WebBindingInitializer webBindingInitializer;

    private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("MvcAsync");

    @Nullable
    private Long asyncRequestTimeout;

    private CallableProcessingInterceptor[] callableInterceptors = new CallableProcessingInterceptor[0];

    private DeferredResultProcessingInterceptor[] deferredResultInterceptors = new DeferredResultProcessingInterceptor[0];

    private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();

    private boolean ignoreDefaultModelOnRedirect = false;

    private int cacheSecondsForSessionAttributeHandlers = 0;

    /**
     * 是否对相同 Session 加锁
     */
    private boolean synchronizeOnSession = false;

    private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    @Nullable
    private ConfigurableBeanFactory beanFactory;

    // ========== 缓存 ==========
    private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
    private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
    private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
    private final Map<Class<?>, Set<Method>> modelAttributeCache = new ConcurrentHashMap<>(64);
    private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>();
    
    // ... 省略 getter、setter 方法
    public RequestMappingHandlerAdapter() {
        // 初始化 messageConverters
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316

        this.messageConverters = new ArrayList<>(4);
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(stringHttpMessageConverter);
        try {
            this.messageConverters.add(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    }
}

有许多的属性,不着急理解,先列几个主要的属性对象:

  • HandlerMethodArgumentResolverComposite argumentResolvers:参数处理器组合对象

  • HandlerMethodReturnValueHandlerComposite returnValueHandlers:返回值处理器组合对象

  • List<HttpMessageConverter<?>> messageConverters:HTTP 消息转换器集合对象

  • List<Object> requestResponseBodyAdvice: RequestResponseAdvice 集合对象

在构造方法中默认会添加了四个 HttpMessageConverter 对象,当然,默认还会添加其他的,例如 MappingJackson2HttpMessageConverter 为 JSON 消息格式的转换器

1.afterPropertiesSet 初始化方法

因为 RequestMappingHandlerAdapter 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:

@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBody advice beans
    // <1> 初始化 ControllerAdvice 相关
    initControllerAdviceCache();

    // <2> 初始化 argumentResolvers 属性
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // <3> 初始化 initBinderArgumentResolvers 属性
    if (this.initBinderArgumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // <4> 初始化 returnValueHandlers 属性
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}
  1. 调用 initControllerAdviceCache() 方法,初始化 ControllerAdvice 相关,详情见下文

  1. 初始化 argumentResolvers 属性,调用 getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文

  1. 初始化 initBinderArgumentResolvers 属性,调用 getDefaultInitBinderArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文

  1. 初始化 returnValueHandlers 属性,调用 getDefaultReturnValueHandlers() 方法,获得默认的 HandlerMethodReturnValueHandler 数组,详情见下文

1.1 initControllerAdviceCache

initControllerAdviceCache() 方法,初始化 ControllerAdvice 相关,方法如下:

private void initControllerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    // <1> 扫描 @ControllerAdvice 注解的 Bean 们,生成对应的 ControllerAdviceBean 对象,并将进行排序
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();

    // <2> 遍历 ControllerAdviceBean 数组
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        // <2.1> 扫描有 `@ModelAttribute` ,无 `@RequestMapping` 注解的方法,添加到 `modelAttributeAdviceCache` 属性中
        // 该类方法用于在执行方法前修改 Model 对象
        Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
        if (!attrMethods.isEmpty()) {
            this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
        }
        // <2.2> 扫描有 `@InitBinder` 注解的方法,添加到 `initBinderAdviceCache` 属性中
        // 该类方法用于在执行方法前初始化数据绑定器
        Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
        if (!binderMethods.isEmpty()) {
            this.initBinderAdviceCache.put(adviceBean, binderMethods);
        }
        // <2.3> 如果是 RequestBodyAdvice 或 ResponseBodyAdvice 的子类,添加到 requestResponseBodyAdviceBeans 中
        if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            requestResponseBodyAdviceBeans.add(adviceBean);
        }
    }

    // <2.3> 将 requestResponseBodyAdviceBeans 添加到 this.requestResponseBodyAdvice 属性种
    if (!requestResponseBodyAdviceBeans.isEmpty()) {
        this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
    }

    // 打印日志
    if (logger.isDebugEnabled()) {
        int modelSize = this.modelAttributeAdviceCache.size();
        int binderSize = this.initBinderAdviceCache.size();
        int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
        int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
        if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
            logger.debug("ControllerAdvice beans: none");
        }
        else {
            logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
                    " @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
        }
    }
}

从 Spring 上下文扫描 @ControllerAdvice 注解的 Bean 们,生成对应的 ControllerAdviceBean 对象,并将进行排序,方法如下:

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
    return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
            // 排除代理目标类,AOP 相关
            .filter(name -> !ScopedProxyUtils.isScopedTarget(name))
            // 包含 @ControllerAdvice 注解
            .filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
            // 生成对应的 ControllerAdviceBean 对象
            .map(name -> new ControllerAdviceBean(name, context))
            .collect(Collectors.toList());
}
  1. @ControllerAdvice 注解:用于 Controller 类的增强类,其中可定义多种增强的方法,例如 @ExceptionHandler 注解的方法用于处理器 Controller 抛出的异常

  1. 遍历 1 中生成 ControllerAdviceBean 数组

  1. 扫描 @ModelAttribute @RequestMapping 注解的方法,添加到 modelAttributeAdviceCache 属性中,该类方法用于在执行方法前修改 Model 对象

  1. 扫描 @InitBinder 注解的方法,添加到 initBinderAdviceCache 属性中,该类方法用于在执行方法前初始化数据绑定器

  1. 如果是 RequestBodyAdvice 或 ResponseBodyAdvice 的子类,保存至 requestResponseBodyAdviceBeans 临时变量中

  1. 2.c 的 requestResponseBodyAdviceBeans 保存至 requestResponseBodyAdvice 属性中

1.2 getDefaultArgumentResolvers

getDefaultArgumentResolvers(),初始化默认的参数解析器,方法如下:

private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

    // Annotation-based argument resolution
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
    resolvers.add(new RequestParamMapMethodArgumentResolver());
    resolvers.add(new PathVariableMethodArgumentResolver());
    resolvers.add(new PathVariableMapMethodArgumentResolver());
    resolvers.add(new MatrixVariableMethodArgumentResolver());
    resolvers.add(new MatrixVariableMapMethodArgumentResolver());
    resolvers.add(new ServletModelAttributeMethodProcessor(false));
    resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new RequestHeaderMapMethodArgumentResolver());
    resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());
    resolvers.add(new MapMethodProcessor());
    resolvers.add(new ErrorsMethodArgumentResolver());
    resolvers.add(new SessionStatusMethodArgumentResolver());
    resolvers.add(new UriComponentsBuilderMethodArgumentResolver());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    // Catch-all
    resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
    resolvers.add(new ServletModelAttributeMethodProcessor(true));

    return resolvers;
}

1.3 getDefaultReturnValueHandlers

getDefaultReturnValueHandlers(),初始化默认的返回值处理器,方法如下:

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

    // Single-purpose return value types
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
            this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
    handlers.add(new StreamingResponseBodyReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));
    handlers.add(new HttpHeadersReturnValueHandler());
    handlers.add(new CallableMethodReturnValueHandler());
    handlers.add(new DeferredResultMethodReturnValueHandler());
    handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

    // Annotation-based return value types
    handlers.add(new ModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
            this.contentNegotiationManager, this.requestResponseBodyAdvice));

    // Multi-purpose return value types
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());

    // Custom return value types
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    // Catch-all
    if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
        handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
    }
    else {
        handlers.add(new ModelAttributeMethodProcessor(true));
    }

    return handlers;
}
  • 按顺序添加了非常多的返回值处理器对象

supportsInternal 方法

实现 supportsInternal() 接口,方法如下:

@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
    return true;
}

直接返回 true,也就是说处理器只要是 HandlerMethod 对象就可以

getLastModifiedInternal 方法

@Override
protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
    return -1;
}

直接返回 -1

handleInternal 方法

实现 handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,处理请求,执行处理器,方法如下:

@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, 
                                      HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    // <1> 校验请求(HttpMethod 和 Session 的校验)
    checkRequest(request);
    // <2> 调用 HandlerMethod 方法
    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) { // 同步相同 Session 的逻辑,默认情况false
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 获取Session的锁对象
            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)) { // 响应不包含'Cache-Control'头
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            prepareResponse(response);
        }
    }
    return mav;
}

1、调用父类 WebContentGenerator 的 checkRequest(ttpServletRequest request) 方法,校验请求(HttpMethod 和 Session)是否合法

protected final void checkRequest(HttpServletRequest request) throws ServletException {
    // Check whether we should support the request method.
    String method = request.getMethod();
    if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
        throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
    }
    // Check whether a session is required.
    if (this.requireSession && request.getSession(false) == null) {
        throw new HttpSessionRequiredException("Pre-existing session required but none found");
    }
}

在 AbstractHandlerMethodAdapter 的构造方法中,传入 restrictDefaultSupportedMethods 参数为 false,表示不需要严格校验 HttpMethod,这里正常情况都会校验通过

2、调用 invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,执行 HandlerMethod 处理器

这里会判断 synchronizeOnSession 属性,控制是否同步相同 Session 的逻辑,其中 WebUtils#getSessionMutex(session) 方法,用来获得锁的对象,方法如下:

public static Object getSessionMutex(HttpSession session) {
    Assert.notNull(session, "Session must not be null");
    Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
    if (mutex == null) {
        mutex = session;
    }
    return mutex;
}

当然,因为锁是通过 synchronized 是 JVM 进程级,所以在分布式环境下,无法达到同步相同 Session 的功能

默认情况下,synchronizeOnSessionfalse


【重点】invokeHandlerMethod方法

invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) 方法,执行 HandlerMethod 处理器,方法如下:

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, 
                                           HandlerMethod handlerMethod) throws Exception {
    // <1> 创建 ServletWebRequest 对象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // <2> 创建 WebDataBinderFactory 对象
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // <3> 创建 ModelFactory 对象
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // <4> 创建 ServletInvocableHandlerMethod 对象,并设置其相关属性
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        // <5> 创建 ModelAndViewContainer 对象,并初始其相关属性
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // <6> 创建 AsyncWebRequest 异步请求对象
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // <7> 创建 WebAsyncManager 异步请求管理器对象
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        // <8>
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        // <9> 执行调用
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        // <10>
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // <11> 获得 ModelAndView 对象
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        // <12> 标记请求完成
        webRequest.requestCompleted();
    }
}

因为,Spring MVC 提供了大量的特性,所以 HandlerAdapter 又涉及许多组件。我们主要先梳理好主流程,所以涉及的组件,还是先不详细解析。我们的目的是,看到怎么调用 HandlerMethod 方法的,即调用 Controller 的 @RequestMapping 注解的方法。

  1. 创建 ServletWebRequest 对象,包含了 request 请求和 response响应

  1. 调用 getDataBinderFactory(HandlerMethod handlerMethod) 方法,创建 WebDataBinderFactory 对象,有关于数据绑定,暂时忽略

  1. 调用 getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) 方法,创建 ModelFactory 对象,有关于往 Model 对象设置数据,暂时忽略

  1. 【核心】调用 createInvocableHandlerMethod(HandlerMethod handlerMethod) 方法,创建 ServletInvocableHandlerMethod 对象,然后设置其属性。本文会对 ServletInvocableHandlerMethod 做简单的解析。

  1. 创建 ModelAndViewContainer 对象,并初始其相关属性

  1. 创建 AsyncWebRequest 异步请求对象,暂时忽略

  1. 创建 WebAsyncManager 异步请求管理器对象,暂时忽略

  1. 异步处理,并发结果相关,暂时忽略

9、【核心】调用 ServletInvocableHandlerMethodinvokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) 方法,执行处理器,方法如下:

// ServletInvocableHandlerMethod.java
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, 
                            Object... providedArgs) throws Exception {
    // <1> 执行调用
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // <2> 设置响应状态码
    setResponseStatus(webRequest);

    // <3> 设置 ModelAndViewContainer 为请求已处理,返回,和 @ResponseStatus 注解相关
    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;
    }
    // <4> 设置 ModelAndViewContainer 为请求未处理
    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // <5> 处理返回值
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(formatErrorForReturnValue(returnValue), ex);
        }
        throw ex;
    }
}

// InvocableHandlerMethod.java
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    // <y> 解析参数
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Arguments: " + Arrays.toString(args));
    }
    // 执行调用
    return doInvoke(args);
}

// InvocableHandlerMethod.java
@Nullable
protected Object doInvoke(Object... args) throws Exception {
    // <z1> 设置方法为可访问
    ReflectionUtils.makeAccessible(getBridgedMethod());
    try {
        // <z2> 执行调用
        return getBridgedMethod().invoke(getBean(), args);
    } catch (IllegalArgumentException ex) {
        assertTargetBean(getBridgedMethod(), getBean(), args);
        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
        throw new IllegalStateException(formatInvokeError(text, args), ex);
    } catch (InvocationTargetException ex) {
        // Unwrap for HandlerExceptionResolvers ...
        Throwable targetException = ex.getTargetException();
        if (targetException instanceof RuntimeException) {
            throw (RuntimeException) targetException;
        }
        else if (targetException instanceof Error) {
            throw (Error) targetException;
        }
        else if (targetException instanceof Exception) {
            throw (Exception) targetException;
        }
        else {
            throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
        }
    }
}

可以大致过一下上面的执行逻辑,解析参数,通过反射执行方法,解析执行结果

  1. 异步处理,并发结果相关,暂时忽略

  1. 调用

getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) 方法,获得 ModelAndView 对象,方法如下:

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
        ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    // 情况一,如果 mavContainer 已处理,则返回“空”的 ModelAndView 对象。
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    // 情况二,如果 mavContainer 未处理,则基于 `mavContainer` 生成 ModelMap 对象
    ModelMap model = mavContainer.getModel();
    // 创建 ModelAndView 对象,并设置相关属性
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    // FlashMap保持处理
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}
  • 情况一,如果 mavContainer 已处理,则返回“空”的 ModelAndView 对象,@ResponseBody 注解的结果处理则直接返回 null

  • 情况二,如果 mavContainer 未处理,则基于 mavContainer 生成 ModelAndView 对象

  1. 在后续的文档分析中会讲到,注意这里的 requestHandled 属性,到时候再回过头来理解😈

  1. 标记请求完成,暂时忽略

总结

Spring MVC 通过 HandlerMapping 组件会为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)。其中处理器的实现有多种,例如通过实现 Controller 接口、HttpRequestHandler 接口,或者使用 @RequestMapping 注解将方法作为一个处理器等。这就导致 Spring MVC 无法直接执行这个处理器,所以这里需要一个处理器适配器,由它去执行处理器。

HandlerAdapter 处理器适配器对应的也有多种,那种适配器支持处理这种类型的处理器,则由该适配器去执行,如下:

  • HttpRequestHandlerAdapter:执行实现了 HttpRequestHandler 接口的处理器

  • SimpleControllerHandlerAdapter:执行实现了 Controller 接口的处理器

  • SimpleServletHandlerAdapter:执行实现了 Servlet 接口的处理器

  • RequestMappingHandlerAdapter:执行 HandlerMethod 类型的处理器,也就是通过 @RequestMapping 等注解标注的方法

这里我们重点看 RequestMappingHandlerAdapter 对象,因为这种方式是目前使用最普遍的,其他类型的 HandlerAdapter 处理器适配器做了解即可

本文讲述了 RequestMappingHandlerAdapter 处理执行器的整个流程,大致逻辑如下:

  1. 通过 ServletInvocableHandlerMethodHandlerMethod 处理器的封装)对象去执行

  1. 需要通过 HandlerMethodArgumentResolver 对象进行参数解析

  1. 通过反射执行对应的 Method 方法对象

  1. 需要通过 HandlerMethodReturnValueHandler 对象对执行结果进行处理,设置到 response 响应中,生成对应的 ModelAndView 对象

上面涉及到的三个组件分别在后续的文档中进行解析,先整体,后局部。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/367109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始学typescript

https://coding.imooc.com/learn/list/412.html 公司花钱买的&#xff0c;我边学边做笔记 设置 vscode设置 然后下个Prettier - Code formatter 以后保存就能格式化了 下载ts npm install typescript3.6.4 -g ts版本 npm install -g ts-node8.4.1 node执行ts文件 这样&a…

_linux (TCP协议通讯流程)

文章目录TCP协议通讯流程TCP 和 UDP 对比TCP协议通讯流程 下图是基于TCP协议的客户端/服务器程序的一般流程: 服务器初始化: 调用socket, 创建文件描述符;调用bind, 将当前的文件描述符和ip/port绑定在一起;如果这个端口已经被其他进程占用了, 就会bind失 败;调用listen, 声…

FPGA入门系列15--SPI(文末有易灵思核心板及配套下载线)

文章简介 本系列文章主要针对FPGA初学者编写&#xff0c;包括FPGA的模块书写、基础语法、状态机、RAM、UART、SPI、VGA、以及功能验证等。将每一个知识点作为一个章节进行讲解&#xff0c;旨在更快速的提升初学者在FPGA开发方面的能力&#xff0c;每一个章节中都有针对性的代码…

国家推进招投标全过程电子签,契约锁帮助组织减负91%

根据某工程建设集团反馈&#xff0c;电子签章的应用帮助招投标工作实现&#xff1a;“参与方5分钟内线上实名认证&#xff1b;招标、中标通知等格式文件最快2分钟完成盖章&#xff1b;标书等大体量文件20分钟内盖章生成&#xff1b;专家实名认证远程评标、10分钟完成线上开标&a…

leetcode 502. IPO(上市,3种方法)

假设leetcode 即将上市&#xff0c;如何筹集资金的问题。 有两个数组profits和capital, 分别代表第 i 个项目有多少净利润 和 需要多少启动资金。 手上的原始资金是w, 用这个w的资金去启动项目&#xff0c;完成项目之后净利润会加到w上&#xff0c;再做下一个项目&#xff0c; …

硬件原理图中的“英文缩写”大全

设计原理图时&#xff0c;网络标号要尽量简洁眀了。本文总结了一下基本的表示方法&#xff0c;供大家参考。常用控制接口 EN&#xff1a;Enable&#xff0c;使能。使芯片能够工作。要用的时候&#xff0c;就打开EN脚&#xff0c;不用的时候就关闭。有些芯片是高使能&#xff0c…

SPI机制源码:JDK Dubbo Spring

JDK 17 Dubbo 3.1.6 JDK SPI JDK SPI在sql驱动类加载、以及slf4j日志实现加载方面有具体实现。 示例 public class Test {private static final Logger logger LoggerFactory.getLogger(Test.class);public static void main(String[] args) {ServiceLoader<JdkSpiServi…

软件测试计划怎么写?模板在这呢

目录 第1章 引言 第2章 项目背景 第3章质量目标 第4章 资源需求 第5章 测试策略 第6章 测试计划 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 第1章 引言 1.1目的 简述本计划的目的&#xff0c;旨…

【THREE.JS学习(3)】使用THREEJS加载GeoJSON地图数据

本文接着系列文章&#xff08;2&#xff09;进行介绍&#xff0c;以VUE2为开发框架&#xff0c;该文涉及代码存放在HelloWorld.vue中。相较于上一篇文章对div命名class等&#xff0c;该文简洁许多。<template> <div></div> </template>接着引入核心库i…

RPC(3)--基于 Nacos 的服务发现与负载均衡版

nacos:提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。 nacos架构如下(图片来源) 依赖包&#xff1a; <dependency>…

国内领先的十大API接口排行

应用程序编程接口API即&#xff08;Application Programming Interface&#xff09;&#xff0c;现在众多企业的应用系统中常用的开放接口&#xff0c;对接相应的系统、软件功能&#xff0c;简化专业化的程序开发。 一、百度API 百度API超市开通1136个数据服务接口。 网址&a…

git 的使用方法 (下 - 远程仓库和图形化)

目录前言&#xff1a;一、什么是协同开发二、Gitee 使用协同开发1. 首先注册一个码云账号2. 新建一个仓库3. 根据下图把新建仓库设置为开源4. 在远端合并分支的方法5. 链接 git 远程6. 提交&#xff08;同步&#xff09;远程7. 远程拉取至本地8. 远程分支三、git 图形化的使用1…

ROS2手写接收IMU数据(Imu)代码并发布

目录前言接收IMU数据IMU的串口连接问题python接收串口数据python解析数据ROS2发布IMU数据可视化IMU数据效果前言 在前面测试完了单独用激光雷达建图之后&#xff0c;一直想把IMU的数据融合进去&#xff0c;由于经费的限制&#xff0c;忍痛在淘宝上买了一款便宜的IMU—GY95T&am…

实验室设计|兽医实验室设计|SICOLAB

新建兽医实验室时&#xff0c;需要考虑以下几个方面&#xff1a;&#xff08;1&#xff09;实验室建筑设计&#xff1a;实验室建筑设计应充分考虑实验室的功能需求&#xff0c;例如安全、通风、排水、电力等方面的设计&#xff0c;确保实验室内部环境的稳定和安全。&#xff08…

XX项目自动化测试方案模板,你学会了吗?

目录 1、引言 2、自动化实施目标 3、自动化技术选型 4、测试环境需求 5、人员进度安排 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 1、引言 文档版本 版本 作者 审批 备注 V1.0 Vincent XXX …

selenium环境安装及使用

selenium简介官网https://www.selenium.dev简介用于web浏览器测试的工具支持的浏览器包括IE&#xff0c;Firefox,Chrome&#xff0c;edge等使用简单&#xff0c;可使用java&#xff0c;python等多种语言编写用例脚本主要由三个工具构成&#xff0c;webdriver,IDE,web自动化环境…

【深度学习】优化器

1.什么是优化器 优化器是在深度学习的反向传播过程中&#xff0c;指引损失函数&#xff08;目标函数&#xff09;的各个参数往正确的方向更新合适的大小&#xff0c;使得更新后的各个参数让目标函数不断逼近全局最小点。 2.优化器 2-1 BGD 批量梯度下降法&#xff0c;是梯度下…

【阿旭机器学习实战】【33】中文文本分类之情感分析--朴素贝叶斯、KNN、逻辑回归

【阿旭机器学习实战】系列文章主要介绍机器学习的各种算法模型及其实战案例&#xff0c;欢迎点赞&#xff0c;关注共同学习交流。 目录1.查看原始数据结构2.导入数据并进行数据处理2.1 提取数据与标签2.2 过滤停用词2.3 TfidfVectorizer将文本向量化3.利用不同模型进行训练与评…

如何使用HTTPS加密保护网站?

加密 Web 内容并不是什么新鲜事&#xff1a;自发布通过SSL/TLS协议来加密 Web 内容的规范以来&#xff0c;已经过去了近 20 年。然而&#xff0c;近年来&#xff0c;运行安全的HTTPS加密 Web 服务器已经从一种选择变成了一种安全防护的必需品。攻击者继续寻找并找到窃取用户和W…

计算机网络概述 第二部分

5.网络分层 ①OSI 7层模型 数据链路层 (Data Link Layer) 实现相邻&#xff08;Neighboring&#xff09;网络实体间的数据传输 成帧&#xff08;Framing&#xff09;&#xff1a;从物理层的比特流中提取出完整的帧 错误检测与纠正&#xff1a;为提供可靠数据通信提供可能 …