SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

本文主要是介绍SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 这样的方式来执行request的handler方法。

  先来分析一下ha.handle方法的调用过程:HandlerAdapter接口有一个抽象实现类AbstractHandlerMethodAdapter,在该抽象类中通过具体方法handle调用抽象方法handleInternal:

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {


    private int order = Ordered.LOWEST_PRECEDENCE;
        @Override
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }
        //抽象方法,由具体的Adapter实现
        protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;


}

  RequestMappingHandlerAdapter类继承了抽象类AbstractHandlerMethodAdapter,实现了抽象方法 handleInternal,下面看看handleInternal方法的具体实现(需要注意,handler方法在synchronizeOnSession为true的情况下会放在同步代码块中进行执行):

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
    
    //省略若干代码...
    
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {


        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            // Always prevent caching in case of session attribute management.
            checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
        }
        else {
            // Uses configured default cacheSeconds setting.
            checkAndPrepare(request, response, true);
        }


        // Execute invokeHandlerMethod in synchronized block if required.
        /*
         * synchronizeOnSession默认为false,如果其为true,那么request对于的handler将会被放置在同步代码块
         * 中进行执行。问题:什么时候???通过怎样的方式将synchronizeOnSession设置为true???
         */
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    return invokeHandleMethod(request, response, handlerMethod);
                }
            }
        }
        // 不在同步块中执行handler方法
        return invokeHandleMethod(request, response, handlerMethod);
    }   
}

 现在就分析上面代码块中的 invokeHandleMethod(request, response, handlerMethod) 方法的执行流程,看看在调用handler前后又完成了什么工作,同时分析出@ModelAttribute的作用。先来总体看看,然后再各个部分分别做 分析,一共分为6个步骤(step1 ~ step6):

/**
 * Invoke the @RequestMapping handler method preparing a @ModelAndView
 * if view resolution is required.
 */
private ModelAndView invokeHandleMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {


    ServletWebRequest webRequest = new ServletWebRequest(request, response);


    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);


    //step 1
    //新建一个mavContainer,用于存放所有可能会用到的ModelAndView
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    
    //step 2
    /*
     * Attributes can be set two ways. The servlet container may set attributes
     * to make available custom information about a request. For example, for
     * requests made using HTTPS, the attribute
     * <code>javax.servlet.request.X509Certificate</code> can be used to
     * retrieve information on the certificate of the client. Attributes can
     * also be set programatically using {@link ServletRequest#setAttribute}.
     * This allows information to be embedded into a request before a
     * {@link RequestDispatcher} call.
     *
     * RequestContextUtils.getInputFlashMap(request)可以获取到request中的attribute,
     * 并且将所有的request中的attribute放置在mavContainer中
     */
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    
    //step 3
    /*
     * 会在这个方法里将所有标注了@ModelAttribute的方法调用一遍,并且将该方法相关的ModelAndView放入到mavContainer中:
     * 1、最常见的就是@ModelAttribute标注的方法入参中有Map,Model
     * 2、如果方法有返回值,那么也会结果处理后放入到mavContainer中(是否只有ModelAndView类型的返回值才能放,有待考察??)
     */
    modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);


    //step 4
    //许多和 asyncManager 相关的东西,这个貌似和拦截器有关。
    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);


    final WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);


    if (asyncManager.hasConcurrentResult()) {
        Object result = asyncManager.getConcurrentResult();
        mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
        asyncManager.clearConcurrentResult();


        if (logger.isDebugEnabled()) {
            logger.debug("Found concurrent result value [" + result + "]");
        }
        requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
    }
    
    //step 5
    /*
     * Invokes the method and handles the return value through one of the configured {@link HandlerMethodReturnValueHandler}s.
     * 在这里调用handler方法
     */
    requestMappingMethod.invokeAndHandle(webRequest, mavContainer);


    //step 6
    //要么返回ModelAndView,要么返回null
    if (asyncManager.isConcurrentHandlingStarted()) {
        return null;
    }
    return getModelAndView(mavContainer, modelFactory, webRequest);
}

 下面从step1~step6逐一的进行分析。

 

  step1:ModelAndViewContainer mavContainer = new ModelAndViewContainer();

  首先看一下ModelMap是如何定义的,这对理解ModelAndViewContainer以及后面的代码有帮助,主要是理解:ModelMap实际上就是一个LinkedHashMap,而且这个Map的”值“是Object类型,能够放置所有类型的Java对象

