静态资源配置原理
• SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类)
• SpringMVC功能的自动配置类 WebMvcAutoConfiguration
生效条件:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {}
给容器中配了什么。
WebMvcAutoConfigurationAdapter
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
//把WebMvcProperties和ResourceProperties类绑在容器中
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
WebMvcProperties和spring.mvc前缀相关的进行绑定
ResourceProperties和spring.resources前缀相关的进行绑定
• 配置文件的相关属性和xxx进行了绑定。WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources
WebMvcAutoConfigurationAdapter配置类只有一个有参构造器
//有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties;获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory容器
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath dispatherServlet能处理的路径
//ServletRegistrationBean 给应用注册原生的Servlet、Filter这些用的....
public WebMvcAutoConfigurationAdapter(
ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations
) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
资源处理的默认规则
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//是否在配置文件里面配置了spring.resources的add-mappings属性
//默认为true,如果设置为false所有在配置文件中的静态配置都会失效
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
//getCache().getPeriod可以在spring.resources中配缓存
//cache下的period属性以秒单位 ,意思就是所有的静态资源默认可以存多少秒,以后浏览器就会把静态资源存这么长的时间
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
//webjars的规则
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
//获取spring.mvc的static-path-pattern属性的值,如果没有设置,默认为/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
//getStaticLocations获取静态位置,默认为 "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
// /**的静态请求在上面四个静态文件中找 .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
spring:
# mvc:
# static-path-pattern: /res/**
resources:
add-mappings: false 禁用所有静态资源规则
欢迎页的处理规则
HandlerMapping:处理器映射。保存了每一个Handler能处理哪些请求
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}
创建WelcomePageHandlerMapping对象的构造方法
WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
//
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
logger.info("Adding welcome page: " + welcomePage.get());
//要用欢迎页功能,必须是/**,就转到欢迎页,如果欢迎页不存在就不管
this.setRootViewName("forward:index.html");
} else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
// 调用Controller /index
logger.info("Adding welcome page template: index");
this.setRootViewName("index");
}
}
4、favicon
与代码没关系,浏览器都会默认发当前项目下的favicon.ico /favicon.ico
3、请求参数处理
0、请求映射
1、rest使用与原理
• @xxxMapping;
• Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
• 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
• 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
• 核心Filter;HiddenHttpMethodFilter
• 用法: 表单method=post,隐藏域 _method=put
• SpringBoot中手动开启
WebMvcAutoConfiguration中加入的组件
springmvc用来兼容rest风格的,表单可以提交put,delete等请求
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
//开启spring.mvc.hiddenmethod.filter功能 设置为true,开启rest风格,默认为false,
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"},
matchIfMissing = false
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
//WebMvcAutoConfiguration中加入支持rest风格的组件时,如果自己定义了HiddenHttpMethodFilter,就不加框架写好的
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//自定义filter
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
Rest原理(表单提交要使用REST的时候)
• 表单提交会带上_method=PUT
• 请求过来被HiddenHttpMethodFilter拦截
• 请求是否正常,并且是POST
• 获取到_method的值。
• 兼容以下请求;PUT.DELETE.PATCH
• 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
• 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
Rest使用客户端工具,
• 如PostMan直接发送Put、delete等方式请求,无需Filter。
HiddenHttpMethodFilter类
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//拿到原生request请求
HttpServletRequest requestToUse = request;
//表单提交必须是post才能用rest风格并且请求没有错误
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
//获取_method的值 若为delete
String paramValue = request.getParameter(this.methodParam);
//不为空
if (StringUtils.hasLength(paramValue)) {
//delete转成大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
//ALLOWED_METHODS兼容put,delete,patch
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
ALLOWED_METHODS支持的请求方式
在HiddenHttpMethodFilter静态代码块中
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
HttpMethodRequestWrapper实际上就是HttpServletRequest,重写了getMethod方法,method值是我们传入的值
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
• 扩展:如何把_method 这个名字换成我们自己喜欢的。
@Configuration(proxyBeanMethods = false)
public class WebConfig {
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_m");
return hiddenHttpMethodFilter;
}
}
2、请求映射原理
doGet调用processRequest方法,processRequest调用doService方法,doService方法调用doDispatcher方法进行映射
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet-》doDispatch()
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;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = getHandler(processedRequest);
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//HandlerMapping:处理器映射。/xxx->>xxxx
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
handlerMapping
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
所有的请求映射都在HandlerMapping中。
• SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
• SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
• 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
• 如果有就找到这个请求对应的handler
• 如果没有就是下一个 HandlerMapping
• 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//HandlerMapping:处理器映射。/xxx->>xxxx
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
3、参数处理原理
• DispatcherServlet类中doDispatcher()方法中调用getHandler方法,该方法中再HandlerMapping中找到能处理请求的Handler(Controller.method())
• DispatcherServlet类中doDispatcher()方法为当前找到的Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
• 适配器执行目标方法并确定方法参数的每一个值
1、HandlerAdapter
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
2、执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
DispatcherServlet类中doDispatcher()方法中,该行是执行目标方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//上面的handle方法调用requestMappingHandlerAdapter中的handleInternal()方法,handleInternal方法中调用invokeHandlerMethod执行目标方法
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//上面的invokeHandlerMethod方法中调用ServletInvocableHandlerMethod中invokeAndHandle方法,invokeAndHandle方法调用自己的invokeForRequest方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//InvocableHandlerMethod中invokeForRequest方法
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3、参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器
4、返回值处理器
5、如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
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;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
5.1、挨个判断所有参数解析器那个支持解析这个参数
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
5.2、解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
1.2、Servlet API:
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
ServletRequestMethodArgumentResolver 以上的部分参数
HttpServlet为形参的时候,如何给他注入值
ServletRequestMethodArgumentResolver
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
return (WebRequest.class.isAssignableFrom(paramType) ||
ServletRequest.class.isAssignableFrom(paramType) ||
MultipartRequest.class.isAssignableFrom(paramType) ||
HttpSession.class.isAssignableFrom(paramType) ||
(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
Principal.class.isAssignableFrom(paramType) ||
InputStream.class.isAssignableFrom(paramType) ||
Reader.class.isAssignableFrom(paramType) ||
HttpMethod.class == paramType ||
Locale.class == paramType ||
TimeZone.class == paramType ||
ZoneId.class == paramType);
}
1.3、复杂参数:
Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
Map<String,Object> map, Model model, HttpServletRequest request 都是可以给request域中放数据,
request.getAttribute();
Map、Model类型的参数,找到支持的参数解析器后该解析器中会返回 mavContainer.getModel();—> getModel返回BindingAwareModelMap ,BindingAwareModelMap 是Model 也是Map
无论是model还是map最终底层调用的是mavContainer.getModel(); 从而获取到值
model和map其实是同一个对象,BindingAwareModelMap 都是同一个
6、目标方法执行完成
将所有的数据都放在 ModelAndViewContainer;包含要去的面地址View。还包含Model数据。
7、处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
1.4、自定义对象参数:
可以自动类型转换与格式化,可以级联封装。
/**
* 姓名: <input name="userName"/> <br/>
* 年龄: <input name="age"/> <br/>
* 生日: <input name="birth"/> <br/>
* 宠物姓名:<input name="pet.name"/><br/>
* 宠物年龄:<input name="pet.age"/>
*/
@Data
public class Person {
private String userName;
private Integer age;
private Date birth;
private Pet pet;
}
@Data
public class Pet {
private String name;
private String age;
}
数据绑定:页面提交的请求数据(GET,POST)都可以和对象属性进行绑定
@PostMapping("/saveuser")
public Person saveuser(Person person){
return person;
}
原理:
1.
自定义类型的参数用ServletModelAttributeMethodProcessor进行解析
2、参数处理原理
• HandlerMapping中找到能处理请求的Handler(Controller.method())
• 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter
• 适配器执行目标方法并确定方法参数的每一个值
1、HandlerAdapter
0 - 支持方法上标注@RequestMapping
1 - 支持函数式编程的
xxxxxx
2、执行目标方法
// Actually invoke the handler.
//DispatcherServlet -- doDispatch
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mav = invokeHandlerMethod(request, response, handlerMethod); //执行目标方法
//ServletInvocableHandlerMethod
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//获取方法的参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
3、参数解析器-HandlerMethodArgumentResolver
确定将要执行的目标方法的每一个参数的值是什么;
SpringMVC目标方法能写多少种参数类型。取决于参数解析器。
当前解析器是否支持解析这种参数
支持就调用 resolveArgument
4、返回值处理器
5、如何确定目标方法每一个参数的值
============InvocableHandlerMethod==========================
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
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;
}
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
5.1、挨个判断所有参数解析器那个支持解析这个参数
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
5.2、解析这个参数的值
调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法即可
5.3、自定义类型参数 封装POJO
ServletModelAttributeMethodProcessor 这个参数处理器支持
以下是如何判断支持的
先判断是否为简单类型,不是简单类型,所以返回true说明ServletModelAttributeMethodProcessor 就支持
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
进行处理
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
String name = ModelFactory.getNameForParameter(parameter);
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
//创建一个空person对象实例,准备给这个person对象封装页面带来的所有值
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
//bindingResult 就是绑定结果,空person对象就是要跟页面的数据进行绑定
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
//web数据绑定器,将请求参数的值绑定到指定的JavaBean里面也就是attribute变量里面,一开始创建的空对象
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
WebDataBinder :web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型(JavaBean – Integer)
byte – > file
@FunctionalInterfacepublic interface Converter<S, T>
未来我们可以给WebDataBinder里面放自己的Converter;
private static final class StringToNumber implements Converter<String, T>
自定义 Converter
//1、WebMvcConfigurer定制化SpringMVC的功能
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
// 不移除;后面的内容。矩阵变量功能就可以生效
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String source) {
// 啊猫,3
if(!StringUtils.isEmpty(source)){
Pet pet = new Pet();
String[] split = source.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
};
}
自定义的converter原理
bind中的conveter从124个变成了125个。数据类型转换pet的时候用我们自己的converter
6、目标方法执行完成
将所有的数据都放在 ModelAndViewContainer;包含要去的页面地址View。还包含Model数据。
7、处理派发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
InternalResourceView:
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response);
}
}
暴露模型作为请求域属性
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
HttpServletRequest request) throws Exception {
//model中的所有数据遍历挨个放在请求域中
model.forEach((name, value) -> {
if (value != null) {
request.setAttribute(name, value);
}
else {
request.removeAttribute(name);
}
});
}
4、数据响应与内容协商
1、响应JSON
1.1、jackson.jar+@ResponseBody
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
web场景自动引入了json场景
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.3.4.RELEASE</version>
<scope>compile</scope>
</dependency>
给前端自动返回json数据;
1、返回值解析器
//利用返回值处理器处理返回值
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
handleReturnValue源码
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//先把返回值person对象和返回值的类型person类型拿来,找哪个返回值处理器能处理,遍历循环每一个返回值处理器,找异步返回值处理器
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
//找到以后用返回值处理器处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
handleReturnValue源码,返回值处理器如何处理
RequestResponseBodyMethodProcessor
@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);
}
2、返回值解析器原理
• 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
• 2、返回值处理器调用 handleReturnValue 进行处理
• 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
• 1. 利用 MessageConverters 进行处理 将数据写为json
• 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
• 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
• 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
• 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
• 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
1.2、SpringMVC到底支持哪些返回值
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
1.3、HTTPMessageConverter原理
1、MessageConverter规范
HttpMessageConverter: 看是否支持将 此 Class类型的对象,转为MediaType类型的数据。
例子:Person对象转为JSON。或者 JSON转为Person
2、默认的MessageConverter
0 - 只支持Byte类型的
1 - String
2 - String
3 - Resource
4 - ResourceRegion
5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class
6 - MultiValueMap
7 - true
8 - true
9 - 支持注解方式xml处理的。
最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
2、内容协商
根据客户端接收能力不同,返回不同媒体类型的数据。
1、引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
2、postman分别测试返回json和xml
只需要改变请求头中Accept字段。Http协议中规定的,告诉服务器本客户端可以接收的数据类型。
3、开启浏览器参数方式内容协商功能
为了方便内容协商,开启基于请求参数的内容协商功能。
spring:
contentnegotiation:
favor-parameter: true #开启请求参数内容协商模式
发请求: http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
确定客户端接收什么样的内容类型;
1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)
2、最终进行内容协商返回给客户端json即可。
4、内容协商原理
• 1、判断当前响应头中是否已经有确定的媒体类型。MediaType
• 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)【application/xml】
• contentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)
4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。
5、客户端需要【application/xml】。服务端能力【10种、既能转换为json、xml】
6、进行内容协商的最佳匹配媒体类型
7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。
导入了jackson处理xml的包,xml的converter就会自动进来
WebMvcConfigurationSupport
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
所有的MessageConverter合起来可以支持各种媒体类型数据的操作(支持读,写),支持读写是因为MessageConverter接口能读就调用读方法,能写就调用写方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> var1, @Nullable MediaType var2);
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
5、自定义 MessageConverter
实现多协议数据兼容。json、xml、x-guigu
0、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;
SpringMVC的什么功能。一个入口给容器中添加一个 WebMvcConfigurer
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
}
有可能我们添加的自定义的功能会覆盖默认很多功能,导致一些默认的功能失效。
大家考虑,上述功能除了我们完全自定义外?SpringBoot有没有为我们提供基于配置文件的快速修改媒体类型功能?怎么配置呢?【提示:参照SpringBoot官方文档web开发内容协商章节】
WebConfig
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new GuiguMessageConverter());
}
GuiguMessageConverter
/**
* 自定义的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());
}
}
自定义内容协商策略
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//Map<String, MediaType> 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");
HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();
configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
}
1、视图解析
1、视图解析原理流程
1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer这个容器里面。包括数据和视图地址(想要去的路径)
2、如果方法的参数是一个自定义类型对象(这个对象的值是从请求参数中确定的),把自定义类型对象的值重新放在 ModelAndViewContainer
3、任何目标方法执行完成以后都会返回 ModelAndView对象(里面有数据和视图地址,原来的数据是放在ModelAndViewContainer里面的,ModelAndView中的数据是从ModelAndViewContainer中获取的)。
如果请求的方法没有返回视图名,会从request中获取请求的路径作为默认的跳转页
4、processDispatchResult 处理派发结果(最终决定页面该如何响应),以下是如何进行响应的
4.1 、render(mv, request, response); 进行页面渲染逻辑
mv中既有要去的视图地址,也有给model中放的数据
4.1.1、根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
怎么得到view对象
4.1.1.1、所有的视图解析器(viewResolvers中)尝试是否能根据当前返回值得到View对象
4.1.1.2、根据返回值得到了RedirectView redirect:/main.html --> Thymeleaf new RedirectView()
4.1.1.3、ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
4.1.1.4、view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
• RedirectView 如何渲染【重定向到一个页面】
• 1、获取目标url地址:/main.html
• 2、response.sendRedirect(encodedURL);
视图解析:
• 返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
• 返回值以 redirect: 开始: new RedirectView() --》 render就是重定向
• 返回值是普通字符串: new ThymeleafView()—> 如果需要响应html,利用html的页面解析器把所有的内容都写到write中
自定义视图解析器+自定义视图; 大厂学院。
3、拦截器原理
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
2、先来顺序执行 所有拦截器的 preHandle方法
• 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
• 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的 afterCompletion;
3、如果任何一个拦截器返回false。直接跳出不执行目标方法
4、所有拦截器都返回True。执行目标方法
5、倒序执行所有拦截器的postHandle方法。
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
7、文件上传
1、页面表单
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
2、文件上传代码
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@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";
}
3、自动配置原理
文件上传自动配置类-MultipartAutoConfiguration-MuitipartProperties
• 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】,只能解析标准的以servlet这种协议上传的文件
所有上传有关的配置全都配置在MuitipartProperties
如果想替换StandardServletMultipartResolver,直接在自己的自动配置类中配置即可,原因如下:
@ConditionalOnMissingBean({MultipartResolver.class})
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
• 原理步骤
处理所有请求之前,即getHandler(processedRequest)之前,会先执行checkMultipart(request),判断当前请求是不是文件上传请求,如果是,会把原生的request包装成processRequest
checkMultipart(request)方法
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//全系统就一个multipartResolver
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
} else if (this.hasMultipartException(request)) {
this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
} else {
try {
//如果是文件上传请求,就用文件上传解析器把这个request请求解析了然后返回
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException var3) {
if (request.getAttribute("javax.servlet.error.exception") == null) {
throw var3;
}
}
this.logger.debug("Multipart resolution failed for error dispatch", var3);
}
}
return request;
}
isMultipart源码
StandardServletMultipartResolver中
public boolean isMultipart(HttpServletRequest request) {
//判断请求类型是不是multipart/
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
• 原理步骤
• 1、请求进来使用文件上传解析器判断(isMultipart判断参数是不是multipart/)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
• 2、参数解析器来解析请求中的文件内容封装成MultipartFile
• 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
最后使用FileCopyUtils。实现文件流的拷贝
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos)
3、异常处理自动配置原理
• 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】。(默认是一个白页)
以json数据响应出去
以html数据响应出去
4、异常处理步骤流程
1、执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
2、进入视图解析流程(页面渲染?)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
mv只有正确执行了方法才有值,否则为null,此处出现异常所以为null
dispatchException:异常对象
3、mv = processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
• 1、遍历所有的 handlerExceptionResolvers,看谁能处理当前异常【handlerExceptionResolvers是HandlerExceptionResolver的list ,HandlerExceptionResolver是处理器异常解析器】
2、系统默认的 异常解析器;
• 1、DefaultErrorAttributes先来处理异常。把异常信息保存到request域,并且返回null;
• 2、默认没有任何人能处理异常,所以异常会被抛出以/error请求的方式抛出
• 1、如果没有任何人能处理最终底层就会发送 /error 请求。会被底层的BasicErrorController处理,调用解析错误视图方法
• 2、解析错误视图;遍历所有的 ErrorViewResolver 看谁能解析。
3、默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
4、模板引擎最终响应这个页面 error/500.html
定制错误处理逻辑
• 自定义错误页
• error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
• @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
• @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
• Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
• response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则
• ErrorViewResolver 实现自定义处理异常;
• response.sendError 。error请求就会转给controller
• 你的异常没有任何人能处理。tomcat底层 response.sendError。error请求就会转给controller
• basicErrorController 要去的页面地址是 ErrorViewResolver ;
扩展:DispatcherServlet 如何注册进来
DispatcherServletAutoConfiguration:DispatcherServlet 的自动配置类,也是在自动配置包下
DispatcherServletAutoConfiguration:
@Bean(
name = {"dispatcherServlet"}
)
// DispatcherServlet很多属性都是绑定在WebMvc里面的
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(
name = {"dispatcherServletRegistration"}
)
@ConditionalOnBean(
value = {DispatcherServlet.class},
name = {"dispatcherServlet"}
)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
//通过下面这句进行注册进来的,getPath默认是/,可以通过修改spring.mvc.servlet.path修改默认的处理请求的路径
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet, webMvcProperties.getServlet().getPath());
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
DispatcherServletRegistrationBean
public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet> implements DispatcherServletPath {
WebMvcProperties
• 容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
• 通过 ServletRegistrationBean 把 DispatcherServlet 配置进来。
• 默认映射的是 / 路径。
Tomcat-Servlet;
多个Servlet都能处理到同一层路径,精确优选原则
A: /my/
B: /my/1
10、嵌入式Servlet容器
1、切换嵌入式Servlet容器
• 默认支持的webServer
• Tomcat, Jetty, or Undertow
• ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
• 切换服务器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
//想要导入的服务器
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
• 原理
• SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
• web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
• ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory(Servlet 的web服务器工厂—> Servlet 的web服务器)
• SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
• 底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
• ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedJetty;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedTomcat;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration.EmbeddedUndertow;
• ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。根据 @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
判断,(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedUndertow {
EmbeddedUndertow() {
}
@Bean
UndertowServletWebServerFactory undertowServletWebServerFactory(ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers, ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.getDeploymentInfoCustomizers().addAll((Collection)deploymentInfoCustomizers.orderedStream().collect(Collectors.toList()));
factory.getBuilderCustomizers().addAll((Collection)builderCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedJetty {
EmbeddedJetty() {
}
@Bean
JettyServletWebServerFactory JettyServletWebServerFactory(ObjectProvider<JettyServerCustomizer> serverCustomizers) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.getServerCustomizers().addAll((Collection)serverCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
• TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize—this.tomcat.start();
• 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
2、定制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、SpringBoot启动过程
• 创建 SpringApplication
• 保存一些信息。
• 判定当前应用的类型。ClassUtils。Servlet
• bootstrappers:初始启动引导器(List):去spring.factories文件中找 org.springframework.boot.Bootstrapper
• 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer
• List<ApplicationContextInitializer<?>> initializers
• 找 ApplicationListener ;应用监听器。去spring.factories找 ApplicationListener
• List<ApplicationListener<?>> listeners
• 运行 SpringApplication
• StopWatch
• 记录应用的启动时间
• 创建引导上下文(Context环境)createBootstrapContext()
• 获取到所有之前的 bootstrappers 挨个执行 intitialize() 来完成对引导启动器上下文环境设置
• 让当前应用进入headless模式。java.awt.headless
• 获取所有 RunListener(运行监听器)【为了方便所有Listener进行事件感知】
• getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener.
• 遍历 SpringApplicationRunListener 调用 starting 方法;
• 相当于通知所有感兴趣系统正在启动过程的人,项目正在 starting。
• 保存命令行参数;ApplicationArguments
• 准备环境 prepareEnvironment();
• 返回或者创建基础环境信息对象。StandardServletEnvironment
• 配置环境信息对象。
• 读取所有的配置源的配置属性值。
• 绑定环境信息
• 监听器调用 listener.environmentPrepared();通知所有的监听器当前环境准备完成
• 创建IOC容器(createApplicationContext())
• 根据项目类型(Servlet)创建容器,
• 当前会创建 AnnotationConfigServletWebServerApplicationContext
• 准备ApplicationContext IOC容器的基本信息 prepareContext()
• 保存环境信息
• IOC容器的后置处理流程。
• 应用初始化器;applyInitializers;
• 遍历所有的 ApplicationContextInitializer 。调用 initialize.。来对ioc容器进行初始化扩展功能
• 遍历所有的 listener 调用 contextPrepared。EventPublishRunListenr;通知所有的监听器contextPrepared
• 所有的监听器 调用 contextLoaded。通知所有的监听器 contextLoaded;
• 刷新IOC容器。refreshContext
• 创建容器中的所有组件(Spring注解)
• 容器刷新完成后工作?afterRefresh
• 所有监听 器 调用 listeners.started(context); 通知所有的监听器 started
• 调用所有runners;callRunners()
• 获取容器中的 ApplicationRunner
• 获取容器中的 CommandLineRunner
• 合并所有runner并且按照@Order进行排序
• 遍历所有的runner。调用 run 方法
• 如果以上有异常,
• 调用Listener 的 failed
• 调用所有监听器的 running 方法 listeners.running(context); 通知所有的监听器 running
• running如果有问题。继续通知 failed 。调用所有 Listener 的 failed;通知所有的监听器 failed
public interface Bootstrapper {
/**
* Initialize the given {@link BootstrapRegistry} with any required registrations.
* @param registry the registry to initialize
*/
void intitialize(BootstrapRegistry registry);
}
@FunctionalInterface
public interface ApplicationRunner {
/**
* Callback used to run the bean.
* @param args incoming application arguments
* @throws Exception on error
*/
void run(ApplicationArguments args) throws Exception;
}
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;