• 16.springmvc工作原理分析


    springmvc 如何做URL映射关系

    1.SpringIOC容器加载时开始遍历所有的bean对象 判断 bean对象 类上是否有加上

    @Controller注解,如果类上有加该注解的话 则该类就是为我们控制类;

    2.在容器初始化时会建立所有url和controller的对应关系,利用java反射机制,查找该控制类中所有方法,判断方法上是否有加上@RequestMapping注解,如果有加上该注解的话 保存到Map中(key=/mayiktDemo01,MayiktController#mayiktDemo01())

    3.定义DispatcherServlet 接受客户端所有请求 从该map集合中查找到具体的控制类 方法 调用(java反射机制调用)

    springmvc 请求映射原理

    SpringMVC 前端控制器 DispatcherServlet

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        // Handler 责任链
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            
            try {
                // 判断客户端发送的请求是否是上传文件
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                
                //根据请求路径  获取对应的Handler责任链(处理器)
                mappedHandler = getHandler(processedRequest);// 如果没有查找到的情况下 则返回404
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                //根据对应的Handler 获取对应的适配器
    
              HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
               //根据适配器执行请求方法
               //mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    责任链设计模式 具体流程

    springmvc 拦截器

    拦截器A

    拦截器B

    拦截器C

    public String demo01() {

    }

    1、用户向服务器发送请求,请求会先达到 SpringMVC 前端控制器 DispatcherServlet ;

    2、DispatcherServlet 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 执行链对象的形式返回

    3、DispatcherServlet 根据获得的 Handler,获取对应的 HandlerAdapter;

    5、获取到 HandlerAdapter,将开始执行拦截器的 preHandler(…)方法;

    6、提取 Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller)方法,处理请求,在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:

    HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的类型信息

    数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等

    数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等

    数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中

    7、Handler 方法执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。

    8、开始执行拦截器的 postHandle(…)方

    9、根据返回的 ModelAndView(此时会判断是否存在异常:如果存在异常,则执行 HandlerExceptionResolver 进行异常处理)选择一个适合的 ViewResolver 进行视图解析,根据 Model 和 View,来渲染视图

    10、渲染视图完毕执行拦截器的 afterCompletion(…)方法

    11、将渲染结果返回给客户端

    HandlerMappings

    1.实现Controller方式所使用的适配器

    2.实现HTTP请求处理器的适配器:HttpRequestHandlerAdapter

    3.注解方式(@Controller)的处理器适配器:

    4.实现servlet方式的适配器:

    HandlerMapping 负责解析请求URL

    HandlerMapping的分类

    SpingMVC中的HandlerMapping负责解析请求URL,对应到Handler进行处理(这里的Handler一般为Controller里的一个方法method,也可以为servlet或者Controller等)

    1.RequestMappingHandlerMapping 会将Controller中配置@RequestMapping注解的方法做映射存储 url 具体 控制类和方法(map=key=url ,控制类和方法)

    2.BeanNameUrlHandlerMapping 根据bean标签的名称找到对应的Controller类

     <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
     <bean id="/baanNameMayikt" class="com.mayikt.controller.BeanNameController" />    
    
    • 1
    • 2

    3.RouterFunctionMapping 函数式接口

    img

    1.RequestMappingHandlerMapping 会将Controller中配置@RequestMapping注解的方法做映射存储 走AbstractHandlerMethodMapping#

    protected void initHandlerMethods() {
    		for (String beanName : getCandidateBeanNames()) {
    			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
    				processCandidateBean(beanName);
    			}
    		}
    		handlerMethodsInitialized(getHandlerMethods());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping

    获取注解映射url 存放在this.handlerMap.put(urlPath, resolvedHandler);

    protected void detectHandlers() throws BeansException {
    		ApplicationContext applicationContext = obtainApplicationContext();
    		String[] beanNames = (this.detectHandlersInAncestorContexts ?
    				BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
    				applicationContext.getBeanNamesForType(Object.class));
    
    		// Take any bean name that we can determine URLs for.
    		for (String beanName : beanNames) {
    			String[] urls = determineUrlsForHandler(beanName);
    			if (!ObjectUtils.isEmpty(urls)) {
    				// URL paths found: Let's consider it a handler.
    				registerHandler(urls, beanName);
    			}
    		}
    
    		if ((logger.isDebugEnabled() && !getHandlerMap().isEmpty()) || logger.isTraceEnabled()) {
    			logger.debug("Detected " + getHandlerMap().size() + " mappings in " + formatMappingName());
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    如果我们是使用@Component(“/implController”) 自定义url映射 走的

    BeanNameUrlHandlerMapping

    org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#lookupHandler this.handlerMap.get(urlPath); 查找对应的 控制类

    img

    如果使用@RequestMapping()

    org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal 查找具体url

    img

    因为springmvc支持多种url映射方式 ,每一种url映射的方式 都有自己独立的

    handlerMapping 处理的方式。

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    查找具体HandlerMapping

    根据url请求地址查找具体的HandlerMapping

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
                // 循环查找对应的handlerMapping
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注册HandlerMappings

    	protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
            // 初始化HandlerMappings
    		initHandlerMappings(context);
            // 初始化HandlerAdapters
    		initHandlerAdapters(context);
            
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    从DispatcherServlet.properties中获取HandlerMapping

    org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    	org.springframework.web.servlet.function.support.RouterFunctionMapping
    
    • 1
    • 2
    • 3

    使用反射初始化HandlerMapping 同时注册到handlerMappings 集合中。

    springmvc适配器简单介绍

    1.实现Controller接口方式所使用的适配器:SimpleControllerHandlerAdapter

    2.实现HTTP请求处理器的适配器:HttpRequestHandlerAdapter

    3.注解方式(@Controller)的处理器适配器:RequestMappingHandlerAdapter

    4.实现servlet方式的适配器:SimpleServletHandlerAdapter

    5.自己开发者新增适配器

    方式一:@Controller/@RequestMapping

    方式二:实现HttpRequestHandler接口

    方式三:实现Controller接口

    适配器模式就是把一个类的接口替换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

    public static void invok(Controller controller) {
          // 判断请求url 如果是 使用Controller 该方式查找具体方法执行
            if (controller instanceof AnnotationController) {
                AnnotationController annotationController = (AnnotationController) controller;
                annotationController.requestMapping();
                return;
            }
            if (controller instanceof ImplController) {
                ImplController implController = (ImplController) controller;
                implController.handleRequest();
            }
            if (controller instanceof RequestHandlerController) {
                RequestHandlerController requestHandlerController = (RequestHandlerController) controller;
                requestHandlerController.requestHandler();
            }
    
            
        }
    
        public static void main(String[] args) {
            AdapterInvok.invok(new ImplController());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    springmvc适配器模式源码解读

    获取HandlerAdapter源码解读

    // 根据handler获取具体的HandlerAdapter

    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    //根据 handler 获取对应的HandlerAdapter

    	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    		if (this.handlerAdapters != null) {
    			for (HandlerAdapter adapter : this.handlerAdapters) {
                    //判断该handler是否是为该HandlerAdapter 是则返回
                    //该HandlerAdapter
    				if (adapter.supports(handler)) {
    					return adapter;
    				}
    			}
    		}
    		throw new ServletException("No adapter for handler [" + handler +
    				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用实现controller接口的方式 SimpleControllerHandlerAdapter

    执行我们的 handleRequest

    @Component("/implController")
    public class MayiktImplController implements Controller {
         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            PrintWriter writer = response.getWriter();
            writer.print("mayikt--MayiktImplController");
            writer.close();
            return null;
        }
    public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    
    	@Override
    	public boolean supports(Object handler) {
    		return (handler instanceof Controller);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    执行// Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    请求的方法
    
    • 1
    • 2
    • 3
    直接利用 多态机制 访问handleRequest(HttpServletRequest reques  也就是没有使用反射调用我们的方法。
    
    • 1

    使用到我们的RequestMapping(“/mayiktDemo02”)

    使用org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports

    	public final boolean supports(Object handler) {
    		return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    	}
    
    • 1
    • 2
    • 3

    得到bean和方法使用反射机制 调用 请求方法

    	protected Object doInvoke(Object... args) throws Exception {
    		ReflectionUtils.makeAccessible(getBridgedMethod());
    		try {
    			return getBridgedMethod().invoke(getBean(), args);
    
    • 1
    • 2
    • 3
    • 4

    初始化HandlerAdapter源码解读

    // 初始化HandlerAdapters

    	protected void initStrategies(ApplicationContext context) {
    		initMultipartResolver(context);
    		initLocaleResolver(context);
    		initThemeResolver(context);
    		initHandlerMappings(context);
            // 初始化HandlerAdapters
    		initHandlerAdapters(context);
    		initHandlerExceptionResolvers(context);
    		initRequestToViewNameTranslator(context);
    		initViewResolvers(context);
    		initFlashMapManager(context);
    	}
    
    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.
    		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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    使用反射机制初始化适配器模式

    	// Ensure we have at least some HandlerAdapters, by registering
    		// default HandlerAdapters if no other adapters are found.
    		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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    读取配置DispatcherServlet.properties 文件img

    使用java反射机制初始化适配器类

    protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    		String key = strategyInterface.getName();
            //读取配置DispatcherServlet.properties 文件
    		String value = defaultStrategies.getProperty(key);
    		if (value != null) {
    			String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
    			List<T> strategies = new ArrayList<>(classNames.length);
    			for (String className : classNames) {
    				try {
                        //使用java反射机制初始化适配器类
    					Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
    					Object strategy = createDefaultStrategy(context, clazz);
    					strategies.add((T) strategy);
    				}
    				catch (ClassNotFoundException ex) {
    					throw new BeanInitializationException(
    							"Could not find DispatcherServlet's default strategy class [" + className +
    							"] for interface [" + key + "]", ex);
    				}
    				catch (LinkageError err) {
    					throw new BeanInitializationException(
    							"Unresolvable class definition for DispatcherServlet's default strategy class [" +
    							className + "] for interface [" + key + "]", err);
    				}
    			}
    			return strategies;
    		}
    		else {
    			return new LinkedList<>();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    根据Handler查找对应的适配器源码解读

    根据Handler查找对应的适配器源码解读

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    		if (this.handlerAdapters != null) {
    			for (HandlerAdapter adapter : this.handlerAdapters) {
                    //根据Handler查找对应的适配器源码解读
    				if (adapter.supports(handler)) {
    					return adapter;
    				}
    			}
    		}
    		throw new ServletException("No adapter for handler [" + handler +
    				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    img

    循环遍历每个适配器 判断该 handler 查找匹配对应的HandlerAdapter

    1.如果是RequestMappingHandlerAdapter 则执行AbstractHandlerMethodAdapter#supports方法

    注解方式(@Controller)的处理器适配器:RequestMappingHandlerAdapter

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

    2.HttpRequestHandlerAdapter#supports方法

    	public boolean supports(Object handler) {
    		return (handler instanceof HttpRequestHandler);
    	}
    
    • 1
    • 2
    • 3

    3.SimpleControllerHandlerAdapter#supports方法

    	@Override
    	public boolean supports(Object handler) {
    		return (handler instanceof Controller);
    	}
    
    • 1
    • 2
    • 3
    • 4

    4.SimpleServletHandlerAdapter#supports方法

    	public boolean supports(Object handler) {
    		return (handler instanceof Servlet);
    	}
    
    • 1
    • 2
    • 3

    执行对应的ha.handle方法

    执行调用对应的ha.handle方法

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    • 1

    执行对应适配器的handle方法

    img

    RequestMappingHandlerAdapter

    如果是RequestMappingHandlerAdapter 则执行AbstractHandlerMethodAdapter#handle方法 使用java反射机制调用控制层方法

    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 {
    			// //使用java反射机制调用 方法
    			mav = invokeHandlerMethod(request, response, handlerMethod);
    		}
    
    		if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    			if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    				applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    			}
    			else {
    				prepareResponse(response);
    			}
    		}
    
    		return mav;
    	}
    	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
            // 拼接url上参数
    		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    		if (logger.isTraceEnabled()) {
    			logger.trace("Arguments: " + Arrays.toString(args));
    		}
            //使用反射调用方法
    		return doInvoke(args);
    	}
          protected Object doInvoke(Object... args) throws Exception {
    		ReflectionUtils.makeAccessible(getBridgedMethod());
    		try {
                //使用反射调用方法
    			return getBridgedMethod().invoke(getBean(), args);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    如果是RequestMappingHandlerAdapter 则执行

    img

    RequestMappingHandlerAdapter

    如果是实现了controller接口,会执行SimpleControllerHandlerAdapter

    #handle方法 ,执行到 controller接口对应的实现类

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

    img

    MayiktImplController#handleRequest

    package com.mayikt.controller;
    
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.mvc.Controller;
    import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.PrintWriter;
    import java.util.HashMap;
    
    /**
    * @author songchuanfu
    * @qq 1802284273
    */
    @Component("/implController")
    public class MayiktImplController implements Controller {
        @Override
        public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
            PrintWriter writer = response.getWriter();
            writer.print("mayikt--MayiktImplController");
            writer.close();
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    微信小程序案例2-3:婚礼邀请函
    44--Django-项目实战-全栈开发-基于django+drf+vue+elementUI企业级项目开发流程-支付宝二次封装、支付成功页面以及后台设计
    【Web前端笔记14】函数与对象
    图像分类(二) 全面解读复现ZFNet
    类型安全的 Go HTTP 请求
    Android 基础知识3-4 Activity的声明周期
    基于C++的图像增强处理
    超声波传感器(CHx01) 学习笔记 Ⅴ- 参数配置
    传统 Web 框架部署与迁移
    依赖倒置原则(Dependence Inversion Principle)
  • 原文地址:https://blog.csdn.net/u014001523/article/details/126415958