/**
 * 可以看出ModelMap实际上就是一个LinkedHashMap,且“值”为超类Object类型
 * 能够放置所有的Java对象
 */
public class ModelMap extends LinkedHashMap<String, Object> {
    public ModelMap() {
    }


    // 调用这个构造函数之前会先调用其父类构造函数,得到一个Map对象
    public ModelMap(String attributeName, Object attributeValue) {
        addAttribute(attributeName, attributeValue);
    }
    
    // 就是将attributeValue对象放置到Map末尾,同时指定键值为attributeName
    public ModelMap addAttribute(String attributeName, Object attributeValue) {
        put(attributeName, attributeValue);
        return this;
    }
    
    // attributes是一个Map集合;所谓merge无非是将attributes这个集合放置到现有集合的末尾
    public ModelMap mergeAttributes(Map<String, ?> attributes) {
        if (attributes != null) {
            for (Map.Entry<String, ?> entry : attributes.entrySet()) {
                String key = entry.getKey();
                if (!containsKey(key)) {
                    put(key, entry.getValue());
                }
            }
        }
        return this;
    }
    
    //省略一些方法的定义...
}

  再来看ModelAndViewContainer到底是个什么东西,从下面的ModelAndViewContainer定义中不难理解其含有两个ModelMap对象defaultModel和redirectModel默认情况下使用defaultModel,也可以通过其方法设置使用redirectModel。

/**
 * 定义了两个ModelMap对象:defaultModel和redirectModel,实际上也就是两个Map<String, Object>
 * 其中defaultModel已经完成了初始化。默认使用defaultModel。
 * 也可以通过其中的方法来设置使用redirectModel,这个是方便移植和使用其它框架而设定的。
 */
public class ModelAndViewContainer {


    private boolean ignoreDefaultModelOnRedirect = false;
  // view的用法值得去探究
    private Object view;
    
    /* BindingAwareModelMap实际上也就是一个Map,
     * 看看定义 public class BindingAwareModelMap extends ModelMap implements Model
     *
     * 从这里可以看出,ModelAndViewContainer对象都有一个默认的ModelMap
     */
    private final ModelMap defaultModel = new BindingAwareModelMap();


    // 这个为方便移植其它框架的Model而设置的,SpringMVC本身使用的是defaultModel
    private ModelMap redirectModel;


    private boolean redirectModelScenario = false;


    private final SessionStatus sessionStatus = new SimpleSessionStatus();


    private boolean requestHandled = false;
    
    /**
     * Set a view name to be resolved by the DispatcherServlet via a ViewResolver.
     * Will override any pre-existing view name or View.
     */
    public void setViewName(String viewName) {
        this.view = viewName;
    }
    
    /**
     * 返回"default" 或者是 "redirect" 模型,具体根据redirectModelScenario等
     * 属性的值来确定(具体用法参看javadoc)
     */
    public ModelMap getModel() {
        if (useDefaultModel()) {
            return this.defaultModel;
        }
        else {
            return (this.redirectModel != null) ? this.redirectModel : new ModelMap();
        }
    }
    
    // 返回默认的Model,而不考虑其它的属性值如何
    public ModelMap getDefaultModel() {
        return this.defaultModel;
    }
    
}

 到现在为止,step1完成的工作已经分析完全。

 

  step2:  mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request))

/* * 
 * 检索request域中的attribute,并将其放置在mavContainer末尾
 *
 * RequestContextUtils.getInputFlashMap(request)会调用HttpServletRequest.getAttribute方法。
 * 可以获取到request中的attribute属性。这个attribute就是我们属性的attribute,它可以通过两
 * 种方式来设置:①servlet容器为request设置的;②通过ServletRequest.setAttribute方法来设置。
 * 
 */
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));

