• SpringBoot2应用及其底层源码学习(二)(转自尚硅谷)


    37、响应处理-【源码分析】-ReturnValueHandler原理

    在这里插入图片描述

    假设给前端自动返回json数据,需要引入相关的依赖

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jsonartifactId>
        <version>2.3.4.RELEASEversion>
        <scope>compilescope>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    控制层代码如下:

    @Controller
    public class ResponseTestController {
        
    	@ResponseBody  //利用返回值处理器里面的消息转换器进行处理
        @GetMapping(value = "/test/person")
        public Person getPerson(){
            Person person = new Person();
            person.setAge(28);
            person.setBirth(new Date());
            person.setUserName("zhangsan");
            return person;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    32、请求处理-【源码分析】-各种类型参数解析原理 - 返回值处理器有讨论ReturnValueHandler。现在直接看看重点:

    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
    		implements BeanFactoryAware, InitializingBean {
    
        ...
        
    	@Nullable
    	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    		ServletWebRequest webRequest = new ServletWebRequest(request, response);
    		try {
    			
                ...
                
                ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
                    
    			if (this.argumentResolvers != null) {
    				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    			}
    			if (this.returnValueHandlers != null) {//<----关注点
    				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    			}
    
                ...
    
    			invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return null;
    			}
    
    			return getModelAndView(mavContainer, modelFactory, webRequest);
    		}
    		finally {
    			webRequest.requestCompleted();
    		}
    	}
    
    • 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
    public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
        
    	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
    
    		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    		
            ...
            
    		try {
                //看下块代码
    			this.returnValueHandlers.handleReturnValue(
    					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    		}
    		catch (Exception ex) {
    			...
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
        
        ...
        
    	@Override
    	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    
            //selectHandler()实现在下面
    		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    		if (handler == null) {
    			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    		}
            //开始处理
    		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    	}
        
       	@Nullable
    	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
    			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
    				continue;
    			}
    			if (handler.supportsReturnType(returnType)) {
    				return handler;
    			}
    		}
    		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
    • 27
    • 28
    • 29
    • 30
    • 31

    @ResponseBody 注解,即RequestResponseBodyMethodProcessor,它实现HandlerMethodReturnValueHandler接口

    public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    
        ...
        
    	@Override
    	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
    			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
    		mavContainer.setRequestHandled(true);
    		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    
            // 使用消息转换器进行写出操作,本方法下一章节介绍:
    		// Try even with null return value. ResponseBodyAdvice could get involved.
    		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    38、响应处理-【源码分析】-HTTPMessageConverter原理

    返回值处理器ReturnValueHandler原理:

    1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
    2. 返回值处理器调用 handleReturnValue 进行处理
    3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
      • 利用 MessageConverters 进行处理 将数据写为json
        1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
        2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
        3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
          1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
          2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
    //RequestResponseBodyMethodProcessor继承这类
    public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    		implements HandlerMethodReturnValueHandler {
    
        ...
        
        //承接上一节内容
        protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
                Object body;
                Class<?> valueType;
                Type targetType;
    
                if (value instanceof CharSequence) {
                    body = value.toString();
                    valueType = String.class;
                    targetType = String.class;
                }
                else {
                    body = value;
                    valueType = getReturnValueType(body, returnType);
                    targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
                }
    
    			...
    
                //内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
                MediaType selectedMediaType = null;
                MediaType contentType = outputMessage.getHeaders().getContentType();
                boolean isContentTypePreset = contentType != null && contentType.isConcrete();
                if (isContentTypePreset) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found 'Content-Type:" + contentType + "' in response");
                    }
                    selectedMediaType = contentType;
                }
                else {
                    HttpServletRequest request = inputMessage.getServletRequest();
                    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
                    //服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
                    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    
                    if (body != null && producibleTypes.isEmpty()) {
                        throw new HttpMessageNotWritableException(
                                "No converter found for return value of type: " + valueType);
                    }
                    List<MediaType> mediaTypesToUse = new ArrayList<>();
                    for (MediaType requestedType : acceptableTypes) {
                        for (MediaType producibleType : producibleTypes) {
                            if (requestedType.isCompatibleWith(producibleType)) {
                                mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                            }
                        }
                    }
                    if (mediaTypesToUse.isEmpty()) {
                        if (body != null) {
                            throw new HttpMediaTypeNotAcceptableException(producibleTypes);
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
                        }
                        return;
                    }
    
                    MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
    
                    //选择一个MediaType
                    for (MediaType mediaType : mediaTypesToUse) {
                        if (mediaType.isConcrete()) {
                            selectedMediaType = mediaType;
                            break;
                        }
                        else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                            selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                            break;
                        }
                    }
    
                    if (logger.isDebugEnabled()) {
                        logger.debug("Using '" + selectedMediaType + "', given " +
                                acceptableTypes + " and supported " + producibleTypes);
                    }
                }
    
            	
                if (selectedMediaType != null) {
                    selectedMediaType = selectedMediaType.removeQualityValue();
                    //本节主角:HttpMessageConverter
                    for (HttpMessageConverter<?> converter : this.messageConverters) {
                        GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                                (GenericHttpMessageConverter<?>) converter : null);
                        
                        //判断是否可写
                        if (genericConverter != null ?
                                ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                                converter.canWrite(valueType, selectedMediaType)) {
                            body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                    (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                    inputMessage, outputMessage);
                            if (body != null) {
                                Object theBody = body;
                                LogFormatUtils.traceDebug(logger, traceOn ->
                                        "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                                addContentDispositionHeader(inputMessage, outputMessage);
    							//开始写入
                                if (genericConverter != null) {
                                    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                                }
                                else {
                                    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                                }
                            }
                            else {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Nothing to write: null body");
                                }
                            }
                            return;
                        }
                    }
                }
    			...
            }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    HTTPMessageConverter接口:

    /**
     * Strategy interface for converting from and to HTTP requests and responses.
     */
    public interface HttpMessageConverter<T> {
    
    	/**
    	 * Indicates whether the given class can be read by this converter.
    	 */
    	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    
    	/**
    	 * Indicates whether the given class can be written by this converter.
    	 */
    	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    
    	/**
    	 * Return the list of {@link MediaType} objects supported by this converter.
    	 */
    	List<MediaType> getSupportedMediaTypes();
    
    	/**
    	 * Read an object of the given type from the given input message, and returns it.
    	 */
    	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
    			throws IOException, HttpMessageNotReadableException;
    
    	/**
    	 * Write an given object to the given output message.
    	 */
    	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
    			throws IOException, HttpMessageNotWritableException;
    
    }
    
    
    • 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

    HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。

    例子:Person对象转为JSON,或者 JSON转为Person,这将用到MappingJackson2HttpMessageConverter

    在这里插入图片描述

    public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
    	...
    }
    
    • 1
    • 2
    • 3

    关于MappingJackson2HttpMessageConverter的实例化请看下节。

    关于HttpMessageConverters的初始化

    DispatcherServlet的初始化时会调用initHandlerAdapters(ApplicationContext context)

    public class DispatcherServlet extends FrameworkServlet {
        
        ...
        
    	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);
    			}
    		}
          ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    上述代码会加载ApplicationContext的所有HandlerAdapter,用来处理@RequestMappingRequestMappingHandlerAdapter实现HandlerAdapter接口,RequestMappingHandlerAdapter也被实例化。

    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
    		implements BeanFactoryAware, InitializingBean {
        
        ...
    
        private List<HttpMessageConverter<?>> messageConverters;
        
        ...
        
    	public RequestMappingHandlerAdapter() {
    		this.messageConverters = new ArrayList<>(4);
    		this.messageConverters.add(new ByteArrayHttpMessageConverter());
    		this.messageConverters.add(new StringHttpMessageConverter());
    		if (!shouldIgnoreXml) {
    			try {
    				this.messageConverters.add(new SourceHttpMessageConverter<>());
    			}
    			catch (Error err) {
    				// Ignore when no TransformerFactory implementation is available
    			}
    		}
    		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在构造器中看到一堆HttpMessageConverter。接着,重点查看AllEncompassingFormHttpMessageConverter类:

    public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
    
    	/**
    	 * Boolean flag controlled by a {@code spring.xml.ignore} system property that instructs Spring to
    	 * ignore XML, i.e. to not initialize the XML-related infrastructure.
    	 * 

    The default is "false". */ private static final boolean shouldIgnoreXml = SpringProperties.getFlag("spring.xml.ignore"); private static final boolean jaxb2Present; private static final boolean jackson2Present; private static final boolean jackson2XmlPresent; private static final boolean jackson2SmilePresent; private static final boolean gsonPresent; private static final boolean jsonbPresent; private static final boolean kotlinSerializationJsonPresent; static { ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader(); jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader); jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader); kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader); } public AllEncompassingFormHttpMessageConverter() { if (!shouldIgnoreXml) { try { addPartConverter(new SourceHttpMessageConverter<>()); } catch (Error err) { // Ignore when no TransformerFactory implementation is available } if (jaxb2Present && !jackson2XmlPresent) { addPartConverter(new Jaxb2RootElementHttpMessageConverter()); } } if (jackson2Present) { addPartConverter(new MappingJackson2HttpMessageConverter());//<----重点看这里 } else if (gsonPresent) { addPartConverter(new GsonHttpMessageConverter()); } else if (jsonbPresent) { addPartConverter(new JsonbHttpMessageConverter()); } else if (kotlinSerializationJsonPresent) { addPartConverter(new KotlinSerializationJsonHttpMessageConverter()); } if (jackson2XmlPresent && !shouldIgnoreXml) { addPartConverter(new MappingJackson2XmlHttpMessageConverter()); } if (jackson2SmilePresent) { addPartConverter(new MappingJackson2SmileHttpMessageConverter()); } } } public class FormHttpMessageConverter implements HttpMessageConverter<MultiValueMap<String, ?>> { ... private List<HttpMessageConverter<?>> partConverters = new ArrayList<>(); ... public void addPartConverter(HttpMessageConverter<?> partConverter) { Assert.notNull(partConverter, "'partConverter' must not be null"); this.partConverters.add(partConverter); } ... }

    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    AllEncompassingFormHttpMessageConverter类构造器看到MappingJackson2HttpMessageConverter类的实例化,AllEncompassingFormHttpMessageConverter包含MappingJackson2HttpMessageConverter

    ReturnValueHandler是怎么与MappingJackson2HttpMessageConverter关联起来?请看下节。

    ReturnValueHandler与MappingJackson2HttpMessageConverter关联

    再次回顾RequestMappingHandlerAdapter

    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
    		implements BeanFactoryAware, InitializingBean {
        
        ...
        @Nullable
    	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;//我们关注的returnValueHandlers
        
       	
        @Override
    	@Nullable//本方法在AbstractHandlerMethodAdapter
    	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
    
    		return handleInternal(request, response, (HandlerMethod) handler);
    	}
            
        @Override
    	protected ModelAndView handleInternal(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    		ModelAndView mav;
            ...
            mav = invokeHandlerMethod(request, response, handlerMethod);
            ...
    		return mav;
    	}
        
        @Nullable
    	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    		ServletWebRequest webRequest = new ServletWebRequest(request, response);
    		try {
    			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
    			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    			if (this.argumentResolvers != null) {
    				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    			}
    			if (this.returnValueHandlers != null) {//<---我们关注的returnValueHandlers
    				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    			}
                
                ...
                
    			invocableMethod.invokeAndHandle(webRequest, mavContainer);
    			if (asyncManager.isConcurrentHandlingStarted()) {
    				return null;
    			}
    
    			return getModelAndView(mavContainer, modelFactory, webRequest);
    		}
    		finally {
    			webRequest.requestCompleted();
    		}
    	}
        
       @Override
    	public void afterPropertiesSet() {
    		// Do this first, it may add ResponseBody advice beans
    		
            ...
            
    		if (this.returnValueHandlers == null) {//赋值returnValueHandlers
    			List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    			this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    		}
    	}
        
        private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    		List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
    
    		...
    		// Annotation-based return value types
            //这里就是 ReturnValueHandler与 MappingJackson2HttpMessageConverter关联 的关键点
    		handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),//<---MessageConverters也就传参传进来的
    				this.contentNegotiationManager, this.requestResponseBodyAdvice));//
            ...
    
    		return handlers;
    	}
        
        //------
        
        public List<HttpMessageConverter<?>> getMessageConverters() {
    		return this.messageConverters;
    	}
        
        //RequestMappingHandlerAdapter构造器已初始化部分messageConverters
       	public RequestMappingHandlerAdapter() {
    		this.messageConverters = new ArrayList<>(4);
    		this.messageConverters.add(new ByteArrayHttpMessageConverter());
    		this.messageConverters.add(new StringHttpMessageConverter());
    		if (!shouldIgnoreXml) {
    			try {
    				this.messageConverters.add(new SourceHttpMessageConverter<>());
    			}
    			catch (Error err) {
    				// Ignore when no TransformerFactory implementation is available
    			}
    		}
    		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    	}
    
        ...
                  
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    应用中WebMvcAutoConfiguration(底层是WebMvcConfigurationSupport实现)传入更多messageConverters,其中就包含MappingJackson2HttpMessageConverter

    39、响应处理-【源码分析】-内容协商原理

    根据客户端接收能力不同,返回不同媒体类型的数据。

    引入XML依赖:

     <dependency>
         <groupId>com.fasterxml.jackson.dataformatgroupId>
         <artifactId>jackson-dataformat-xmlartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    可用Postman软件分别测试返回json和xml:只需要改变请求头中Accept字段(application/json、application/xml)。

    Http协议中规定的,Accept字段告诉服务器本客户端可以接收的数据类型。

    内容协商原理

    1. 判断当前响应头中是否已经有确定的媒体类型MediaType
    2. 获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)(这一步在下一节有详细介绍)
      • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
      • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
    3. 遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
    4. 找到支持操作Person的converter,把converter支持的媒体类型统计出来。
    5. 客户端需要application/xml,服务端有10种MediaType。
    6. 进行内容协商的最佳匹配媒体类型
    7. 用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
    //RequestResponseBodyMethodProcessor继承这类
    public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    		implements HandlerMethodReturnValueHandler {
    
        ...
        
        //跟上一节的代码一致
        protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
                Object body;
                Class<?> valueType;
                Type targetType;
    
                if (value instanceof CharSequence) {
                    body = value.toString();
                    valueType = String.class;
                    targetType = String.class;
                }
                else {
                    body = value;
                    valueType = getReturnValueType(body, returnType);
                    targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
                }
    
    			...
    
                //本节重点
                //内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
                MediaType selectedMediaType = null;
                MediaType contentType = outputMessage.getHeaders().getContentType();
                boolean isContentTypePreset = contentType != null && contentType.isConcrete();
                if (isContentTypePreset) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found 'Content-Type:" + contentType + "' in response");
                    }
                    selectedMediaType = contentType;
                }
                else {
                    HttpServletRequest request = inputMessage.getServletRequest();
                    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
                    //服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
                    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    
                    if (body != null && producibleTypes.isEmpty()) {
                        throw new HttpMessageNotWritableException(
                                "No converter found for return value of type: " + valueType);
                    }
                    List<MediaType> mediaTypesToUse = new ArrayList<>();
                    for (MediaType requestedType : acceptableTypes) {
                        for (MediaType producibleType : producibleTypes) {
                            if (requestedType.isCompatibleWith(producibleType)) {
                                mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
                            }
                        }
                    }
                    if (mediaTypesToUse.isEmpty()) {
                        if (body != null) {
                            throw new HttpMediaTypeNotAcceptableException(producibleTypes);
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("No match for " + acceptableTypes + ", supported: " + producibleTypes);
                        }
                        return;
                    }
    
                    MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
    
                    //选择一个MediaType
                    for (MediaType mediaType : mediaTypesToUse) {
                        if (mediaType.isConcrete()) {
                            selectedMediaType = mediaType;
                            break;
                        }
                        else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                            selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                            break;
                        }
                    }
    
                    if (logger.isDebugEnabled()) {
                        logger.debug("Using '" + selectedMediaType + "', given " +
                                acceptableTypes + " and supported " + producibleTypes);
                    }
                }
    
            	
                if (selectedMediaType != null) {
                    selectedMediaType = selectedMediaType.removeQualityValue();
                    //本节主角:HttpMessageConverter
                    for (HttpMessageConverter<?> converter : this.messageConverters) {
                        GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
                                (GenericHttpMessageConverter<?>) converter : null);
                        
                        //判断是否可写
                        if (genericConverter != null ?
                                ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
                                converter.canWrite(valueType, selectedMediaType)) {
                            body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
                                    (Class<? extends HttpMessageConverter<?>>) converter.getClass(),
                                    inputMessage, outputMessage);
                            if (body != null) {
                                Object theBody = body;
                                LogFormatUtils.traceDebug(logger, traceOn ->
                                        "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
                                addContentDispositionHeader(inputMessage, outputMessage);
    							//开始写入
                                if (genericConverter != null) {
                                    genericConverter.write(body, targetType, selectedMediaType, outputMessage);
                                }
                                else {
                                    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                                }
                            }
                            else {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Nothing to write: null body");
                                }
                            }
                            return;
                        }
                    }
                }
    			...
            }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126

    40、响应处理-【源码分析】-基于请求参数的内容协商原理

    上一节内容协商原理的第二步:

    获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段application/xml)

    • contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
    • HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
    //RequestResponseBodyMethodProcessor继承这类
    public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
    		implements HandlerMethodReturnValueHandler {
    
        ...
        
        //跟上一节的代码一致
        protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
                    ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
                    throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    
                Object body;
                Class<?> valueType;
                Type targetType;
            
            	...
            
                        //本节重点
                //内容协商(浏览器默认会以请求头(参数Accept)的方式告诉服务器他能接受什么样的内容类型)
                MediaType selectedMediaType = null;
                MediaType contentType = outputMessage.getHeaders().getContentType();
                boolean isContentTypePreset = contentType != null && contentType.isConcrete();
                if (isContentTypePreset) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Found 'Content-Type:" + contentType + "' in response");
                    }
                    selectedMediaType = contentType;
                }
                else {
                    HttpServletRequest request = inputMessage.getServletRequest();
                    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
                    //服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
                    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
                ...
                
    	}
        
        //在AbstractMessageConverterMethodArgumentResolver类内
       	private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
    			throws HttpMediaTypeNotAcceptableException {
    
            //内容协商管理器 默认使用基于请求头的策略
    		return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
    	}
            
    }
    
    • 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
    public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
    	
        ...
        
        public ContentNegotiationManager() {
    		this(new HeaderContentNegotiationStrategy());//内容协商管理器 默认使用基于请求头的策略
    	}
        
        @Override
    	public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
    		for (ContentNegotiationStrategy strategy : this.strategies) {
    			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
    			if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
    				continue;
    			}
    			return mediaTypes;
    		}
    		return MEDIA_TYPE_ALL_LIST;
    	}
        ...
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    //基于请求头的策略
    public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {
    
    	/**
    	 * {@inheritDoc}
    	 * @throws HttpMediaTypeNotAcceptableException if the 'Accept' header cannot be parsed
    	 */
    	@Override
    	public List<MediaType> resolveMediaTypes(NativeWebRequest request)
    			throws HttpMediaTypeNotAcceptableException {
    
    		String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
    		if (headerValueArray == null) {
    			return MEDIA_TYPE_ALL_LIST;
    		}
    
    		List<String> headerValues = Arrays.asList(headerValueArray);
    		try {
    			List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
    			MediaType.sortBySpecificityAndQuality(mediaTypes);
    			return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
    		}
    		catch (InvalidMediaTypeException ex) {
    			throw new HttpMediaTypeNotAcceptableException(
    					"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
    		}
    	}
    
    }
    
    • 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

    开启浏览器参数方式内容协商功能

    为了方便内容协商,开启基于请求参数的内容协商功能。

    spring:
      mvc:
        contentnegotiation:
          favor-parameter: true  #开启请求参数内容协商模式
    
    • 1
    • 2
    • 3
    • 4

    内容协商管理器,就会多了一个ParameterContentNegotiationStrategy(由Spring容器注入)

    public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
    
    	private String parameterName = "format";//
    
    
    	/**
    	 * Create an instance with the given map of file extensions and media types.
    	 */
    	public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
    		super(mediaTypes);
    	}
    
    
    	/**
    	 * Set the name of the parameter to use to determine requested media types.
    	 * 

    By default this is set to {@code "format"}. */ public void setParameterName(String parameterName) { Assert.notNull(parameterName, "'parameterName' is required"); this.parameterName = parameterName; } public String getParameterName() { return this.parameterName; } @Override @Nullable protected String getMediaTypeKey(NativeWebRequest request) { return request.getParameter(getParameterName()); } //---以下方法在AbstractMappingContentNegotiationStrategy类 @Override public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException { return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest)); } /** * An alternative to {@link #resolveMediaTypes(NativeWebRequest)} that accepts * an already extracted key. * @since 3.2.16 */ public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key) throws HttpMediaTypeNotAcceptableException { if (StringUtils.hasText(key)) { MediaType mediaType = lookupMediaType(key); if (mediaType != null) { handleMatch(key, mediaType); return Collections.singletonList(mediaType); } mediaType = handleNoMatch(webRequest, key); if (mediaType != null) { addMapping(key, mediaType); return Collections.singletonList(mediaType); } } return MEDIA_TYPE_ALL_LIST; } }

    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    然后,浏览器地址输入带format参数的URL:

    http://localhost:8080/test/person?format=json
    或
    http://localhost:8080/test/person?format=xml
    
    • 1
    • 2
    • 3

    这样,后端会根据参数format的值,返回对应json或xml格式的数据。

    41、响应处理-【源码分析】-自定义MessageConverter

    实现多协议数据兼容。json、xml、x-guigu(这个是自创的)

    1. @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

    2. Processor 处理方法返回值。通过 MessageConverter处理

    3. 所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

    4. 内容协商找到最终的 messageConverter

    SpringMVC的什么功能,一个入口给容器中添加一个 WebMvcConfigurer

    @Configuration(proxyBeanMethods = false)
    public class WebConfig {
        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {
    
                @Override
                public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
                    converters.add(new GuiguMessageConverter());
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    /**
     * 自定义的Converter
     */
    public class GuiguMessageConverter implements HttpMessageConverter<Person> {
    
        @Override
        public boolean canRead(Class<?> clazz, MediaType mediaType) {
            return false;
        }
    
        @Override
        public boolean canWrite(Class<?> clazz, MediaType mediaType) {
            return clazz.isAssignableFrom(Person.class);
        }
    
        /**
         * 服务器要统计所有MessageConverter都能写出哪些内容类型
         *
         * application/x-guigu
         * @return
         */
        @Override
        public List<MediaType> getSupportedMediaTypes() {
            return MediaType.parseMediaTypes("application/x-guigu");
        }
    
        @Override
        public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
            return null;
        }
    
        @Override
        public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
            //自定义协议数据的写出
            String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
    
    
            //写出去
            OutputStream body = outputMessage.getBody();
            body.write(data.getBytes());
        }
    }
    
    • 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
    import java.util.Date;
    
    @Controller
    public class ResponseTestController {
    
        /**
         * 1、浏览器发请求直接返回 xml    [application/xml]        jacksonXmlConverter
         * 2、如果是ajax请求 返回 json   [application/json]      jacksonJsonConverter
         * 3、如果硅谷app发请求,返回自定义协议数据  [appliaction/x-guigu]   xxxxConverter
         *          属性值1;属性值2;
         *
         * 步骤:
         * 1、添加自定义的MessageConverter进系统底层
         * 2、系统底层就会统计出所有MessageConverter能操作哪些类型
         * 3、客户端内容协商 [guigu--->guigu]
         *
         * 作业:如何以参数的方式进行内容协商
         * @return
         */
        @ResponseBody  //利用返回值处理器里面的消息转换器进行处理
        @GetMapping(value = "/test/person")
        public Person getPerson(){
            Person person = new Person();
            person.setAge(28);
            person.setBirth(new Date());
            person.setUserName("zhangsan");
            return person;
        }
    
    }
    
    • 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

    用Postman发送/test/person(请求头Accept:application/x-guigu),将返回自定义协议数据的写出。

    42、响应处理-【源码分析】-浏览器与PostMan内容协商完全适配

    假设你想基于自定义请求参数的自定义内容协商功能。

    换句话,在地址栏输入http://localhost:8080/test/person?format=gg返回数据,跟http://localhost:8080/test/person且请求头参数Accept:application/x-guigu的返回自定义协议数据的一致。

    @Configuration(proxyBeanMethods = false)
    public class WebConfig /*implements WebMvcConfigurer*/ {
    
        //1、WebMvcConfigurer定制化SpringMVC的功能
        @Bean
        public WebMvcConfigurer webMvcConfigurer(){
            return new WebMvcConfigurer() {
    
                /**
                 * 自定义内容协商策略
                 * @param configurer
                 */
                @Override
                public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
                    //Map mediaTypes
                    Map<String, MediaType> mediaTypes = new HashMap<>();
                    mediaTypes.put("json",MediaType.APPLICATION_JSON);
                    mediaTypes.put("xml",MediaType.APPLICATION_XML);
                    //自定义媒体类型
                    mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
                    //指定支持解析哪些参数对应的哪些媒体类型
                    ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
    //                parameterStrategy.setParameterName("ff");
    
                    //还需添加请求头处理策略,否则accept:application/json、application/xml则会失效
                    HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
    
                    configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
                }
            }
        }
        
        ...
        
    }
    
    • 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

    日后开发要注意,有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。

    43、视图解析-Thymeleaf初体验

    Thymeleaf is a modern server-side Java template engine for both web and standalone environments.

    Thymeleaf’s main goal is to bring elegant natural templates to your development workflow — HTML that can be correctly displayed in browsers and also work as static prototypes, allowing for stronger collaboration in development teams.

    With modules for Spring Framework, a host of integrations with your favourite tools, and the ability to plug in your own functionality, Thymeleaf is ideal for modern-day HTML5 JVM web development — although there is much more it can do.——Link

    Thymeleaf官方文档

    thymeleaf使用

    引入Starter
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-thymeleafartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    自动配置好了thymeleaf
    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties(ThymeleafProperties.class)
    @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
    @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
    public class ThymeleafAutoConfiguration {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    自动配好的策略

    1. 所有thymeleaf的配置值都在 ThymeleafProperties

    2. 配置好了 SpringTemplateEngine

    3. 配好了 ThymeleafViewResolver

    4. 我们只需要直接开发页面

    public static final String DEFAULT_PREFIX = "classpath:/templates/";//模板放置处
    public static final String DEFAULT_SUFFIX = ".html";//文件的后缀名
    
    • 1
    • 2

    编写一个控制层:

    @Controller
    public class ViewTestController {
        @GetMapping("/hello")
        public String hello(Model model){
            //model中的数据会被放在请求域中 request.setAttribute("a",aa)
            model.addAttribute("msg","一定要大力发展工业文化");
            model.addAttribute("link","http://www.baidu.com");
            return "success";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    /templates/success.html

    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Titletitle>
    head>
    <body>
    <h1 th:text="${msg}">niceh1>
    <h2>
        <a href="www.baidu.com" th:href="${link}">去百度a>  <br/>
        <a href="www.google.com" th:href="@{/link}">去百度a>
    h2>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    server:
      servlet:
        context-path: /app #设置应用名
    
    • 1
    • 2
    • 3

    这个设置后,URL要插入/app, 如http://localhost:8080/app/hello.html

    基本语法

    表达式
    表达式名字语法用途
    变量取值${…}获取请求域、session域、对象等值
    选择变量*{…}获取上下文对象值
    消息#{…}获取国际化等值
    链接@{…}生成链接
    片段表达式~{…}jsp:include 作用,引入公共页面片段
    字面量
    • 文本值: ‘one text’ , ‘Another one!’ ,…
    • 数字: 0 , 34 , 3.0 , 12.3 ,…
    • 布尔值: true , false
    • 空值: null
    • 变量: one,two,… 变量不能有空格
    文本操作
    • 字符串拼接: +
    • 变量替换: |The name is ${name}|
    数学运算
    • 运算符: + , - , * , / , %
    布尔运算
    • 运算符: and , or
    • 一元运算: ! , not
    比较运算
    • 比较: > , < , >= , <= ( gt , lt , ge , le )
    • 等式: == , != ( eq , ne )
    条件运算
    • If-then: (if) ? (then)
    • If-then-else: (if) ? (then) : (else)
    • Default: (value) ?: (defaultvalue)
    特殊操作
    • 无操作: _

    设置属性值-th:attr

    • 设置单个值
    <form action="subscribe.html" th:attr="action=@{/subscribe}">
      <fieldset>
        <input type="text" name="email" />
        <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
      fieldset>
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 设置多个值
    <img src="../../images/gtvglogo.png"  
         th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
    
    • 1
    • 2

    官方文档 - 5 Setting Attribute Values

    迭代

    <tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onionstd>
        <td th:text="${prod.price}">2.41td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yestd>
    tr>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    <tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
        <td th:text="${prod.name}">Onionstd>
        <td th:text="${prod.price}">2.41td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yestd>
    tr>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    条件运算

    <a href="comments.html"
    	th:href="@{/product/comments(prodId=${prod.id})}"
    	th:if="${not #lists.isEmpty(prod.comments)}">viewa>
    
    • 1
    • 2
    • 3
    <div th:switch="${user.role}">
          <p th:case="'admin'">User is an administratorp>
          <p th:case="#{roles.manager}">User is a managerp>
          <p th:case="*">User is some other thingp>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    属性优先级

    OrderFeatureAttributes
    1Fragment inclusionth:insert th:replace
    2Fragment iterationth:each
    3Conditional evaluationth:if th:unless th:switch th:case
    4Local variable definitionth:object th:with
    5General attribute modificationth:attr th:attrprepend th:attrappend
    6Specific attribute modificationth:value th:href th:src ...
    7Text (tag body modification)th:text th:utext
    8Fragment specificationth:fragment
    9Fragment removalth:remove

    官方文档 - 10 Attribute Precedence

    44、web实验-后台管理系统基本功能

    项目创建

    使用IDEA的Spring Initializr。

    • thymeleaf、
    • web-starter、
    • devtools、
    • lombok

    登陆页面

    • /static 放置 css,js等静态资源

    • /templates/login.html 登录页

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
    <form class="form-signin" action="index.html" method="post" th:action="@{/login}">
    
        ...
        
        
        <label style="color: red" th:text="${msg}">label>
        
        <input type="text" name="userName" class="form-control" placeholder="User ID" autofocus>
        <input type="password" name="password" class="form-control" placeholder="Password">
        
        <button class="btn btn-lg btn-login btn-block" type="submit">
            <i class="fa fa-check">i>
        button>
        
        ...
        
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • /templates/main.html 主页

    thymeleaf内联写法:

    <p>Hello, [[${session.user.name}]]!p>
    
    • 1

    登录控制层

    @Controller
    public class IndexController {
        /**
         * 来登录页
         * @return
         */
        @GetMapping(value = {"/","/login"})
        public String loginPage(){
    
            return "login";
        }
    
        @PostMapping("/login")
        public String main(User user, HttpSession session, Model model){ //RedirectAttributes
    
            if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
                //把登陆成功的用户保存起来
                session.setAttribute("loginUser",user);
                //登录成功重定向到main.html;  重定向防止表单重复提交
                return "redirect:/main.html";
            }else {
                model.addAttribute("msg","账号密码错误");
                //回到登录页面
                return "login";
            }
        }
        
         /**
         * 去main页面
         * @return
         */
        @GetMapping("/main.html")
        public String mainPage(HttpSession session, Model model){
            
            //最好用拦截器,过滤器
            Object loginUser = session.getAttribute("loginUser");
            if(loginUser != null){
            	return "main";
            }else {
                //session过期,没有登陆过
            	//回到登录页面
    	        model.addAttribute("msg","请重新登录");
        	    return "login";
            }
        }
        
    }
    
    • 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

    模型

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class User {
        private String userName;
        private String password;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    45、web实验-抽取公共页面

    官方文档 - Template Layout

    • 公共页面/templates/common.html
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head th:fragment="commonheader">
        
        <link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
        <link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">
        ...
    head>
    <body>
    
    <div id="leftmenu" class="left-side sticky-left-side">
    	...
    
        <div class="left-side-inner">
    		...
    
            
            <ul class="nav nav-pills nav-stacked custom-nav">
                <li><a th:href="@{/main.html}"><i class="fa fa-home">i> <span>Dashboardspan>a>li>
                ...
                <li class="menu-list nav-active"><a href="#"><i class="fa fa-th-list">i> <span>Data Tablesspan>a>
                    <ul class="sub-menu-list">
                        <li><a th:href="@{/basic_table}"> Basic Tablea>li>
                        <li><a th:href="@{/dynamic_table}"> Advanced Tablea>li>
                        <li><a th:href="@{/responsive_table}"> Responsive Tablea>li>
                        <li><a th:href="@{/editable_table}"> Edit Tablea>li>
                    ul>
                li>
                ...
            ul>
            
        div>
    div>
    
    
    
    
    <div th:fragment="headermenu" class="header-section">
    
        
        <a class="toggle-btn"><i class="fa fa-bars">i>a>
        
    	...
    
    div>
    
    
    <div id="commonscript">
        
        <script th:src="@{/js/jquery-1.10.2.min.js}">script>
        <script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}">script>
        <script th:src="@{/js/jquery-migrate-1.2.1.min.js}">script>
        <script th:src="@{/js/bootstrap.min.js}">script>
        <script th:src="@{/js/modernizr.min.js}">script>
        <script th:src="@{/js/jquery.nicescroll.js}">script>
        
        <script th:src="@{/js/scripts.js}">script>
    div>
    body>
    html>
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • /templates/table/basic_table.html
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
      <meta name="description" content="">
      <meta name="author" content="ThemeBucket">
      <link rel="shortcut icon" href="#" type="image/png">
    
      <title>Basic Tabletitle>
        <div th:include="common :: commonheader"> div>
    head>
    
    <body class="sticky-header">
    
    <section>
    <div th:replace="common :: #leftmenu">div>
        
        
        <div class="main-content" >
    
            <div th:replace="common :: headermenu">div>
            ...
        div>
        
    section>
    
    
    <div th:replace="common :: #commonscript">div>
    
    
    body>
    html>
    
    
    • 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

    Difference between th:insert and th:replace (and th:include)

    46、web实验-遍历数据与页面bug修改

    控制层代码:

    @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                                         new User("lisi", "123444"),
                                         new User("haha", "aaaaa"),
                                         new User("hehe ", "aaddd"));
        model.addAttribute("users",users);
    
        return "table/dynamic_table";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    页面代码:

    <table class="display table table-bordered" id="hidden-table-info">
        <thead>
            <tr>
                <th>#th>
                <th>用户名th>
                <th>密码th>
            tr>
        thead>
        <tbody>
            <tr class="gradeX" th:each="user,stats:${users}">
                <td th:text="${stats.count}">Tridenttd>
                <td th:text="${user.userName}">Internettd>
                <td >[[${user.password}]]td>
            tr>
        tbody>
    table>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    47、视图解析-【源码分析】-视图解析器与视图

    视图解析原理流程

    1. 目标方法处理的过程中(阅读DispatcherServlet源码),所有数据都会被放在 ModelAndViewContainer 里面,其中包括数据和视图地址。

    2. 方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

    3. 任何目标方法执行完成以后都会返回ModelAndView(数据和视图地址)。

    4. processDispatchResult()处理派发结果(页面改如何响应)

      • render(mv, request, response); 进行页面渲染逻辑

        • 根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
        1. 所有的视图解析器尝试是否能根据当前返回值得到View对象
        2. 得到了 redirect:/main.html --> Thymeleaf new RedirectView()
        3. ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
        4. view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作。
        • RedirectView 如何渲染【重定向到一个页面】
        • 获取目标url地址
        • response.sendRedirect(encodedURL);

    视图解析
    - 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
    - 返回值以 redirect: 开始: new RedirectView() --> render就是重定向
    - 返回值是普通字符串:new ThymeleafView()—>


    阅读源码:最好自己在IDE,打断点,且Debug模式运行实例,这样比较没那么沉闷。

    48、拦截器-登录检查与静态资源放行

    1. 编写一个拦截器实现HandlerInterceptor接口

    2. 拦截器注册到容器中(实现WebMvcConfigureraddInterceptors()

    3. 指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截】

    编写一个实现HandlerInterceptor接口的拦截器:

    @Slf4j
    public class LoginInterceptor implements HandlerInterceptor {
    
        /**
         * 目标方法执行之前
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            String requestURI = request.getRequestURI();
            log.info("preHandle拦截的请求路径是{}",requestURI);
    
            //登录检查逻辑
            HttpSession session = request.getSession();
    
            Object loginUser = session.getAttribute("loginUser");
    
            if(loginUser != null){
                //放行
                return true;
            }
    
            //拦截住。未登录。跳转到登录页
            request.setAttribute("msg","请先登录");
    //        re.sendRedirect("/");
            request.getRequestDispatcher("/").forward(request,response);
            return false;
        }
    
        /**
         * 目标方法执行完成以后
         */
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("postHandle执行{}",modelAndView);
        }
    
        /**
         * 页面渲染以后
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log.info("afterCompletion执行异常{}",ex);
        }
    }
    
    • 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

    拦截器注册到容器中 && 指定拦截规则:

    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer{
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
                    .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
                    .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                            "/js/**","/aa/**"); //放行的请求
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    49、拦截器-【源码分析】-拦截器的执行时机和原理

    1. 根据当前请求,找到HandlerExecutionChain(可以处理请求的handler以及handler的所有 拦截器)
    2. 先来顺序执行 所有拦截器的 preHandle()方法。
      • 如果当前拦截器preHandle()返回为true。则执行下一个拦截器的preHandle()
      • 如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion();
    3. 如果任何一个拦截器返回false,直接跳出不执行目标方法。
    4. 所有拦截器都返回true,才执行目标方法。
    5. 倒序执行所有拦截器的postHandle()方法。
    6. 前面的步骤有任何异常都会直接倒序触发 afterCompletion()
    7. 页面成功渲染完成以后,也会倒序触发 afterCompletion()

    在这里插入图片描述

    DispatcherServlet中涉及到HandlerInterceptor的地方:

    public class DispatcherServlet extends FrameworkServlet {
        
        ...
        
    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;
    
    		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
                	...
                
                    //该方法内调用HandlerInterceptor的preHandle()
    				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    					return;
    				}
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
                	...
                    //该方法内调用HandlerInterceptor的postHandle()
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}			
            	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
                //该方法内调用HandlerInterceptor接口的afterCompletion方法
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
                //该方法内调用HandlerInterceptor接口的afterCompletion方法
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			...
    		}
    	}
    
    	private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
    
    		if (mappedHandler != null) {
                //该方法内调用HandlerInterceptor接口的afterCompletion方法
    			mappedHandler.triggerAfterCompletion(request, response, ex);
    		}
    		throw ex;
    	}
    
    	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    			@Nullable Exception exception) throws Exception {
    
            ...
    
    		if (mappedHandler != null) {
                //该方法内调用HandlerInterceptor接口的afterCompletion方法
    			// Exception (if any) is already handled..
    			mappedHandler.triggerAfterCompletion(request, response, 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
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    public class HandlerExecutionChain {
        
        ...
        
    	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		for (int i = 0; i < this.interceptorList.size(); i++) {
    			HandlerInterceptor interceptor = this.interceptorList.get(i);
                //HandlerInterceptor的preHandle方法
    			if (!interceptor.preHandle(request, response, this.handler)) {
                    
    				triggerAfterCompletion(request, response, null);
    				return false;
    			}
    			this.interceptorIndex = i;
    		}
    		return true;
    	}
        
       	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    			throws Exception {
    
    		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
    			HandlerInterceptor interceptor = this.interceptorList.get(i);
                
                //HandlerInterceptor接口的postHandle方法
    			interceptor.postHandle(request, response, this.handler, mv);
    		}
    	}
        
        void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    		for (int i = this.interceptorIndex; i >= 0; i--) {
    			HandlerInterceptor interceptor = this.interceptorList.get(i);
    			try {
                    //HandlerInterceptor接口的afterCompletion方法
    				interceptor.afterCompletion(request, response, this.handler, ex);
    			}
    			catch (Throwable ex2) {
    				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    			}
    		}
    	}
    } 
        
    
    • 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

    50、文件上传-单文件与多文件上传的使用

    • 页面代码/static/form/form_layouts.html
    <form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
        <div class="form-group">
            <label for="exampleInputEmail1">邮箱label>
            <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
        div>
        
        <div class="form-group">
            <label for="exampleInputPassword1">名字label>
            <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
        div>
        
        <div class="form-group">
            <label for="exampleInputFile">头像label>
            <input type="file" name="headerImg" id="exampleInputFile">
        div>
        
        <div class="form-group">
            <label for="exampleInputFile">生活照label>
            <input type="file" name="photos" multiple>
        div>
        
        <div class="checkbox">
            <label>
                <input type="checkbox"> Check me out
            label>
        div>
        <button type="submit" class="btn btn-primary">提交button>
    form>
    
    • 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
    • 控制层代码
    @Slf4j
    @Controller
    public class FormTestController {
    
        @GetMapping("/form_layouts")
        public String form_layouts(){
            return "form/form_layouts";
        }
    
        @PostMapping("/upload")
        public String upload(@RequestParam("email") String email,
                             @RequestParam("username") String username,
                             @RequestPart("headerImg") MultipartFile headerImg,
                             @RequestPart("photos") MultipartFile[] photos) throws IOException {
    
            log.info("上传的信息:email={},username={},headerImg={},photos={}",
                     email,username,headerImg.getSize(),photos.length);
    
            if(!headerImg.isEmpty()){
                //保存到文件服务器,OSS服务器
                String originalFilename = headerImg.getOriginalFilename();
                headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
            }
    
            if(photos.length > 0){
                for (MultipartFile photo : photos) {
                    if(!photo.isEmpty()){
                        String originalFilename = photo.getOriginalFilename();
                        photo.transferTo(new File("H:\\cache\\"+originalFilename));
                    }
                }
            }
    
    
            return "main";
        }
    }
    
    • 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

    文件上传相关的配置类:

    • org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
    • org.springframework.boot.autoconfigure.web.servlet.MultipartProperties

    文件大小相关配置项:

    spring.servlet.multipart.max-file-size=10MB
    spring.servlet.multipart.max-request-size=100MB
    
    • 1
    • 2

    51、文件上传-【源码流程】文件上传参数解析器

    文件上传相关的自动配置类MultipartAutoConfiguration有创建文件上传参数解析器StandardServletMultipartResolver

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
    @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(MultipartProperties.class)
    public class MultipartAutoConfiguration {
    
    	private final MultipartProperties multipartProperties;
    
    	public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
    		this.multipartProperties = multipartProperties;
    	}
    
    	@Bean
    	@ConditionalOnMissingBean({ MultipartConfigElement.class, CommonsMultipartResolver.class })
    	public MultipartConfigElement multipartConfigElement() {
    		return this.multipartProperties.createMultipartConfig();
    	}
    
    	@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    	@ConditionalOnMissingBean(MultipartResolver.class)
    	public StandardServletMultipartResolver multipartResolver() {
            //配置好文件上传解析器
    		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
    		multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
    		return multipartResolver;
    	}
    
    }
    
    • 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
    //文件上传解析器
    public class StandardServletMultipartResolver implements MultipartResolver {
    
    	private boolean resolveLazily = false;
    
    	public void setResolveLazily(boolean resolveLazily) {
    		this.resolveLazily = resolveLazily;
    	}
    
    
    	@Override
    	public boolean isMultipart(HttpServletRequest request) {
    		return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
    	}
    
    	@Override
    	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
    	}
    
    	@Override
    	public void cleanupMultipart(MultipartHttpServletRequest request) {
    		if (!(request instanceof AbstractMultipartHttpServletRequest) ||
    				((AbstractMultipartHttpServletRequest) request).isResolved()) {
    			// To be on the safe side: explicitly delete the parts,
    			// but only actual file parts (for Resin compatibility)
    			try {
    				for (Part part : request.getParts()) {
    					if (request.getFile(part.getName()) != null) {
    						part.delete();
    					}
    				}
    			}
    			catch (Throwable ex) {
    				LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
    			}
    		}
    	}
    
    }
    
    • 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
    public class DispatcherServlet extends FrameworkServlet {
        
        @Nullable
    	private MultipartResolver multipartResolver;
        
    	private void initMultipartResolver(ApplicationContext context) {
    		...
            
            //这个就是配置类配置的StandardServletMultipartResolver文件上传解析器
    		this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
    		...
    	}
        
    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		HttpServletRequest processedRequest = request;
    		HandlerExecutionChain mappedHandler = null;
    		boolean multipartRequestParsed = false;//最后finally的回收flag
    		...
    		try {
    			ModelAndView mv = null;
    			Exception dispatchException = null;
    
    			try {
                    //做预处理,如果有上传文件 就new StandardMultipartHttpServletRequest包装类
    				processedRequest = checkMultipart(request);
    				multipartRequestParsed = (processedRequest != request);
    				// Determine handler for the current request.
    				mappedHandler = getHandler(processedRequest);
    				
                    ...
    
    				// Determine handler adapter for the current request.
    				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    
    				...
    
    				// Actually invoke the handler.
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    
                }
                ....
                
    		finally {
    
                ...
                
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
    		}
    	}
    
    	protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    		if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
                ...
    			return this.multipartResolver.resolveMultipart(request);
                ...
    		}
        }
    
    	protected void cleanupMultipart(HttpServletRequest request) {
    		if (this.multipartResolver != null) {
    			MultipartHttpServletRequest multipartRequest =
    					WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
    			if (multipartRequest != null) {
    				this.multipartResolver.cleanupMultipart(multipartRequest);
    			}
    		}
    	}
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());跳到以下的类

    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
    		implements BeanFactoryAware, InitializingBean {
    	@Override
    	protected ModelAndView handleInternal(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    		ModelAndView mav;
    		...
    		mav = invokeHandlerMethod(request, response, handlerMethod);
            ...
    		return mav;
    	}
        
        @Nullable
    	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
    		ServletWebRequest webRequest = new ServletWebRequest(request, response);
    		try {
    			WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    			ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    
    			ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    			if (this.argumentResolvers != null) {//关注点
    				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    			}
    			...
    			invocableMethod.invokeAndHandle(webRequest, mavContainer);
    			...
    
    			return getModelAndView(mavContainer, modelFactory, webRequest);
    		}
    		finally {
    			webRequest.requestCompleted();
    		}
    	}
        
    }
    
    • 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

    this.argumentResolvers其中主角类RequestPartMethodArgumentResolver用来生成

    public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
        
        ...
    	public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
    		Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    		...
    	}
        
    	@Nullable
    	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
    
    		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    		...
    		return doInvoke(args);//反射调用
    	}
        
    	@Nullable
    	protected Object doInvoke(Object... args) throws Exception {
    		Method method = getBridgedMethod();
    		ReflectionUtils.makeAccessible(method);
    		return method.invoke(getBean(), args);
    		...
    	}
        
        //处理得出multipart参数,准备稍后的反射调用(@PostMapping标记的上传方法)
        protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    			Object... providedArgs) throws Exception {
    
    		MethodParameter[] parameters = getMethodParameters();
    		...
    		Object[] args = new Object[parameters.length];
    		for (int i = 0; i < parameters.length; i++) {
    			MethodParameter parameter = parameters[i];
    			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    			args[i] = findProvidedArgument(parameter, providedArgs);
    			if (args[i] != null) {
    				continue;
    			}
                //关注点1
    			if (!this.resolvers.supportsParameter(parameter)) {
    				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    			}
    			try {
                    //关注点2
    				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    			}
    			catch (Exception ex) {
    				...
    			}
    		}
    		return 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
    • 53
    • 54
    • 55
    • 56
    public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
    
        //对应上面代码关注点1
        @Override
    	public boolean supportsParameter(MethodParameter parameter) {
            //标注@RequestPart的参数
    		if (parameter.hasParameterAnnotation(RequestPart.class)) {
    			return true;
    		}
    		else {
    			if (parameter.hasParameterAnnotation(RequestParam.class)) {
    				return false;
    			}
    			return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
    		}
    	}
    
        //对应上面代码关注点2
    	@Override
    	@Nullable
    	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
    			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
    		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    		Assert.state(servletRequest != null, "No HttpServletRequest");
    
    		RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
    		boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
    
    		String name = getPartName(parameter, requestPart);
    		parameter = parameter.nestedIfOptional();
    		Object arg = null;
    
            //封装成MultipartFile类型的对象作参数
    		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
    			arg = mpArg;
    		}
            
            ...
    
    		return adaptArgumentIfNecessary(arg, parameter);
    	}
    }
    
    • 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
    public final class MultipartResolutionDelegate {
        ...
        
    	@Nullable
    	public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
    			throws Exception {
    
    		MultipartHttpServletRequest multipartRequest =
    				WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
    		boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
    
    		if (MultipartFile.class == parameter.getNestedParameterType()) {
    			if (!isMultipart) {
    				return null;
    			}
    			if (multipartRequest == null) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
    			return multipartRequest.getFile(name);
    		}
    		else if (isMultipartFileCollection(parameter)) {
    			if (!isMultipart) {
    				return null;
    			}
    			if (multipartRequest == null) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
    			List<MultipartFile> files = multipartRequest.getFiles(name);
    			return (!files.isEmpty() ? files : null);
    		}
    		else if (isMultipartFileArray(parameter)) {
    			if (!isMultipart) {
    				return null;
    			}
    			if (multipartRequest == null) {
    				multipartRequest = new StandardMultipartHttpServletRequest(request);
    			}
    			List<MultipartFile> files = multipartRequest.getFiles(name);
    			return (!files.isEmpty() ? files.toArray(new MultipartFile[0]) : null);
    		}
    		else if (Part.class == parameter.getNestedParameterType()) {
    			if (!isMultipart) {
    				return null;
    			}
    			return request.getPart(name);
    		}
    		else if (isPartCollection(parameter)) {
    			if (!isMultipart) {
    				return null;
    			}
    			List<Part> parts = resolvePartList(request, name);
    			return (!parts.isEmpty() ? parts : null);
    		}
    		else if (isPartArray(parameter)) {
    			if (!isMultipart) {
    				return null;
    			}
    			List<Part> parts = resolvePartList(request, name);
    			return (!parts.isEmpty() ? parts.toArray(new Part[0]) : null);
    		}
    		else {
    			return UNRESOLVABLE;
    		}
    	}
        
        ...
        
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    52、错误处理-SpringBoot默认错误处理机制

    Spring Boot官方文档 - Error Handling

    默认规则

    • 默认情况下,Spring Boot提供/error处理所有错误的映射

    • 机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

    {
      "timestamp": "2020-11-22T05:53:28.416+00:00",
      "status": 404,
      "error": "Not Found",
      "message": "No message available",
      "path": "/asadada"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 要对其进行自定义,添加View解析为error

    • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。

    • /templates/error/下的4xx,5xx页面会被自动解析

    53、错误处理-【源码分析】底层组件功能分析

    • ErrorMvcAutoConfiguration 自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
    • public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver
      • DefaultErrorAttributes:定义错误页面中可以包含数据(异常明细,堆栈信息等)。
    • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)
    • 处理默认 /error 路径的请求,页面响应 new ModelAndView("error", model);
      • 容器中有组件 View->id是error;(响应默认错误页)
      • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
    • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver
    • 如果发生异常错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面(主要作用)。
      • error/404、5xx.html
      • 如果想要返回页面,就会找error视图(StaticView默认是一个白页)。

    54、错误处理-【源码流程】异常处理流程

    譬如写一个会抛出异常的控制层:

    @Slf4j
    @RestController
    public class HelloController {
    
        @RequestMapping("/hello")
        public String handle01(){
    
            int i = 1 / 0;//将会抛出ArithmeticException
    
            log.info("Hello, Spring Boot 2!");
            return "Hello, Spring Boot 2!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    当浏览器发出/hello请求,DispatcherServletdoDispatch()mv = ha.handle(processedRequest, response, mappedHandler.getHandler());将会抛出ArithmeticException

    public class DispatcherServlet extends FrameworkServlet {
        ...
    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    		...
    				// Actually invoke the handler.
                	//将会抛出ArithmeticException
    				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
    				applyDefaultViewName(processedRequest, mv);
    				mappedHandler.applyPostHandle(processedRequest, response, mv);
    			}
    			catch (Exception ex) {
                    //将会捕捉ArithmeticException
    				dispatchException = ex;
    			}
    			catch (Throwable err) {
    				...
    			}
        		//捕捉后,继续运行
    			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    		}
    		catch (Exception ex) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    		}
    		catch (Throwable err) {
    			triggerAfterCompletion(processedRequest, response, mappedHandler,
    					new NestedServletException("Handler processing failed", err));
    		}
    		finally {
    			...
    		}
    	}
    
    	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    			@Nullable Exception exception) throws Exception {
    
    		boolean errorView = false;
    
    		if (exception != null) {
    			if (exception instanceof ModelAndViewDefiningException) {
    				...
    			}
    			else {
    				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    				//ArithmeticException将在这处理
                    mv = processHandlerException(request, response, handler, exception);
    				errorView = (mv != null);
    			}
    		}
    		...
    	}
    
    	protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    			@Nullable Object handler, Exception ex) throws Exception {
    
    		// Success and error responses may use different content types
    		request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    
    		// Check registered HandlerExceptionResolvers...
    		ModelAndView exMv = null;
    		if (this.handlerExceptionResolvers != null) {
                //遍历所有的 handlerExceptionResolvers,看谁能处理当前异常HandlerExceptionResolver处理器异常解析器
    			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    				exMv = resolver.resolveException(request, response, handler, ex);
    				if (exMv != null) {
    					break;
    				}
    			}
    		}
    		...
    	
            //若只有系统的自带的异常解析器(没有自定义的),异常还是会抛出
    		throw ex;
    	}
    
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    系统自带的异常解析器

    在这里插入图片描述

    • DefaultErrorAttributes先来处理异常,它主要功能把异常信息保存到request域,并且返回null。
    public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
        ...
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            this.storeErrorAttributes(request, ex);
            return null;
        }
    
        private void storeErrorAttributes(HttpServletRequest request, Exception ex) {
            request.setAttribute(ERROR_ATTRIBUTE, ex);//把异常信息保存到request域
        }
        ...
        
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 默认没有任何解析器(上图的HandlerExceptionResolverComposite)能处理异常,所以最后异常会被抛出。

    • 最终底层就会转发/error 请求。会被底层的BasicErrorController处理。

    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
    
        @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
        public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
           HttpStatus status = getStatus(request);
           Map<String, Object> model = Collections
                 .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
           response.setStatus(status.value());
           ModelAndView modelAndView = resolveErrorView(request, response, status, model);
           //如果/template/error内没有4**.html或5**.html,
           //modelAndView为空,最终还是返回viewName为error的modelAndView
           return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
        }
        
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        
        ...
        
    	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            ...
         	// Actually invoke the handler.
    		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    		...
            //渲染页面
    		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
            ...
        }
        
        private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    			@Nullable Exception exception) throws Exception {
    
            boolean errorView = false;
            ...
    		// Did the handler return a view to render?
    		if (mv != null && !mv.wasCleared()) {
    			render(mv, request, response);
    			if (errorView) {
    				WebUtils.clearErrorRequestAttributes(request);
    			}
    		}
    		...
    	}
        
        protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    		...
    
    		View view;
    		String viewName = mv.getViewName();
    		if (viewName != null) {
    			// We need to resolve the view name.
                //找出合适error的View,如果/template/error内没有4**.html或5**.html,
                //将会返回默认异常页面ErrorMvcAutoConfiguration.StaticView
                //这里按需深究代码吧!
    			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    			...
    		}
    		...
    		try {
    			if (mv.getStatus() != null) {
    				response.setStatus(mv.getStatus().value());
    			}
                //看下面代码块的StaticView的render块
    			view.render(mv.getModelInternal(), request, response);
    		}
    		catch (Exception ex) {
    			...
    		}
    	}
        
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
    // Load before the main WebMvcAutoConfiguration so that the error View is available
    @AutoConfigureBefore(WebMvcAutoConfiguration.class)
    @EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class, WebMvcProperties.class })
    public class ErrorMvcAutoConfiguration {
        
        ...
            
       	@Configuration(proxyBeanMethods = false)
    	@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
    	@Conditional(ErrorTemplateMissingCondition.class)
    	protected static class WhitelabelErrorViewConfiguration {
    
            //将创建一个名为error的系统默认异常页面View的Bean
    		private final StaticView defaultErrorView = new StaticView();
    
    		@Bean(name = "error")
    		@ConditionalOnMissingBean(name = "error")
    		public View defaultErrorView() {
    			return this.defaultErrorView;
    		}
    
    		// If the user adds @EnableWebMvc then the bean name view resolver from
    		// WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
    		@Bean
    		@ConditionalOnMissingBean
    		public BeanNameViewResolver beanNameViewResolver() {
    			BeanNameViewResolver resolver = new BeanNameViewResolver();
    			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
    			return resolver;
    		}
    
    	}     
       
        
    	private static class StaticView implements View {
    
    		private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
    
    		private static final Log logger = LogFactory.getLog(StaticView.class);
    
    		@Override
    		public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
    				throws Exception {
    			if (response.isCommitted()) {
    				String message = getMessage(model);
    				logger.error(message);
    				return;
    			}
    			response.setContentType(TEXT_HTML_UTF8.toString());
    			StringBuilder builder = new StringBuilder();
    			Object timestamp = model.get("timestamp");
    			Object message = model.get("message");
    			Object trace = model.get("trace");
    			if (response.getContentType() == null) {
    				response.setContentType(getContentType());
    			}
                //系统默认异常页面html代码
    			builder.append("

    Whitelabel Error Page

    "
    ).append( "

    This application has no explicit mapping for /error, so you are seeing this as a fallback.

    "
    ) .append("
    ").append(timestamp).append("
    "
    ) .append("
    There was an unexpected error (type=").append(htmlEscape(model.get("error"))) .append(", status=").append(htmlEscape(model.get("status"))).append(").
    "
    ); if (message != null) { builder.append("
    ").append(htmlEscape(message)).append("
    "
    ); } if (trace != null) { builder.append("
    ").append(htmlEscape(trace)).append("
    "
    ); } builder.append(""); response.getWriter().append(builder.toString()); } private String htmlEscape(Object input) { return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null; } private String getMessage(Map<String, ?> model) { Object path = model.get("path"); String message = "Cannot render error page for request [" + path + "]"; if (model.get("message") != null) { message += " and exception [" + model.get("message") + "]"; } message += " as the response has already been committed."; message += " As a result, the response may have the wrong status code."; return message; } @Override public String getContentType() { return "text/html"; } } }
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    55、错误处理-【源码流程】几种异常处理原理

    • 自定义错误页

      • error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
    • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的

    @Slf4j
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler({ArithmeticException.class,NullPointerException.class})  //处理异常
        public String handleArithException(Exception e){
    
            log.error("异常是:{}",e);
            return "login"; //视图地址
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responseStatus注解的信息底层调用 response.sendError(statusCode, resolvedReason),tomcat发送的/error
    @ResponseStatus(value= HttpStatus.FORBIDDEN,reason = "用户数量太多")
    public class UserTooManyException extends RuntimeException {
    
        public  UserTooManyException(){
    
        }
        public  UserTooManyException(String message){
            super(message);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    @Controller
    public class TableController {
        
    	@GetMapping("/dynamic_table")
        public String dynamic_table(@RequestParam(value="pn",defaultValue = "1") Integer pn,Model model){
            //表格内容的遍历
    	     List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                    new User("lisi", "123444"),
                    new User("haha", "aaaaa"),
                    new User("hehe ", "aaddd"));
            model.addAttribute("users",users);
    
            if(users.size()>3){
                throw new UserTooManyException();//抛出自定义异常
            }
            return "table/dynamic_table";
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • Spring自家异常如 org.springframework.web.bind.MissingServletRequestParameterExceptionDefaultHandlerExceptionResolver 处理Spring自家异常。

      • response.sendError(HttpServletResponse.SC_BAD_REQUEST/*400*/, ex.getMessage());
    • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

    @Order(value= Ordered.HIGHEST_PRECEDENCE)  //优先级,数字越小优先级越高
    @Component
    public class CustomerHandlerExceptionResolver implements HandlerExceptionResolver {
        @Override
        public ModelAndView resolveException(HttpServletRequest request,
                                             HttpServletResponse response,
                                             Object handler, Exception ex) {
    
            try {
                response.sendError(511,"我喜欢的错误");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return new ModelAndView();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • ErrorViewResolver 实现自定义处理异常
      • response.sendError(),error请求就会转给controller。
      • 你的异常没有任何人能处理,tomcat底层调用response.sendError(),error请求就会转给controller。
      • basicErrorController 要去的页面地址是 ErrorViewResolver
    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}")
    public class BasicErrorController extends AbstractErrorController {
    
        ...
        
    	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    		HttpStatus status = getStatus(request);
    		Map<String, Object> model = Collections
    				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
    		response.setStatus(status.value());
    		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    	}
        
        protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
    			Map<String, Object> model) {
            //这里用到ErrorViewResolver接口
    		for (ErrorViewResolver resolver : this.errorViewResolvers) {
    			ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
    			if (modelAndView != null) {
    				return modelAndView;
    			}
    		}
    		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
    • 27
    • 28
    • 29
    • 30
    • 31
    @FunctionalInterface
    public interface ErrorViewResolver {
    
    	ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    56、原生组件注入-原生注解与Spring方式注入

    官方文档 - Servlets, Filters, and listeners

    使用原生的注解

    @WebServlet(urlPatterns = "/my")
    public class MyServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            resp.getWriter().write("66666");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @Slf4j
    @WebFilter(urlPatterns={"/css/*","/images/*"}) //my
    public class MyFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            log.info("MyFilter初始化完成");
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("MyFilter工作");
            chain.doFilter(request,response);
        }
    
        @Override
        public void destroy() {
            log.info("MyFilter销毁");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    @Slf4j
    @WebListener
    public class MyServletContextListener implements ServletContextListener {
    
    
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            log.info("MySwervletContextListener监听到项目初始化完成");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            log.info("MySwervletContextListener监听到项目销毁");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    最后还要在主启动类添加注解@ServletComponentScan

    @ServletComponentScan(basePackages = "com.lun")//
    @SpringBootApplication(exclude = RedisAutoConfiguration.class)
    public class Boot05WebAdminApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(Boot05WebAdminApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Spring方式注入

    ServletRegistrationBean, FilterRegistrationBean, and ServletListenerRegistrationBean

    @Configuration(proxyBeanMethods = true)
    public class MyRegistConfig {
    
        @Bean
        public ServletRegistrationBean myServlet(){
            MyServlet myServlet = new MyServlet();
    
            return new ServletRegistrationBean(myServlet,"/my","/my02");
        }
    
    
        @Bean
        public FilterRegistrationBean myFilter(){
    
            MyFilter myFilter = new MyFilter();
    //        return new FilterRegistrationBean(myFilter,myServlet());
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
            filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
            return filterRegistrationBean;
        }
    
        @Bean
        public ServletListenerRegistrationBean myListener(){
            MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
            return new ServletListenerRegistrationBean(mySwervletContextListener);
        }
    }
    
    • 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

    57、原生组件注入-【源码分析】DispatcherServlet注入原理

    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration配置类

    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass(DispatcherServlet.class)
    @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
    public class DispatcherServletAutoConfiguration {
    
    	/*
    	 * The bean name for a DispatcherServlet that will be mapped to the root URL "/"
    	 */
    	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
    
    	/*
    	 * The bean name for a ServletRegistrationBean for the DispatcherServlet "/"
    	 */
    	public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
    
    	@Configuration(proxyBeanMethods = false)
    	@Conditional(DefaultDispatcherServletCondition.class)
    	@ConditionalOnClass(ServletRegistration.class)
    	@EnableConfigurationProperties(WebMvcProperties.class)
    	protected static class DispatcherServletConfiguration {
    
            //创建DispatcherServlet类的Bean
    		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    		public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
    			DispatcherServlet dispatcherServlet = new DispatcherServlet();
    			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
    			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
    			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
    			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
    			dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
    			return dispatcherServlet;
    		}
    
    		@Bean
    		@ConditionalOnBean(MultipartResolver.class)
    		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
    		public MultipartResolver multipartResolver(MultipartResolver resolver) {
    			// Detect if the user has created a MultipartResolver but named it incorrectly
    			return resolver;
    		}
    
    	}
        
        @Configuration(proxyBeanMethods = false)
    	@Conditional(DispatcherServletRegistrationCondition.class)
    	@ConditionalOnClass(ServletRegistration.class)
    	@EnableConfigurationProperties(WebMvcProperties.class)
    	@Import(DispatcherServletConfiguration.class)
    	protected static class DispatcherServletRegistrationConfiguration {
    
            //注册DispatcherServlet类
    		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
    		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
    				WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
    			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
    					webMvcProperties.getServlet().getPath());
    			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
    			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
    			multipartConfig.ifAvailable(registration::setMultipartConfig);
    			return registration;
    		}
    
    	}
        
        ...
        
    }
    
    • 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
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    DispatcherServlet默认映射的是 / 路径,可以通过在配置文件修改spring.mvc.servlet.path=/mvc

    58、嵌入式Servlet容器-【源码分析】切换web服务器与定制化

    • 默认支持的WebServer

      • Tomcat, Jetty, or Undertow
      • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器。
    • 原理

      • SpringBoot应用启动发现当前是Web应用,web场景包-导入tomcat。
      • web应用会创建一个web版的IOC容器 ServletWebServerApplicationContext
      • ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory (Servlet 的web服务器工厂——>Servlet 的web服务器)。
      • SpringBoot底层默认有很多的WebServer工厂(ServletWebServerFactoryConfiguration内创建Bean),如:
        • TomcatServletWebServerFactory
        • JettyServletWebServerFactory
        • UndertowServletWebServerFactory
      • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
      • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)。
      • ServletWebServerFactoryConfiguration 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
      • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize——this.tomcat.start();
      • 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。

    Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-tomcatartifactId>
            exclusion>
        exclusions>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jettyartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    官方文档 - Use Another Web Server

    定制Servlet容器

    • 实现WebServerFactoryCustomizer

      • 把配置文件的值和ServletWebServerFactory进行绑定
    • 修改配置文件 server.xxx

    • 直接自定义 ConfigurableServletWebServerFactory

    xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    
        @Override
        public void customize(ConfigurableServletWebServerFactory server) {
            server.setPort(9000);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    59、定制化原理-SpringBoot定制化组件的几种方式(小结)

    定制化的常见方式

    • 修改配置文件

    • xxxxxCustomizer

    • 编写自定义的配置类 xxxConfiguration + @Bean替换、增加容器中默认组件,视图解析器

    • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能 + @Bean给容器中再扩展一些组件

    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer{
    }
    
    • 1
    • 2
    • 3
    • @EnableWebMvc + WebMvcConfigurer@Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍)。
      • 原理:
        1. WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。
        2. 一旦使用 @EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)
        3. DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用
          • 把所有系统中的WebMvcConfigurer拿过来,所有功能的定制都是这些WebMvcConfigurer合起来一起生效。
          • 自动配置了一些非常底层的组件,如RequestMappingHandlerMapping,这些组件依赖的组件都是从容器中获取如。
          • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
        4. WebMvcAutoConfiguration里面的配置要能生效必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
        5. @EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。

    原理分析套路

    场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties - 绑定配置文件项。

    ean),如:
    - TomcatServletWebServerFactory
    - JettyServletWebServerFactory
    - UndertowServletWebServerFactory

    • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
    • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)。
    • ServletWebServerFactoryConfiguration 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
    • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize——this.tomcat.start();
    • 内嵌服务器,与以前手动把启动服务器相比,改成现在使用代码启动(tomcat核心jar包存在)。

    Spring Boot默认使用Tomcat服务器,若需更改其他服务器,则修改工程pom.xml:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-tomcatartifactId>
            exclusion>
        exclusions>
    dependency>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jettyartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    官方文档 - Use Another Web Server

    定制Servlet容器

    • 实现WebServerFactoryCustomizer

      • 把配置文件的值和ServletWebServerFactory进行绑定
    • 修改配置文件 server.xxx

    • 直接自定义 ConfigurableServletWebServerFactory

    xxxxxCustomizer:定制化器,可以改变xxxx的默认规则

    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
    
        @Override
        public void customize(ConfigurableServletWebServerFactory server) {
            server.setPort(9000);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    59、定制化原理-SpringBoot定制化组件的几种方式(小结)

    定制化的常见方式

    • 修改配置文件

    • xxxxxCustomizer

    • 编写自定义的配置类 xxxConfiguration + @Bean替换、增加容器中默认组件,视图解析器

    • Web应用 编写一个配置类实现 WebMvcConfigurer 即可定制化web功能 + @Bean给容器中再扩展一些组件

    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer{
    }
    
    • 1
    • 2
    • 3
    • @EnableWebMvc + WebMvcConfigurer@Bean 可以全面接管SpringMVC,所有规则全部自己重新配置; 实现定制和扩展功能(高级功能,初学者退避三舍)。
      • 原理:
        1. WebMvcAutoConfiguration默认的SpringMVC的自动配置功能类,如静态资源、欢迎页等。
        2. 一旦使用 @EnableWebMvc ,会@Import(DelegatingWebMvcConfiguration.class)
        3. DelegatingWebMvcConfiguration的作用,只保证SpringMVC最基本的使用
          • 把所有系统中的WebMvcConfigurer拿过来,所有功能的定制都是这些WebMvcConfigurer合起来一起生效。
          • 自动配置了一些非常底层的组件,如RequestMappingHandlerMapping,这些组件依赖的组件都是从容器中获取如。
          • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
        4. WebMvcAutoConfiguration里面的配置要能生效必须 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
        5. @EnableWebMvc 导致了WebMvcAutoConfiguration 没有生效。

    原理分析套路

    场景starter - xxxxAutoConfiguration - 导入xxx组件 - 绑定xxxProperties - 绑定配置文件项。

  • 相关阅读:
    【15】基础知识:React扩展知识
    uniapp 跳转到指定位置
    安卓文件资源中,一个字串包含引用其他字串的写法
    实时光线追踪(3)Ray Casting
    山东省国高教育研究有限公司与菏泽学院共同举办“AI赋能核心期刊论文选题与高质量写作”学术讲座。
    【数据结构】红黑树
    win10蓝屏0xc000021a怎么修复?
    前端CSS三种实现导航栏吸顶效果
    两分钟打造一个转属于你的网址导航(零基础,告别广告困扰)
    [Android] AndroidManifest.xml 详解
  • 原文地址:https://blog.csdn.net/weixin_42200347/article/details/126571482