step3modelFactory.initModel(webRequest, mavContainer, requestMappingMethod)

  这个方法的作用在javadoc中描述得很清楚:

  Populate the model in the following order:

 

  1. Retrieve "known" session attributes listed as @SessionAttributes.
  2. Invoke @ModelAttribute methods
  3. Find @ModelAttribute method arguments also listed as @SessionAttributes and ensure they're present in the model raising an exception if necessary.

 

  也就是说初始化模型的时候会按顺序完成三件事情

  ①、检索现有的session域中的attributes,并将其放置于mavContainer末尾;

  ②、调用所有@ModelAttribute注解标注的方法;

  ③、找出handler方法中使用@ModelAttribute注解修饰的入参(主要是@ModelAttribute指定的value属性值,如果没有指定则是类名第一个字母小写得到,我们假定它为V),如果V同时被@SessionAttributes的value属性值指定,那么就必须保证在此时的mavContainer中必须含有”key“为V的对象。如果没有,则会抛出一个异常

  

  带着这个印象我们分析代码就会容易很多:

/*
 * 会在这个方法里将所有标注了@ModelAttribute的方法调用一遍,并且将该方法相关的ModelAndView放入到mavContainer中:
 * 1、最常见的就是@ModelAttribute标注的方法入参中有Map,Model
 * 2、如果方法有返回值,那么也会结果处理后放入到mavContainer中(是否只有ModelAndView类型的返回值才能放,有待考察??)
 */
modelFactory.initModel(webRequest, mavContainer, requestMappingMethod){
    //完成①:检索现有的session域中的attributes,并将其放置于mavContainer末尾
    Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    mavContainer.mergeAttributes(sessionAttributes);


    //完成②:将所有的@ModelAttribute标注的方法都调用一遍。调用完了以后,将调用方法的结果放置到mavContainer中
    invokeModelAttributeMethods(request, mavContainer){
        //modelMethods中包含了所有@ModelAttribute标注的方法,在这个while循环中将会把所有@ModelAttribute标注
        //的方法都调用一遍
        while (!this.modelMethods.isEmpty()) {
            InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod();
            String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
            //如果,mavContainer中已经包含有modelName名的attribute,那么,将不会调用@ModelAttribute标注的方法
            if (mavContainer.containsAttribute(modelName)) {
                continue;
            }


            //真正的调用@ModelAttribute标注的方法
            Object returnValue = attrMethod.invokeForRequest(request, mavContainer);
            
            //如果@ModelAttribute标注的方法不是void类型,则将其返回结果转换成returnValueName;如果mavContainer中
            //没有包含returnValueName,则将方法返回的结果放置到mavContainer中。
            if (!attrMethod.isVoid()){
                String returnValueName = getNameForReturnValue(returnValue, attrMethod.getReturnType());
                if (!mavContainer.containsAttribute(returnValueName)) {
                    mavContainer.addAttribute(returnValueName, returnValue);
                }
            }
        }
    };    
    


     // 完成③:
     
     /*
     * 找出handler方法中使用@ModelAttribute注解修饰的入参(主要是@ModelAttribute指定的value属性值,
     * 如果没有指定则是类名第一个字母小写得到,我们假定它为V),如果V同时被@SessionAttributes的value
     * 属性值指定,则将这样的V放入到nameList中。
     *
     */
    List<String> nameList = findSessionAttributeArguments(handlerMethod){
        List<String> result = new ArrayList<String>();
        //遍历处理方法的所有参数
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            //如果处理方法有@ModelAttribute标注
            if (parameter.hasParameterAnnotation(ModelAttribute.class)) {
                //得到参数的String型的名字
                String name = getNameForParameter(parameter);
                //如果在处理方法所在的类定义处使用了@SessionAttributes注解,那么就将该参数放入到List中
                if (this.sessionAttributesHandler.isHandlerSessionAttribute(name, parameter.getParameterType())) {
                    result.add(name);
                }
            }
        }
        return result;
    };
    
    // 保证nameList中记录的V必须在mavContainer中存在,如果不存在则会抛出异常
    for (String name : nameList) {
        //如果mavContainer中没有包含有name名字的attribute,那么再一次检查request中是否包含了name名字的attribute
        if (!mavContainer.containsAttribute(name)) {
            //再一次检查request中是否包含了name名字的attribute
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            //如果没有检测到,则会抛出一个异常
            if (value == null) {
                    throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");
            }
            //如果检测到了,那么会将这个attribute放入到mavContainer中
            mavContainer.addAttribute(name, value);
        }
    }
};

 step4: 暂时不做分析...

 

  step5调用handler方法同时处理返回结果

复制代码
  1 requestMappingMethod.invokeAndHandle(webRequest, mavContainer){
  2         //一、 调用处理方法,并的到返回结果
 3 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs){  4 //为调用处理方法准备参数  5 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs){  6  7 //...  8  9 //从这里可以看出@ModelAttribute能够修饰handler的入参  10 String name = ModelFactory.getNameForParameter(parameter){  11 ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);  12 //如果当前参数使用了@ModelAttribute标注,则获取到该标签的value属性值  13 String attrName = (annot != null) ? annot.value() : null;  14 //如果attrName有text,则返回该attrName值,也就是@ModelAttribute的value属性值。  15 //反之,则返回参数类型第一个字母小写后得到的字符串  16 return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);  17  };  18  19 //检测mavContainer中是否包含有name关键字的ModelAndView,如果包含有,则获取它并返回。如果没有,则创建一个attribute  20 Object attribute = (mavContainer.containsAttribute(name) ?  21  mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));  22  23 //对于WebDataBinder而言,最重要的参数就是name和attribute  24 //将请求域中表单数据绑定到上面的到的attribute中:request域中有的就重新赋值,没有的就保持原有的属性值不变。  25 /*  26  * 结合前面name和attribute的获取过程可以分析出request表单数据绑定的过程:  27  * ①、如果handler的入参使用了@ModelAttribute,同时还指定了其value属性值,那么attrName就是其value属性值;  28  * ②、如果handler的入参处没有指定@ModelAttribute的value属性值,或者是根本就没有使用该注解,那么其  29  * attrName就是参数类名第一个字母小写的到;  30  * ③、搜索mavContainer中是否有键值为attrName的attribute对象,如果有,则将这个对象作为表单数据绑定的对象;  31  * 如果没有,则新创建一个作为数据绑定的对象;  32 */  33 WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);  34 if (binder.getTarget() != null) {  35 //绑定参数  36  bindRequestParameters(binder, webRequest);  37  validateIfApplicable(binder, parameter);  38 if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {  39 throw new BindException(binder.getBindingResult());  40  }  41  }  42  43 // Add resolved attribute and BindingResult at the end of the model  44 Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();  45 //删除原有的attribute  46  mavContainer.removeAttributes(bindingResultModel);  47 //将处理过的attribute添加到末尾,这样mavContainer中相对应的attribute就是更新以后的attribute  48  mavContainer.addAllAttributes(bindingResultModel);  49  50  51 //返回需要格式的args  52 return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);  53  };  54  55 //**真正的调用处理方法,此时的args是依据request表单数据更新过的args  56 Object returnValue = doInvoke(args);  57  58 //返回处理方法返回的结果,这个结果将会进一步处理  59 return returnValue;  60  };  61  62 //二、处理目标方法的返回结果  63 //设置应答状态  64  setResponseStatus(webRequest);  65  66 if (returnValue == null) {  67 if (isRequestNotModified(webRequest) || hasResponseStatus() || mavContainer.isRequestHandled()) {  68 mavContainer.setRequestHandled(true);  69 return;  70  }  71  }  72 else if (StringUtils.hasText(this.responseReason)) {  73 mavContainer.setRequestHandled(true);  74 return;  75  }  76  77 mavContainer.setRequestHandled(false);  78 try {  79 //处理返回结果  80 this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest){  81 //获取返回结果对应的处理方法  82 HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);  83 Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]"); 84 //利用获取到的结果处理方法来处理结果 85 handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest){ 86 if (returnValue == null) { 87 return; 88 } 89 else if (returnValue instanceof String) { 90 String viewName = (String) returnValue; 91 mavContainer.setViewName(viewName); 92 if (isRedirectViewName(viewName)) { 93 mavContainer.setRedirectModelScenario(true); 94 } 95 } 96 else { 97 // should not happen 98 throw new UnsupportedOperationException("Unexpected return type: " + 99 returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); 100 } 101 }; 102 }; 103 } 104 105 };
复制代码

  从上面的代码分析可以得出表单数据绑定的流程:

  1、request的表单数据绑定首先需要创建一个WebDataBinder对象: binder = binderFactory.createBinder(webRequest, attribute, name)。表单数据在webRequest中,还要确定两个关键的参数:attribute【Object类型】, name【String类型】。

  2、确定name(也就是attrName):

    ①、如果handler的入参处使用了@ModelAttribute注解,同时该注解还制定了value属性值,那么name就是value的属性值;

    ②、如果handler的入参数使用了@ModelAttribute注解,但是没有指定value属性值;或者是,入参处根本就没有使用@ModelAttribute注解;那么这2种情况下其name值就是handler入参类名第一个字母小写得到的String;

  3、确定attribute:

    ①、查看mavContainer中是否包含有key=name的attribute对象(mavContainer.getModel()实际上得到的是一个Map<String, Object>)。如果有,则attribute就是该对象;如果没有,则新创建一个对象赋给attribute。其代码如下:

    Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

  4、通过已经确定好的attribute和name就能够完成数据的绑定了。

 

  step6: 返回ModelAndView

1 if (asyncManager.isConcurrentHandlingStarted()) {
2     return null;
3 }
4 
5 return getModelAndView(mavContainer, modelFactory, webRequest);

 

 

  返回有两种情况,第一个貌似和同步机制有关,asyncManager的工作机制后续继续分析。这里主要是分析getModelAndView(mavContainer, modelFactory, webRequest)方法的源码。

复制代码
 1 private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
 2         ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
 3 
 4     modelFactory.updateModel(webRequest, mavContainer);
 5     if (mavContainer.isRequestHandled()) {
 6         return null;
 7     }
 8     ModelMap model = mavContainer.getModel();
 9     ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model);
10     if (!mavContainer.isViewReference()) {
11         mav.setView((View) mavContainer.getView());
12     }
13     if (model instanceof RedirectAttributes) {
14         Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
15         HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
16         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
17     }
18     return mav;
19 }
复制代码

 

  视图模型是一个大的主题,后面再仔细分析。


这篇关于SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/973873

相关文章

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

Java五子棋之坐标校正

上篇针对了Java项目中的解构思维,在这篇内容中我们不妨从整体项目中拆解拿出一个非常重要的五子棋逻辑实现:坐标校正,我们如何使漫无目的鼠标点击变得有序化和可控化呢? 目录 一、从鼠标监听到获取坐标 1.MouseListener和MouseAdapter 2.mousePressed方法 二、坐标校正的具体实现方法 1.关于fillOval方法 2.坐标获取 3.坐标转换 4.坐

Spring Cloud:构建分布式系统的利器

引言 在当今的云计算和微服务架构时代,构建高效、可靠的分布式系统成为软件开发的重要任务。Spring Cloud 提供了一套完整的解决方案,帮助开发者快速构建分布式系统中的一些常见模式(例如配置管理、服务发现、断路器等)。本文将探讨 Spring Cloud 的定义、核心组件、应用场景以及未来的发展趋势。 什么是 Spring Cloud Spring Cloud 是一个基于 Spring

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

java8的新特性之一(Java Lambda表达式)

1:Java8的新特性 Lambda 表达式: 允许以更简洁的方式表示匿名函数(或称为闭包)。可以将Lambda表达式作为参数传递给方法或赋值给函数式接口类型的变量。 Stream API: 提供了一种处理集合数据的流式处理方式,支持函数式编程风格。 允许以声明性方式处理数据集合(如List、Set等)。提供了一系列操作,如map、filter、reduce等,以支持复杂的查询和转

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

电脑不小心删除的文件怎么恢复?4个必备恢复方法!

“刚刚在对电脑里的某些垃圾文件进行清理时,我一不小心误删了比较重要的数据。这些误删的数据还有机会恢复吗?希望大家帮帮我,非常感谢!” 在这个数字化飞速发展的时代,电脑早已成为我们日常生活和工作中不可或缺的一部分。然而,就像生活中的小插曲一样,有时我们可能会在不经意间犯下一些小错误,比如不小心删除了重要的文件。 当那份文件消失在眼前,仿佛被时间吞噬,我们不禁会心生焦虑。但别担心,就像每个问题

详细分析Springmvc中的@ModelAttribute基本知识(附Demo)

目录 前言1. 注解用法1.1 方法参数1.2 方法1.3 类 2. 注解场景2.1 表单参数2.2 AJAX请求2.3 文件上传 3. 实战4. 总结 前言 将请求参数绑定到模型对象上,或者在请求处理之前添加模型属性 可以在方法参数、方法或者类上使用 一般适用这几种场景: 表单处理:通过 @ModelAttribute 将表单数据绑定到模型对象上预处理逻辑:在请求处理之前